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