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