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