chiark / gitweb /
Large update to the config wizard, new style machine check (unfinished)
[cura.git] / Cura / gui / projectPlanner.py
1 from __future__ import absolute_import\r
2 import __init__\r
3 \r
4 import wx, os, platform, types, webbrowser, math, subprocess, threading, time, re, shutil\r
5 import ConfigParser\r
6 import numpy\r
7 \r
8 from wx import glcanvas\r
9 try:\r
10         import OpenGL\r
11         OpenGL.ERROR_CHECKING = False\r
12         from OpenGL.GLU import *\r
13         from OpenGL.GL import *\r
14         hasOpenGLlibs = True\r
15 except:\r
16         print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
17         hasOpenGLlibs = False\r
18 \r
19 from gui import opengl\r
20 from gui import toolbarUtil\r
21 from gui import icon\r
22 from gui import configBase\r
23 from gui import printWindow\r
24 from gui import dropTarget\r
25 from util import validators\r
26 from util import profile\r
27 from util import util3d\r
28 from util import meshLoader\r
29 from util import stl\r
30 from util import mesh\r
31 from util import sliceRun\r
32 from util import gcodeInterpreter\r
33 from util import exporer\r
34 \r
35 class Action(object):\r
36         pass\r
37 \r
38 class ProjectObject(object):\r
39         def __init__(self, parent, filename):\r
40                 super(ProjectObject, self).__init__()\r
41 \r
42                 self.mesh = meshLoader.loadMesh(filename)\r
43 \r
44                 self.parent = parent\r
45                 self.filename = filename\r
46                 self.scale = 1.0\r
47                 self.rotate = 0.0\r
48                 self.flipX = False\r
49                 self.flipY = False\r
50                 self.flipZ = False\r
51                 self.swapXZ = False\r
52                 self.swapYZ = False\r
53                 self.extruder = 0\r
54                 self.profile = None\r
55                 \r
56                 self.modelDisplayList = None\r
57                 self.modelDirty = False\r
58 \r
59                 self.mesh.getMinimumZ()\r
60                 \r
61                 self.centerX = -self.getMinimum()[0] + 5\r
62                 self.centerY = -self.getMinimum()[1] + 5\r
63                 \r
64                 self.updateModelTransform()\r
65 \r
66                 self.centerX = -self.getMinimum()[0] + 5\r
67                 self.centerY = -self.getMinimum()[1] + 5\r
68 \r
69         def isSameExceptForPosition(self, other):\r
70                 if self.filename != other.filename:\r
71                         return False\r
72                 if self.scale != other.scale:\r
73                         return False\r
74                 if self.rotate != other.rotate:\r
75                         return False\r
76                 if self.flipX != other.flipX:\r
77                         return False\r
78                 if self.flipY != other.flipY:\r
79                         return False\r
80                 if self.flipZ != other.flipZ:\r
81                         return False\r
82                 if self.swapXZ != other.swapXZ:\r
83                         return False\r
84                 if self.swapYZ != other.swapYZ:\r
85                         return False\r
86                 if self.extruder != other.extruder:\r
87                         return False\r
88                 if self.profile != other.profile:\r
89                         return False\r
90                 return True\r
91 \r
92         def updateModelTransform(self):\r
93                 self.mesh.setRotateMirror(self.rotate, self.flipX, self.flipY, self.flipZ, self.swapXZ, self.swapYZ)\r
94                 minZ = self.mesh.getMinimumZ()\r
95                 minV = self.getMinimum()\r
96                 maxV = self.getMaximum()\r
97                 self.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minZ])\r
98                 minZ = self.mesh.getMinimumZ()\r
99                 self.modelDirty = True\r
100         \r
101         def getMinimum(self):\r
102                 return self.mesh.getMinimum()\r
103         def getMaximum(self):\r
104                 return self.mesh.getMaximum()\r
105         def getSize(self):\r
106                 return self.mesh.getSize()\r
107         \r
108         def clone(self):\r
109                 p = ProjectObject(self.parent, self.filename)\r
110 \r
111                 p.centerX = self.centerX + 5\r
112                 p.centerY = self.centerY + 5\r
113                 \r
114                 p.filename = self.filename\r
115                 p.scale = self.scale\r
116                 p.rotate = self.rotate\r
117                 p.flipX = self.flipX\r
118                 p.flipY = self.flipY\r
119                 p.flipZ = self.flipZ\r
120                 p.swapXZ = self.swapXZ\r
121                 p.swapYZ = self.swapYZ\r
122                 p.extruder = self.extruder\r
123                 p.profile = self.profile\r
124                 \r
125                 p.updateModelTransform()\r
126                 \r
127                 return p\r
128         \r
129         def clampXY(self):\r
130                 if self.centerX < -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]:\r
131                         self.centerX = -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]\r
132                 if self.centerY < -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]:\r
133                         self.centerY = -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]\r
134                 if self.centerX > self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale:\r
135                         self.centerX = self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale\r
136                 if self.centerY > self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale:\r
137                         self.centerY = self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale\r
138 \r
139 class projectPlanner(wx.Frame):\r
140         "Main user interface window"\r
141         def __init__(self):\r
142                 super(projectPlanner, self).__init__(None, title='Cura - Project Planner')\r
143                 \r
144                 wx.EVT_CLOSE(self, self.OnClose)\r
145                 self.panel = wx.Panel(self, -1)\r
146                 self.SetSizer(wx.BoxSizer(wx.VERTICAL))\r
147                 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)\r
148                 #self.SetIcon(icon.getMainIcon())\r
149                 \r
150                 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))\r
151                 \r
152                 self.list = []\r
153                 self.selection = None\r
154                 self.printMode = 0\r
155 \r
156                 self.machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])\r
157                 self.headSizeMin = numpy.array([profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0])\r
158                 self.headSizeMax = numpy.array([profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0])\r
159 \r
160                 self.extruderOffset = [\r
161                         numpy.array([0,0,0]),\r
162                         numpy.array([profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1'), 0]),\r
163                         numpy.array([profile.getPreferenceFloat('extruder_offset_x2'), profile.getPreferenceFloat('extruder_offset_y2'), 0]),\r
164                         numpy.array([profile.getPreferenceFloat('extruder_offset_x3'), profile.getPreferenceFloat('extruder_offset_y3'), 0])]\r
165 \r
166                 self.toolbar = toolbarUtil.Toolbar(self.panel)\r
167 \r
168                 toolbarUtil.NormalButton(self.toolbar, self.OnLoadProject, 'open.png', 'Open project')\r
169                 toolbarUtil.NormalButton(self.toolbar, self.OnSaveProject, 'save.png', 'Save project')\r
170                 self.toolbar.AddSeparator()\r
171                 group = []\r
172                 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)\r
173                 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick).SetValue(True)\r
174                 self.toolbar.AddSeparator()\r
175                 toolbarUtil.NormalButton(self.toolbar, self.OnPreferences, 'preferences.png', 'Project planner preferences')\r
176                 self.toolbar.AddSeparator()\r
177                 toolbarUtil.NormalButton(self.toolbar, self.OnCutMesh, 'cut-mesh.png', 'Cut a plate STL into multiple STL files, and add those files to the project.\nNote: Splitting up plates sometimes takes a few minutes.')\r
178                 toolbarUtil.NormalButton(self.toolbar, self.OnSaveCombinedSTL, 'save-combination.png', 'Save all the combined STL files into a single STL file as a plate.')\r
179                 self.toolbar.AddSeparator()\r
180                 group = []\r
181                 self.printOneAtATime = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Print one object at a time', callback=self.OnPrintTypeChange)\r
182                 self.printAllAtOnce = toolbarUtil.RadioButton(self.toolbar, group, 'all-at-once-on.png', 'all-at-once-off.png', 'Print all the objects at once', callback=self.OnPrintTypeChange)\r
183                 self.toolbar.AddSeparator()\r
184                 toolbarUtil.NormalButton(self.toolbar, self.OnQuit, 'exit.png', 'Close project planner')\r
185                 \r
186                 self.toolbar.Realize()\r
187 \r
188                 self.toolbar2 = toolbarUtil.Toolbar(self.panel)\r
189 \r
190                 toolbarUtil.NormalButton(self.toolbar2, self.OnAddModel, 'object-add.png', 'Add model')\r
191                 toolbarUtil.NormalButton(self.toolbar2, self.OnRemModel, 'object-remove.png', 'Remove model')\r
192                 self.toolbar2.AddSeparator()\r
193                 toolbarUtil.NormalButton(self.toolbar2, self.OnMoveUp, 'move-up.png', 'Move model up in print list')\r
194                 toolbarUtil.NormalButton(self.toolbar2, self.OnMoveDown, 'move-down.png', 'Move model down in print list')\r
195                 toolbarUtil.NormalButton(self.toolbar2, self.OnCopy, 'copy.png', 'Make a copy of the current selected object')\r
196                 toolbarUtil.NormalButton(self.toolbar2, self.OnSetCustomProfile, 'set-profile.png', 'Set a custom profile to be used to slice a specific object.')\r
197                 self.toolbar2.AddSeparator()\r
198                 toolbarUtil.NormalButton(self.toolbar2, self.OnAutoPlace, 'autoplace.png', 'Automaticly organize the objects on the platform.')\r
199                 toolbarUtil.NormalButton(self.toolbar2, self.OnSlice, 'slice.png', 'Slice to project into a gcode file.')\r
200                 self.toolbar2.Realize()\r
201 \r
202                 self.toolbar3 = toolbarUtil.Toolbar(self.panel)\r
203                 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar3, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.OnMirrorChange)\r
204                 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar3, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.OnMirrorChange)\r
205                 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar3, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.OnMirrorChange)\r
206                 self.toolbar3.AddSeparator()\r
207 \r
208                 # Swap\r
209                 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.OnMirrorChange)\r
210                 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.OnMirrorChange)\r
211                 self.toolbar3.Realize()\r
212                 \r
213                 sizer = wx.GridBagSizer(2,2)\r
214                 self.panel.SetSizer(sizer)\r
215                 self.preview = PreviewGLCanvas(self.panel, self)\r
216                 self.listbox = wx.ListBox(self.panel, -1, choices=[])\r
217                 self.addButton = wx.Button(self.panel, -1, "Add")\r
218                 self.remButton = wx.Button(self.panel, -1, "Remove")\r
219                 self.sliceButton = wx.Button(self.panel, -1, "Slice")\r
220                 self.autoPlaceButton = wx.Button(self.panel, -1, "Auto Place")\r
221                 \r
222                 sizer.Add(self.toolbar, (0,0), span=(1,1), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)\r
223                 sizer.Add(self.toolbar2, (0,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)\r
224                 sizer.Add(self.preview, (1,0), span=(5,1), flag=wx.EXPAND)\r
225                 sizer.Add(self.listbox, (1,1), span=(1,2), flag=wx.EXPAND)\r
226                 sizer.Add(self.toolbar3, (2,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)\r
227                 sizer.Add(self.addButton, (3,1), span=(1,1))\r
228                 sizer.Add(self.remButton, (3,2), span=(1,1))\r
229                 sizer.Add(self.sliceButton, (4,1), span=(1,1))\r
230                 sizer.Add(self.autoPlaceButton, (4,2), span=(1,1))\r
231                 sizer.AddGrowableCol(0)\r
232                 sizer.AddGrowableRow(1)\r
233                 \r
234                 self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)\r
235                 self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)\r
236                 self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)\r
237                 self.autoPlaceButton.Bind(wx.EVT_BUTTON, self.OnAutoPlace)\r
238                 self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)\r
239 \r
240                 panel = wx.Panel(self.panel, -1)\r
241                 sizer.Add(panel, (5,1), span=(1,2))\r
242                 \r
243                 sizer = wx.GridBagSizer(2,2)\r
244                 panel.SetSizer(sizer)\r
245                 \r
246                 self.scaleCtrl = wx.TextCtrl(panel, -1, '')\r
247                 self.rotateCtrl = wx.SpinCtrl(panel, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)\r
248                 self.rotateCtrl.SetRange(0, 360)\r
249 \r
250                 sizer.Add(wx.StaticText(panel, -1, 'Scale'), (0,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
251                 sizer.Add(self.scaleCtrl, (0,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
252                 sizer.Add(wx.StaticText(panel, -1, 'Rotate'), (1,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
253                 sizer.Add(self.rotateCtrl, (1,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
254 \r
255                 if int(profile.getPreference('extruder_amount')) > 1:\r
256                         self.extruderCtrl = wx.ComboBox(panel, -1, '1', choices=map(str, range(1, int(profile.getPreference('extruder_amount'))+1)), style=wx.CB_DROPDOWN|wx.CB_READONLY)\r
257                         sizer.Add(wx.StaticText(panel, -1, 'Extruder'), (2,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
258                         sizer.Add(self.extruderCtrl, (2,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
259                         self.extruderCtrl.Bind(wx.EVT_COMBOBOX, self.OnExtruderChange)\r
260 \r
261                 self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange)\r
262                 self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange)\r
263 \r
264                 self.SetSize((800,600))\r
265 \r
266         def OnClose(self, e):\r
267                 self.Destroy()\r
268 \r
269         def OnQuit(self, e):\r
270                 self.Close()\r
271         \r
272         def OnPreferences(self, e):\r
273                 prefDialog = preferencesDialog(self)\r
274                 prefDialog.Centre()\r
275                 prefDialog.Show(True)\r
276         \r
277         def OnCutMesh(self, e):\r
278                 dlg=wx.FileDialog(self, "Open file to cut", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)\r
279                 dlg.SetWildcard(meshLoader.wildcardFilter())\r
280                 if dlg.ShowModal() == wx.ID_OK:\r
281                         filename = dlg.GetPath()\r
282                         model = meshLoader.loadMesh(filename)\r
283                         pd = wx.ProgressDialog('Splitting model.', 'Splitting model into multiple parts.', model.vertexCount, self, wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_SMOOTH)\r
284                         parts = model.splitToParts(pd.Update)\r
285                         for part in parts:\r
286                                 partFilename = filename[:filename.rfind('.')] + "_part%d.stl" % (parts.index(part))\r
287                                 stl.saveAsSTL(part, partFilename)\r
288                                 item = ProjectObject(self, partFilename)\r
289                                 self.list.append(item)\r
290                                 self.selection = item\r
291                                 self._updateListbox()\r
292                                 self.OnListSelect(None)\r
293                         pd.Destroy()\r
294                 self.preview.Refresh()\r
295                 dlg.Destroy()\r
296         \r
297         def OnDropFiles(self, filenames):\r
298                 for filename in filenames:\r
299                         item = ProjectObject(self, filename)\r
300                         profile.putPreference('lastFile', item.filename)\r
301                         self.list.append(item)\r
302                         self.selection = item\r
303                         self._updateListbox()\r
304                 self.OnListSelect(None)\r
305                 self.preview.Refresh()\r
306 \r
307         def OnPrintTypeChange(self):\r
308                 self.printMode = 0\r
309                 if self.printAllAtOnce.GetValue():\r
310                         self.printMode = 1\r
311                 self.preview.Refresh()\r
312         \r
313         def OnSaveCombinedSTL(self, e):\r
314                 dlg=wx.FileDialog(self, "Save as STL", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
315                 dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")\r
316                 if dlg.ShowModal() == wx.ID_OK:\r
317                         self._saveCombinedSTL(dlg.GetPath())\r
318                 dlg.Destroy()\r
319         \r
320         def _saveCombinedSTL(self, filename):\r
321                 totalCount = 0\r
322                 for item in self.list:\r
323                         totalCount += item.mesh.vertexCount\r
324                 output = mesh.mesh()\r
325                 output._prepareVertexCount(totalCount)\r
326                 for item in self.list:\r
327                         offset = numpy.array([item.centerX, item.centerY, 0])\r
328                         for v in item.mesh.vertexes:\r
329                                 v0 = v * item.scale + offset\r
330                                 output.addVertex(v0[0], v0[1], v0[2])\r
331                 stl.saveAsSTL(output, filename)\r
332         \r
333         def OnSaveProject(self, e):\r
334                 dlg=wx.FileDialog(self, "Save project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
335                 dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")\r
336                 if dlg.ShowModal() == wx.ID_OK:\r
337                         cp = ConfigParser.ConfigParser()\r
338                         i = 0\r
339                         for item in self.list:\r
340                                 section = 'model_%d' % (i)\r
341                                 cp.add_section(section)\r
342                                 cp.set(section, 'filename', item.filename.encode("utf-8"))\r
343                                 cp.set(section, 'centerX', str(item.centerX))\r
344                                 cp.set(section, 'centerY', str(item.centerY))\r
345                                 cp.set(section, 'scale', str(item.scale))\r
346                                 cp.set(section, 'rotate', str(item.rotate))\r
347                                 cp.set(section, 'flipX', str(item.flipX))\r
348                                 cp.set(section, 'flipY', str(item.flipY))\r
349                                 cp.set(section, 'flipZ', str(item.flipZ))\r
350                                 cp.set(section, 'swapXZ', str(item.swapXZ))\r
351                                 cp.set(section, 'swapYZ', str(item.swapYZ))\r
352                                 cp.set(section, 'extruder', str(item.extruder+1))\r
353                                 if item.profile != None:\r
354                                         cp.set(section, 'profile', item.profile)\r
355                                 i += 1\r
356                         cp.write(open(dlg.GetPath(), "w"))\r
357                 dlg.Destroy()\r
358 \r
359         def OnLoadProject(self, e):\r
360                 dlg=wx.FileDialog(self, "Open project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)\r
361                 dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")\r
362                 if dlg.ShowModal() == wx.ID_OK:\r
363                         cp = ConfigParser.ConfigParser()\r
364                         cp.read(dlg.GetPath())\r
365                         self.list = []\r
366                         i = 0\r
367                         while cp.has_section('model_%d' % (i)):\r
368                                 section = 'model_%d' % (i)\r
369                                 \r
370                                 item = ProjectObject(self, unicode(cp.get(section, 'filename'), "utf-8"))\r
371                                 item.centerX = float(cp.get(section, 'centerX'))\r
372                                 item.centerY = float(cp.get(section, 'centerY'))\r
373                                 item.scale = float(cp.get(section, 'scale'))\r
374                                 item.rotate = float(cp.get(section, 'rotate'))\r
375                                 item.flipX = cp.get(section, 'flipX') == 'True'\r
376                                 item.flipY = cp.get(section, 'flipY') == 'True'\r
377                                 item.flipZ = cp.get(section, 'flipZ') == 'True'\r
378                                 item.swapXZ = cp.get(section, 'swapXZ') == 'True'\r
379                                 item.swapYZ = cp.get(section, 'swapYZ') == 'True'\r
380                                 if cp.has_option(section, 'extruder'):\r
381                                         item.extuder = int(cp.get(section, 'extruder')) - 1\r
382                                 if cp.has_option(section, 'profile'):\r
383                                         item.profile = cp.get(section, 'profile')\r
384                                 item.updateModelTransform()\r
385                                 i += 1\r
386                                 \r
387                                 self.list.append(item)\r
388 \r
389                         self.selected = self.list[0]\r
390                         self._updateListbox()                   \r
391                         self.OnListSelect(None)\r
392                         self.preview.Refresh()\r
393 \r
394                 dlg.Destroy()\r
395 \r
396         def On3DClick(self):\r
397                 self.preview.yaw = 30\r
398                 self.preview.pitch = 60\r
399                 self.preview.zoom = 300\r
400                 self.preview.view3D = True\r
401                 self.preview.Refresh()\r
402 \r
403         def OnTopClick(self):\r
404                 self.preview.view3D = False\r
405                 self.preview.zoom = self.machineSize[0] / 2 + 10\r
406                 self.preview.offsetX = 0\r
407                 self.preview.offsetY = 0\r
408                 self.preview.Refresh()\r
409 \r
410         def OnListSelect(self, e):\r
411                 if self.listbox.GetSelection() == -1:\r
412                         return\r
413                 self.selection = self.list[self.listbox.GetSelection()]\r
414                 self.scaleCtrl.SetValue(str(self.selection.scale))\r
415                 self.rotateCtrl.SetValue(int(self.selection.rotate))\r
416                 if int(profile.getPreference('extruder_amount')) > 1:\r
417 \r
418                         self.extruderCtrl.SetValue(str(self.selection.extruder+1))\r
419 \r
420                 self.mirrorX.SetValue(self.selection.flipX)\r
421                 self.mirrorY.SetValue(self.selection.flipY)\r
422                 self.mirrorZ.SetValue(self.selection.flipZ)\r
423                 self.swapXZ.SetValue(self.selection.swapXZ)\r
424                 self.swapYZ.SetValue(self.selection.swapYZ)\r
425                 \r
426                 self.preview.Refresh()\r
427         \r
428         def OnAddModel(self, e):\r
429                 dlg=wx.FileDialog(self, "Open file to print", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)\r
430                 dlg.SetWildcard(meshLoader.wildcardFilter())\r
431                 if dlg.ShowModal() == wx.ID_OK:\r
432                         for filename in dlg.GetPaths():\r
433                                 item = ProjectObject(self, filename)\r
434                                 profile.putPreference('lastFile', item.filename)\r
435                                 self.list.append(item)\r
436                                 self.selection = item\r
437                                 self._updateListbox()\r
438                                 self.OnListSelect(None)\r
439                 self.preview.Refresh()\r
440                 dlg.Destroy()\r
441         \r
442         def OnRemModel(self, e):\r
443                 if self.selection == None:\r
444                         return\r
445                 self.list.remove(self.selection)\r
446                 self._updateListbox()\r
447                 self.preview.Refresh()\r
448         \r
449         def OnMoveUp(self, e):\r
450                 if self.selection == None:\r
451                         return\r
452                 i = self.listbox.GetSelection()\r
453                 if i == 0:\r
454                         return\r
455                 self.list.remove(self.selection)\r
456                 self.list.insert(i-1, self.selection)\r
457                 self._updateListbox()\r
458                 self.preview.Refresh()\r
459 \r
460         def OnMoveDown(self, e):\r
461                 if self.selection == None:\r
462                         return\r
463                 i = self.listbox.GetSelection()\r
464                 if i == len(self.list) - 1:\r
465                         return\r
466                 self.list.remove(self.selection)\r
467                 self.list.insert(i+1, self.selection)\r
468                 self._updateListbox()\r
469                 self.preview.Refresh()\r
470         \r
471         def OnCopy(self, e):\r
472                 if self.selection == None:\r
473                         return\r
474                 \r
475                 item = self.selection.clone()\r
476                 self.list.insert(self.list.index(self.selection), item)\r
477                 self.selection = item\r
478                 \r
479                 self._updateListbox()\r
480                 self.preview.Refresh()\r
481         \r
482         def OnSetCustomProfile(self, e):\r
483                 if self.selection == None:\r
484                         return\r
485 \r
486                 dlg=wx.FileDialog(self, "Select profile", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)\r
487                 dlg.SetWildcard("Profile files (*.ini)|*.ini;*.INI")\r
488                 if dlg.ShowModal() == wx.ID_OK:\r
489                         self.selection.profile = dlg.GetPath()\r
490                 else:\r
491                         self.selection.profile = None\r
492                 self._updateListbox()\r
493                 dlg.Destroy()\r
494         \r
495         def _updateListbox(self):\r
496                 self.listbox.Clear()\r
497                 for item in self.list:\r
498                         if item.profile != None:\r
499                                 self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1] + " *")\r
500                         else:\r
501                                 self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1])\r
502                 if self.selection in self.list:\r
503                         self.listbox.SetSelection(self.list.index(self.selection))\r
504                 elif len(self.list) > 0:\r
505                         self.selection = self.list[0]\r
506                         self.listbox.SetSelection(0)\r
507                 else:\r
508                         self.selection = None\r
509                         self.listbox.SetSelection(-1)\r
510 \r
511         def OnAutoPlace(self, e):\r
512                 bestAllowedSize = int(self.machineSize[1])\r
513                 bestArea = self._doAutoPlace(bestAllowedSize)\r
514                 for i in xrange(10, int(self.machineSize[1]), 10):\r
515                         area = self._doAutoPlace(i)\r
516                         if area < bestArea:\r
517                                 bestAllowedSize = i\r
518                                 bestArea = area\r
519                 self._doAutoPlace(bestAllowedSize)\r
520                 for item in self.list:\r
521                         item.clampXY()\r
522                 self.preview.Refresh()\r
523         \r
524         def _doAutoPlace(self, allowedSizeY):\r
525                 extraSizeMin, extraSizeMax = self.getExtraHeadSize()\r
526 \r
527                 if extraSizeMin[0] > extraSizeMax[0]:\r
528                         posX = self.machineSize[0]\r
529                         dirX = -1\r
530                 else:\r
531                         posX = 0\r
532                         dirX = 1\r
533                 posY = 0\r
534                 dirY = 1\r
535                 \r
536                 minX = self.machineSize[0]\r
537                 minY = self.machineSize[1]\r
538                 maxX = 0\r
539                 maxY = 0\r
540                 for item in self.list:\r
541                         item.centerX = posX + item.getMaximum()[0] * item.scale * dirX\r
542                         item.centerY = posY + item.getMaximum()[1] * item.scale * dirY\r
543                         if item.centerY + item.getSize()[1] >= allowedSizeY:\r
544                                 if dirX < 0:\r
545                                         posX = minX - extraSizeMax[0] - 1\r
546                                 else:\r
547                                         posX = maxX + extraSizeMin[0] + 1\r
548                                 posY = 0\r
549                                 item.centerX = posX + item.getMaximum()[0] * item.scale * dirX\r
550                                 item.centerY = posY + item.getMaximum()[1] * item.scale * dirY\r
551                         posY += item.getSize()[1]  * item.scale * dirY + extraSizeMin[1] + 1\r
552                         minX = min(minX, item.centerX - item.getSize()[0] * item.scale / 2)\r
553                         minY = min(minY, item.centerY - item.getSize()[1] * item.scale / 2)\r
554                         maxX = max(maxX, item.centerX + item.getSize()[0] * item.scale / 2)\r
555                         maxY = max(maxY, item.centerY + item.getSize()[1] * item.scale / 2)\r
556                 \r
557                 for item in self.list:\r
558                         if dirX < 0:\r
559                                 item.centerX -= minX / 2\r
560                         else:\r
561                                 item.centerX += (self.machineSize[0] - maxX) / 2\r
562                         item.centerY += (self.machineSize[1] - maxY) / 2\r
563                 \r
564                 if minX < 0 or maxX > self.machineSize[0]:\r
565                         return ((maxX - minX) + (maxY - minY)) * 100\r
566                 \r
567                 return (maxX - minX) + (maxY - minY)\r
568 \r
569         def OnSlice(self, e):\r
570                 dlg=wx.FileDialog(self, "Save project gcode file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
571                 dlg.SetWildcard("GCode file (*.gcode)|*.gcode")\r
572                 if dlg.ShowModal() != wx.ID_OK:\r
573                         dlg.Destroy()\r
574                         return\r
575                 resultFilename = dlg.GetPath()\r
576                 dlg.Destroy()\r
577 \r
578                 put = profile.setTempOverride\r
579                 oldProfile = profile.getGlobalProfileString()\r
580                 \r
581                 put('add_start_end_gcode', 'False')\r
582                 put('gcode_extension', 'project_tmp')\r
583                 if self.printMode == 0:\r
584                         clearZ = 0\r
585                         actionList = []\r
586                         for item in self.list:\r
587                                 if item.profile != None and os.path.isfile(item.profile):\r
588                                         profile.loadGlobalProfile(item.profile)\r
589                                 put('machine_center_x', item.centerX - self.extruderOffset[item.extruder][0])\r
590                                 put('machine_center_y', item.centerY - self.extruderOffset[item.extruder][1])\r
591                                 put('model_scale', item.scale)\r
592                                 put('flip_x', item.flipX)\r
593                                 put('flip_y', item.flipY)\r
594                                 put('flip_z', item.flipZ)\r
595                                 put('model_rotate_base', item.rotate)\r
596                                 put('swap_xz', item.swapXZ)\r
597                                 put('swap_yz', item.swapYZ)\r
598                                 \r
599                                 action = Action()\r
600                                 action.sliceCmd = sliceRun.getSliceCommand(item.filename)\r
601                                 action.centerX = item.centerX\r
602                                 action.centerY = item.centerY\r
603                                 action.temperature = profile.getProfileSettingFloat('print_temperature')\r
604                                 action.extruder = item.extruder\r
605                                 action.filename = item.filename\r
606                                 clearZ = max(clearZ, item.getSize()[2] * item.scale + 5.0)\r
607                                 action.clearZ = clearZ\r
608                                 action.leaveResultForNextSlice = False\r
609                                 action.usePreviousSlice = False\r
610                                 actionList.append(action)\r
611 \r
612                                 if self.list.index(item) > 0 and item.isSameExceptForPosition(self.list[self.list.index(item)-1]):\r
613                                         actionList[-2].leaveResultForNextSlice = True\r
614                                         actionList[-1].usePreviousSlice = True\r
615 \r
616                                 if item.profile != None:\r
617                                         profile.loadGlobalProfileFromString(oldProfile)\r
618                         \r
619                 else:\r
620                         self._saveCombinedSTL(resultFilename + "_temp_.stl")\r
621                         put('model_scale', 1.0)\r
622                         put('flip_x', False)\r
623                         put('flip_y', False)\r
624                         put('flip_z', False)\r
625                         put('model_rotate_base', 0)\r
626                         put('swap_xz', False)\r
627                         put('swap_yz', False)\r
628                         actionList = []\r
629                         \r
630                         action = Action()\r
631                         action.sliceCmd = sliceRun.getSliceCommand(resultFilename + "_temp_.stl")\r
632                         action.centerX = profile.getProfileSettingFloat('machine_center_x')\r
633                         action.centerY = profile.getProfileSettingFloat('machine_center_y')\r
634                         action.temperature = profile.getProfileSettingFloat('print_temperature')\r
635                         action.extruder = 0\r
636                         action.filename = resultFilename + "_temp_.stl"\r
637                         action.clearZ = 0\r
638                         action.leaveResultForNextSlice = False\r
639                         action.usePreviousSlice = False\r
640 \r
641                         actionList.append(action)\r
642                         \r
643                 #Restore the old profile.\r
644                 profile.resetTempOverride()\r
645                 \r
646                 pspw = ProjectSliceProgressWindow(actionList, resultFilename)\r
647                 pspw.extruderOffset = self.extruderOffset\r
648                 pspw.Centre()\r
649                 pspw.Show(True)\r
650         \r
651         def OnScaleChange(self, e):\r
652                 if self.selection == None:\r
653                         return\r
654                 try:\r
655                         self.selection.scale = float(self.scaleCtrl.GetValue())\r
656                 except ValueError:\r
657                         self.selection.scale = 1.0\r
658                 self.preview.Refresh()\r
659         \r
660         def OnRotateChange(self, e):\r
661                 if self.selection == None:\r
662                         return\r
663                 self.selection.rotate = float(self.rotateCtrl.GetValue())\r
664                 self.selection.updateModelTransform()\r
665                 self.preview.Refresh()\r
666 \r
667         def OnExtruderChange(self, e):\r
668                 if self.selection == None:\r
669                         return\r
670                 self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1\r
671                 self.preview.Refresh()\r
672                 \r
673         def OnMirrorChange(self):\r
674                 if self.selection == None:\r
675                         return\r
676                 self.selection.flipX = self.mirrorX.GetValue()\r
677                 self.selection.flipY = self.mirrorY.GetValue()\r
678                 self.selection.flipZ = self.mirrorZ.GetValue()\r
679                 self.selection.swapXZ = self.swapXZ.GetValue()\r
680                 self.selection.swapYZ = self.swapYZ.GetValue()\r
681                 self.selection.updateModelTransform()\r
682                 self.preview.Refresh()\r
683 \r
684         def getExtraHeadSize(self):\r
685                 extraSizeMin = self.headSizeMin\r
686                 extraSizeMax = self.headSizeMax\r
687                 if profile.getProfileSettingFloat('skirt_line_count') > 0:\r
688                         skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')\r
689                         extraSizeMin = extraSizeMin + numpy.array([skirtSize, skirtSize, 0])\r
690                         extraSizeMax = extraSizeMax + numpy.array([skirtSize, skirtSize, 0])\r
691                 if profile.getProfileSetting('enable_raft') != 'False':\r
692                         raftSize = profile.getProfileSettingFloat('raft_margin') * 2\r
693                         extraSizeMin = extraSizeMin + numpy.array([raftSize, raftSize, 0])\r
694                         extraSizeMax = extraSizeMax + numpy.array([raftSize, raftSize, 0])\r
695                 if profile.getProfileSetting('support') != 'None':\r
696                         extraSizeMin = extraSizeMin + numpy.array([3.0, 0, 0])\r
697                         extraSizeMax = extraSizeMax + numpy.array([3.0, 0, 0])\r
698 \r
699                 if self.printMode == 1:\r
700                         extraSizeMin = numpy.array([6.0, 6.0, 0])\r
701                         extraSizeMax = numpy.array([6.0, 6.0, 0])\r
702                 \r
703                 return extraSizeMin, extraSizeMax\r
704 \r
705 class PreviewGLCanvas(glcanvas.GLCanvas):\r
706         def __init__(self, parent, projectPlannerWindow):\r
707                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
708                 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
709                 self.parent = projectPlannerWindow\r
710                 self.context = glcanvas.GLContext(self)\r
711                 wx.EVT_PAINT(self, self.OnPaint)\r
712                 wx.EVT_SIZE(self, self.OnSize)\r
713                 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
714                 wx.EVT_LEFT_DOWN(self, self.OnMouseLeftDown)\r
715                 wx.EVT_MOTION(self, self.OnMouseMotion)\r
716                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
717                 self.yaw = 30\r
718                 self.pitch = 60\r
719                 self.zoom = self.parent.machineSize[0] / 2 + 10\r
720                 self.offsetX = 0\r
721                 self.offsetY = 0\r
722                 self.view3D = False\r
723                 self.allowDrag = False\r
724         \r
725         def OnMouseLeftDown(self,e):\r
726                 self.allowDrag = True\r
727         \r
728         def OnMouseMotion(self,e):\r
729                 if self.allowDrag and e.Dragging() and e.LeftIsDown():\r
730                         if self.view3D:\r
731                                 self.yaw += e.GetX() - self.oldX\r
732                                 self.pitch -= e.GetY() - self.oldY\r
733                                 if self.pitch > 170:\r
734                                         self.pitch = 170\r
735                                 if self.pitch < 10:\r
736                                         self.pitch = 10\r
737                         else:\r
738                                 item = self.parent.selection\r
739                                 if item != None:\r
740                                         item.centerX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
741                                         item.centerY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
742                                         item.clampXY()\r
743                         self.Refresh()\r
744                 else:\r
745                         self.allowDrag = False\r
746                 if e.Dragging() and e.RightIsDown():\r
747                         if self.view3D:\r
748                                 self.zoom += e.GetY() - self.oldY\r
749                                 if self.zoom < 1:\r
750                                         self.zoom = 1\r
751                         self.Refresh()\r
752                 self.oldX = e.GetX()\r
753                 self.oldY = e.GetY()\r
754         \r
755         def OnMouseWheel(self,e):\r
756                 if self.view3D:\r
757                         self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
758                         if self.zoom < 1.0:\r
759                                 self.zoom = 1.0\r
760                         self.Refresh()\r
761         \r
762         def OnEraseBackground(self,event):\r
763                 #Workaround for windows background redraw flicker.\r
764                 pass\r
765         \r
766         def OnSize(self,event):\r
767                 self.Refresh()\r
768 \r
769         def OnPaint(self,event):\r
770                 dc = wx.PaintDC(self)\r
771                 if not hasOpenGLlibs:\r
772                         dc.Clear()\r
773                         dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
774                         return\r
775                 self.SetCurrent(self.context)\r
776                 opengl.InitGL(self, self.view3D, self.zoom)\r
777                 if self.view3D:\r
778                         glTranslate(0,0,-self.zoom)\r
779                         glRotate(-self.pitch, 1,0,0)\r
780                         glRotate(self.yaw, 0,0,1)\r
781                 else:\r
782                         glScale(1.0/self.zoom, 1.0/self.zoom, 1.0)\r
783                         glTranslate(self.offsetX, self.offsetY, 0.0)\r
784                 glTranslate(-self.parent.machineSize[0]/2, -self.parent.machineSize[1]/2, 0)\r
785 \r
786                 self.OnDraw()\r
787                 self.SwapBuffers()\r
788 \r
789         def OnDraw(self):\r
790                 machineSize = self.parent.machineSize\r
791                 extraSizeMin, extraSizeMax = self.parent.getExtraHeadSize()\r
792 \r
793                 for item in self.parent.list:\r
794                         item.validPlacement = True\r
795                         item.gotHit = False\r
796                 \r
797                 for idx1 in xrange(0, len(self.parent.list)):\r
798                         item = self.parent.list[idx1]\r
799                         iMin1 = (item.getMinimum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - extraSizeMin - self.parent.extruderOffset[item.extruder]\r
800                         iMax1 = (item.getMaximum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) + extraSizeMax - self.parent.extruderOffset[item.extruder]\r
801                         for idx2 in xrange(0, idx1):\r
802                                 item2 = self.parent.list[idx2]\r
803                                 iMin2 = (item2.getMinimum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])\r
804                                 iMax2 = (item2.getMaximum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])\r
805                                 if item != item2 and iMax1[0] >= iMin2[0] and iMin1[0] <= iMax2[0] and iMax1[1] >= iMin2[1] and iMin1[1] <= iMax2[1]:\r
806                                         item.validPlacement = False\r
807                                         item2.gotHit = True\r
808                 \r
809                 seenSelected = False\r
810                 for item in self.parent.list:\r
811                         if item == self.parent.selection:\r
812                                 seenSelected = True\r
813                         if item.modelDisplayList == None:\r
814                                 item.modelDisplayList = glGenLists(1);\r
815                         if item.modelDirty:\r
816                                 item.modelDirty = False\r
817                                 modelSize = item.getMaximum() - item.getMinimum()\r
818                                 glNewList(item.modelDisplayList, GL_COMPILE)\r
819                                 opengl.DrawMesh(item.mesh)\r
820                                 glEndList()\r
821                         \r
822                         if item.validPlacement:\r
823                                 if self.parent.selection == item:\r
824                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.9, 0.7, 1.0])\r
825                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.3, 0.2, 0.0])\r
826                                 else:\r
827                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.8, 0.6, 1.0])\r
828                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.1, 0.1, 0.0])\r
829                         else:\r
830                                 if self.parent.selection == item:\r
831                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])\r
832                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])\r
833                                 else:\r
834                                         glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])\r
835                                         glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])\r
836                         glPushMatrix()\r
837                         \r
838                         glEnable(GL_LIGHTING)\r
839                         glTranslate(item.centerX, item.centerY, 0)\r
840                         glPushMatrix()\r
841                         glScalef(item.scale, item.scale, item.scale)\r
842                         glCallList(item.modelDisplayList)\r
843                         glPopMatrix()\r
844                         \r
845                         vMin = item.getMinimum() * item.scale\r
846                         vMax = item.getMaximum() * item.scale\r
847                         vMinHead = vMin - extraSizeMin - self.parent.extruderOffset[item.extruder]\r
848                         vMaxHead = vMax + extraSizeMax - self.parent.extruderOffset[item.extruder]\r
849 \r
850                         glDisable(GL_LIGHTING)\r
851 \r
852                         if self.parent.selection == item:\r
853                                 if item.gotHit:\r
854                                         glColor3f(1.0,0.0,0.3)\r
855                                 else:\r
856                                         glColor3f(1.0,0.0,1.0)\r
857                                 opengl.DrawBox(vMin, vMax)\r
858                                 if item.gotHit:\r
859                                         glColor3f(1.0,0.3,0.0)\r
860                                 else:\r
861                                         glColor3f(1.0,1.0,0.0)\r
862                                 opengl.DrawBox(vMinHead, vMaxHead)\r
863                         elif seenSelected:\r
864                                 if item.gotHit:\r
865                                         glColor3f(0.5,0.0,0.1)\r
866                                 else:\r
867                                         glColor3f(0.5,0.0,0.5)\r
868                                 opengl.DrawBox(vMinHead, vMaxHead)\r
869                         else:\r
870                                 if item.gotHit:\r
871                                         glColor3f(0.7,0.1,0.0)\r
872                                 else:\r
873                                         glColor3f(0.7,0.7,0.0)\r
874                                 opengl.DrawBox(vMin, vMax)\r
875                         \r
876                         glPopMatrix()\r
877                 \r
878                 opengl.DrawMachine(util3d.Vector3(machineSize[0], machineSize[1], machineSize[2]))\r
879                 glFlush()\r
880 \r
881 class ProjectSliceProgressWindow(wx.Frame):\r
882         def __init__(self, actionList, resultFilename):\r
883                 super(ProjectSliceProgressWindow, self).__init__(None, title='Cura')\r
884                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))\r
885                 \r
886                 self.actionList = actionList\r
887                 self.resultFilename = resultFilename\r
888                 self.abort = False\r
889                 self.prevStep = 'start'\r
890                 self.totalDoneFactor = 0.0\r
891                 self.startTime = time.time()\r
892                 self.sliceStartTime = time.time()\r
893                 \r
894                 self.sizer = wx.GridBagSizer(2, 2) \r
895                 self.statusText = wx.StaticText(self, -1, "Building: %s" % (resultFilename))\r
896                 self.progressGauge = wx.Gauge(self, -1)\r
897                 self.progressGauge.SetRange(10000)\r
898                 self.progressGauge2 = wx.Gauge(self, -1)\r
899                 self.progressGauge2.SetRange(len(self.actionList))\r
900                 self.abortButton = wx.Button(self, -1, "Abort")\r
901                 self.sizer.Add(self.statusText, (0,0), span=(1,5))\r
902                 self.sizer.Add(self.progressGauge, (1, 0), span=(1,5), flag=wx.EXPAND)\r
903                 self.sizer.Add(self.progressGauge2, (2, 0), span=(1,5), flag=wx.EXPAND)\r
904 \r
905                 self.sizer.Add(self.abortButton, (3,0), span=(1,5), flag=wx.ALIGN_CENTER)\r
906                 self.sizer.AddGrowableCol(0)\r
907                 self.sizer.AddGrowableRow(0)\r
908 \r
909                 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)\r
910                 self.SetSizer(self.sizer)\r
911                 self.Layout()\r
912                 self.Fit()\r
913                 \r
914                 threading.Thread(target=self.OnRun).start()\r
915 \r
916         def OnAbort(self, e):\r
917                 if self.abort:\r
918                         self.Close()\r
919                 else:\r
920                         self.abort = True\r
921                         self.abortButton.SetLabel('Close')\r
922 \r
923         def SetProgress(self, stepName, layer, maxLayer):\r
924                 if self.prevStep != stepName:\r
925                         self.totalDoneFactor += sliceRun.sliceStepTimeFactor[self.prevStep]\r
926                         newTime = time.time()\r
927                         #print "#####" + str(newTime-self.startTime) + " " + self.prevStep + " -> " + stepName\r
928                         self.startTime = newTime\r
929                         self.prevStep = stepName\r
930                 \r
931                 progresValue = ((self.totalDoneFactor + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000\r
932                 self.progressGauge.SetValue(int(progresValue))\r
933                 self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")\r
934         \r
935         def OnRun(self):\r
936                 resultFile = open(self.resultFilename, "w")\r
937                 put = profile.setTempOverride\r
938                 self.progressLog = []\r
939                 for action in self.actionList:\r
940                         wx.CallAfter(self.SetTitle, "Building: [%d/%d]"  % (self.actionList.index(action) + 1, len(self.actionList)))\r
941                         if not action.usePreviousSlice:\r
942                                 p = sliceRun.startSliceCommandProcess(action.sliceCmd)\r
943                                 line = p.stdout.readline()\r
944                 \r
945                                 maxValue = 1\r
946                                 while(len(line) > 0):\r
947                                         line = line.rstrip()\r
948                                         if line[0:9] == "Progress[" and line[-1:] == "]":\r
949                                                 progress = line[9:-1].split(":")\r
950                                                 if len(progress) > 2:\r
951                                                         maxValue = int(progress[2])\r
952                                                 wx.CallAfter(self.SetProgress, progress[0], int(progress[1]), maxValue)\r
953                                         else:\r
954                                                 print line\r
955                                                 self.progressLog.append(line)\r
956                                                 wx.CallAfter(self.statusText.SetLabel, line)\r
957                                         if self.abort:\r
958                                                 p.terminate()\r
959                                                 wx.CallAfter(self.statusText.SetLabel, "Aborted by user.")\r
960                                                 resultFile.close()\r
961                                                 return\r
962                                         line = p.stdout.readline()\r
963                                 self.returnCode = p.wait()\r
964                         \r
965                         put('machine_center_x', action.centerX - self.extruderOffset[action.extruder][0])\r
966                         put('machine_center_y', action.centerY - self.extruderOffset[action.extruder][1])\r
967                         put('clear_z', action.clearZ)\r
968                         put('extruder', action.extruder)\r
969                         put('print_temperature', action.temperature)\r
970                         \r
971                         if action == self.actionList[0]:\r
972                                 resultFile.write(';TYPE:CUSTOM\n')\r
973                                 resultFile.write('T%d\n' % (action.extruder))\r
974                                 currentExtruder = action.extruder\r
975                                 prevTemp = action.temperature\r
976                                 resultFile.write(profile.getAlterationFileContents('start.gcode'))\r
977                         else:\r
978                                 #reset the extrusion length, and move to the next object center.\r
979                                 resultFile.write(';TYPE:CUSTOM\n')\r
980                                 if prevTemp != action.temperature and action.temperature > 0:\r
981                                         resultFile.write('M104 S%d\n' % (int(action.temperature)))\r
982                                         prevTemp = action.temperature\r
983                                 resultFile.write(profile.getAlterationFileContents('nextobject.gcode'))\r
984                         resultFile.write(';PRINTNR:%d\n' % self.actionList.index(action))\r
985                         profile.resetTempOverride()\r
986                         \r
987                         if not action.usePreviousSlice:\r
988                                 f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")\r
989                                 data = f.read(4096)\r
990                                 while data != '':\r
991                                         resultFile.write(data)\r
992                                         data = f.read(4096)\r
993                                 f.close()\r
994                                 savedCenterX = action.centerX\r
995                                 savedCenterY = action.centerY\r
996                         else:\r
997                                 f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")\r
998                                 for line in f:\r
999                                         if line[0] != ';':\r
1000                                                 if 'X' in line:\r
1001                                                         line = self._adjustNumberInLine(line, 'X', action.centerX - savedCenterX)\r
1002                                                 if 'Y' in line:\r
1003                                                         line = self._adjustNumberInLine(line, 'Y', action.centerY - savedCenterY)\r
1004                                         resultFile.write(line)\r
1005                                 f.close()\r
1006 \r
1007                         if not action.leaveResultForNextSlice:\r
1008                                 os.remove(sliceRun.getExportFilename(action.filename, "project_tmp"))\r
1009                         \r
1010                         wx.CallAfter(self.progressGauge.SetValue, 10000)\r
1011                         self.totalDoneFactor = 0.0\r
1012                         wx.CallAfter(self.progressGauge2.SetValue, self.actionList.index(action) + 1)\r
1013                 \r
1014                 resultFile.write(';TYPE:CUSTOM\n')\r
1015                 if len(self.actionList) > 1 and self.actionList[-1].clearZ > 1:\r
1016                         #only move to higher Z if we have sliced more then 1 object. This solves the "move into print after printing" problem with the print-all-at-once option.\r
1017                         resultFile.write('G1 Z%f F%f\n' % (self.actionList[-1].clearZ, profile.getProfileSettingFloat('max_z_speed') * 60))\r
1018                 resultFile.write(profile.getAlterationFileContents('end.gcode'))\r
1019                 resultFile.close()\r
1020                 \r
1021                 gcode = gcodeInterpreter.gcode()\r
1022                 gcode.load(self.resultFilename)\r
1023                 \r
1024                 self.abort = True\r
1025                 sliceTime = time.time() - self.sliceStartTime\r
1026                 status = "Build: %s" % (self.resultFilename)\r
1027                 status += "\nSlicing took: %02d:%02d" % (sliceTime / 60, sliceTime % 60)\r
1028                 status += "\nFilament: %.2fm %.2fg" % (gcode.extrusionAmount / 1000, gcode.calculateWeight() * 1000)\r
1029                 status += "\nPrint time: %02d:%02d" % (int(gcode.totalMoveTimeMinute / 60), int(gcode.totalMoveTimeMinute % 60))\r
1030                 cost = gcode.calculateCost()\r
1031                 if cost != False:\r
1032                         status += "\nCost: %s" % (cost)\r
1033                 profile.replaceGCodeTags(self.resultFilename, gcode)\r
1034                 wx.CallAfter(self.statusText.SetLabel, status)\r
1035                 wx.CallAfter(self.OnSliceDone)\r
1036         \r
1037         def _adjustNumberInLine(self, line, tag, f):\r
1038                 m = re.search('^(.*'+tag+')([0-9\.]*)(.*)$', line)\r
1039                 return m.group(1) + str(float(m.group(2)) + f) + m.group(3) + '\n'\r
1040         \r
1041         def OnSliceDone(self):\r
1042                 self.abortButton.Destroy()\r
1043                 self.closeButton = wx.Button(self, -1, "Close")\r
1044                 self.printButton = wx.Button(self, -1, "Print")\r
1045                 self.logButton = wx.Button(self, -1, "Show log")\r
1046                 self.sizer.Add(self.closeButton, (3,0), span=(1,1))\r
1047                 self.sizer.Add(self.printButton, (3,1), span=(1,1))\r
1048                 self.sizer.Add(self.logButton, (3,2), span=(1,1))\r
1049                 if exporer.hasExporer():\r
1050                         self.openFileLocationButton = wx.Button(self, -1, "Open file location")\r
1051                         self.Bind(wx.EVT_BUTTON, self.OnOpenFileLocation, self.openFileLocationButton)\r
1052                         self.sizer.Add(self.openFileLocationButton, (3,3), span=(1,1))\r
1053                 if profile.getPreference('sdpath') != '':\r
1054                         self.copyToSDButton = wx.Button(self, -1, "To SDCard")\r
1055                         self.Bind(wx.EVT_BUTTON, self.OnCopyToSD, self.copyToSDButton)\r
1056                         self.sizer.Add(self.copyToSDButton, (3,4), span=(1,1))\r
1057                 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.closeButton)\r
1058                 self.Bind(wx.EVT_BUTTON, self.OnPrint, self.printButton)\r
1059                 self.Bind(wx.EVT_BUTTON, self.OnShowLog, self.logButton)\r
1060                 self.Layout()\r
1061                 self.Fit()\r
1062 \r
1063         def OnCopyToSD(self, e):\r
1064                 filename = os.path.basename(self.resultFilename)\r
1065                 if profile.getPreference('sdshortnames') == 'True':\r
1066                         filename = sliceRun.getShortFilename(filename)\r
1067                 shutil.copy(self.resultFilename, os.path.join(profile.getPreference('sdpath'), filename))\r
1068         \r
1069         def OnOpenFileLocation(self, e):\r
1070                 exporer.openExporer(self.resultFilename)\r
1071         \r
1072         def OnPrint(self, e):\r
1073                 printWindow.printFile(self.resultFilename)\r
1074 \r
1075         def OnShowLog(self, e):\r
1076                 LogWindow('\n'.join(self.progressLog))\r
1077 \r
1078 class preferencesDialog(configBase.configWindowBase):\r
1079         def __init__(self, parent):\r
1080                 super(preferencesDialog, self).__init__(title="Project Planner Preferences")\r
1081                 \r
1082                 self.parent = parent\r
1083                 wx.EVT_CLOSE(self, self.OnClose)\r
1084                 \r
1085                 extruderAmount = int(profile.getPreference('extruder_amount'))\r
1086                 \r
1087                 left, right, main = self.CreateConfigPanel(self)\r
1088                 configBase.TitleRow(left, 'Machine head size')\r
1089                 c = configBase.SettingRow(left, 'Head size - X towards home (mm)', 'extruder_head_size_min_x', '0', 'Size of your printer head in the X direction, on the Ultimaker your fan is in this direction.', type = 'preference')\r
1090                 validators.validFloat(c, 0.1)\r
1091                 c = configBase.SettingRow(left, 'Head size - X towards end (mm)', 'extruder_head_size_max_x', '0', 'Size of your printer head in the X direction.', type = 'preference')\r
1092                 validators.validFloat(c, 0.1)\r
1093                 c = configBase.SettingRow(left, 'Head size - Y towards home (mm)', 'extruder_head_size_min_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')\r
1094                 validators.validFloat(c, 0.1)\r
1095                 c = configBase.SettingRow(left, 'Head size - Y towards end (mm)', 'extruder_head_size_max_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')\r
1096                 validators.validFloat(c, 0.1)\r
1097                 c = configBase.SettingRow(left, 'Head gantry height (mm)', 'extruder_head_size_height', '0', 'The tallest object height that will always fit under your printers gantry system when the printer head is at the lowest Z position.', type = 'preference')\r
1098                 validators.validFloat(c)\r
1099                 \r
1100                 self.okButton = wx.Button(left, -1, 'Ok')\r
1101                 left.GetSizer().Add(self.okButton, (left.GetSizer().GetRows(), 1))\r
1102                 self.okButton.Bind(wx.EVT_BUTTON, self.OnClose)\r
1103                 \r
1104                 self.MakeModal(True)\r
1105                 main.Fit()\r
1106                 self.Fit()\r
1107 \r
1108         def OnClose(self, e):\r
1109                 self.parent.headSizeMin = numpy.array([profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0])\r
1110                 self.parent.headSizeMax = numpy.array([profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0])\r
1111                 self.parent.Refresh()\r
1112 \r
1113                 self.MakeModal(False)\r
1114                 self.Destroy()\r
1115 \r
1116 class LogWindow(wx.Frame):\r
1117         def __init__(self, logText):\r
1118                 super(LogWindow, self).__init__(None, title="Slice log")\r
1119                 self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_READONLY)\r
1120                 self.SetSize((400,300))\r
1121                 self.Centre()\r
1122                 self.Show(True)\r
1123 \r
1124 def main():\r
1125         app = wx.App(False)\r
1126         projectPlanner().Show(True)\r
1127         app.MainLoop()\r
1128 \r
1129 if __name__ == '__main__':\r
1130         main()\r