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