1 from __future__ import absolute_import
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
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
39 class ProjectObject(object):
40 def __init__(self, parent, filename):
41 super(ProjectObject, self).__init__()
43 self.mesh = meshLoader.loadMesh(filename)
46 self.filename = filename
47 self.matrix = numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)
50 self.modelDisplayList = None
51 self.modelDirty = True
53 self.centerX = self.getSize()[0]/2 + 5
54 self.centerY = self.getSize()[1]/2 + 5
58 def isSameExceptForPosition(self, other):
59 if self.filename != other.filename:
61 if self.matrix != other.matrix:
63 if self.profile != other.profile:
67 def updateMatrix(self):
68 self.mesh.matrix = self.matrix
69 self.mesh.processMatrix()
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))
82 return self.mesh.getMinimum()
84 return self.mesh.getMaximum()
86 return self.mesh.getSize()
87 def getBoundaryCircle(self):
88 return self.mesh.boundaryCircleSize
91 p = ProjectObject(self.parent, self.filename)
93 p.centerX = self.centerX + 5
94 p.centerY = self.centerY + 5
96 p.filename = self.filename
97 p.matrix = self.matrix.copy()
98 p.profile = self.profile
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
115 class projectPlanner(wx.Frame):
116 "Main user interface window"
118 super(projectPlanner, self).__init__(None, title='Cura - Project Planner')
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)
125 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
128 self.selection = None
130 self.alwaysAutoPlace = profile.getPreference('planner_always_autoplace') == 'True'
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])
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])]
142 self.toolbar = toolbarUtil.Toolbar(self.panel)
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()
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()
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')
162 self.toolbar.Realize()
164 self.toolbar2 = toolbarUtil.Toolbar(self.panel)
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()
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")
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)
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)
208 panel = wx.Panel(self.panel, -1)
209 sizer.Add(panel, (5,1), span=(1,2))
211 sizer = wx.GridBagSizer(2,2)
212 panel.SetSizer(sizer)
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)
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)
222 self.resetScaleButton = openglGui.glButton(self.glCanvas, 13, 'Reset', (1,-2), self.OnScaleReset)
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))
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)
245 self.SetSize((800,600))
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)
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()
267 def OnRotateReset(self):
268 if self.selection is None:
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()
277 if self.selection is None:
279 transformedVertexes = (numpy.matrix(self.selection.mesh.vertexes, copy = False) * self.selection.matrix).getA()
280 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
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])
288 dot = (diff[2] / len)
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)
300 transformedVertexes = (numpy.matrix(self.selection.mesh.vertexes, copy = False) * self.selection.matrix).getA()
301 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
304 for v in transformedVertexes:
305 diff = v - minZvertex
306 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
309 dot = (diff[2] / len)
316 rad = math.asin(dotMin)
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)
321 self.selection.updateMatrix()
323 def OnScaleReset(self):
324 if self.selection is None:
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()
332 def OnMirror(self, axis):
333 if self.selection is None:
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)
339 def OnScaleEntry(self, value, axis):
340 if self.selection is None:
346 scale = numpy.linalg.norm(self.selection.matrix[::,axis].getA().flatten())
347 scale = value / scale
350 if self.scaleUniform.getValue():
351 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
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()
358 def OnScaleEntryMM(self, value, axis):
363 scale = self.selection.getSize()[axis]
364 scale = value / scale
367 if self.scaleUniform.getValue():
368 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
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()
375 def OnClose(self, e):
381 def OnPreferences(self, e):
382 prefDialog = preferencesDialog(self)
384 prefDialog.Show(True)
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)
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)
403 self.glCanvas.Refresh()
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()
416 def OnPrintTypeChange(self):
418 if self.printAllAtOnce.GetValue():
420 if self.alwaysAutoPlace:
421 self.OnAutoPlace(None)
422 self.glCanvas.Refresh()
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())
431 def _saveCombinedSTL(self, filename):
433 for item in self.list:
434 totalCount += item.mesh.vertexCount
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
444 output.addVertex(v[0], v[1], v[2])
445 stl.saveAsSTL(output, filename)
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()
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)
463 cp.write(open(dlg.GetPath(), "w"))
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())
474 while cp.has_section('model_%d' % (i)):
475 section = 'model_%d' % (i)
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')
488 self.list.append(item)
490 self.selected = self.list[0]
491 self._updateListbox()
492 self.OnListSelect(None)
493 self.glCanvas.Refresh()
498 self.glCanvas.yaw = 30
499 self.glCanvas.pitch = 60
500 self.glCanvas.zoom = 300
501 self.glCanvas.view3D = True
502 self.glCanvas.Refresh()
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()
511 def OnListSelect(self, e):
512 if self.listbox.GetSelection() == -1:
514 self.selection = self.list[self.listbox.GetSelection()]
515 self.glCanvas.Refresh()
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()
531 def OnRemModel(self, e):
532 if self.selection is None:
534 self.list.remove(self.selection)
535 self._updateListbox()
536 self.glCanvas.Refresh()
538 def OnMoveUp(self, e):
539 if self.selection is None:
541 i = self.listbox.GetSelection()
544 self.list.remove(self.selection)
545 self.list.insert(i-1, self.selection)
546 self._updateListbox()
547 self.glCanvas.Refresh()
549 def OnMoveDown(self, e):
550 if self.selection is None:
552 i = self.listbox.GetSelection()
553 if i == len(self.list) - 1:
555 self.list.remove(self.selection)
556 self.list.insert(i+1, self.selection)
557 self._updateListbox()
558 self.glCanvas.Refresh()
561 if self.selection is None:
564 item = self.selection.clone()
565 self.list.insert(self.list.index(self.selection), item)
566 self.selection = item
568 self._updateListbox()
569 self.glCanvas.Refresh()
571 def OnSetCustomProfile(self, e):
572 if self.selection is None:
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()
580 self.selection.profile = None
581 self._updateListbox()
584 def _updateListbox(self):
586 for item in self.list:
587 if item.profile is not None:
588 self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1] + " *")
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)
597 self.selection = None
598 self.listbox.SetSelection(-1)
599 if self.alwaysAutoPlace:
600 self.OnAutoPlace(None)
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)
610 self._doAutoPlace(bestAllowedSize)
611 if not self.alwaysAutoPlace:
612 for item in self.list:
614 self.glCanvas.Refresh()
616 def _doAutoPlace(self, allowedSizeY):
617 extraSizeMin, extraSizeMax = self.getExtraHeadSize()
619 if extraSizeMin[0] > extraSizeMax[0]:
620 posX = self.machineSize[0]
628 minX = self.machineSize[0]
629 minY = self.machineSize[1]
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:
637 posX = minX - extraSizeMax[0] - 1
639 posX = maxX + extraSizeMin[0] + 1
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)
649 for item in self.list:
651 item.centerX -= minX / 2
653 item.centerX += (self.machineSize[0] - maxX) / 2
654 item.centerY += (self.machineSize[1] - maxY) / 2
656 if minX < 0 or maxX > self.machineSize[0]:
657 return ((maxX - minX) + (maxY - minY)) * 100
659 return (maxX - minX) + (maxY - minY)
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:
667 resultFilename = dlg.GetPath()
670 put = profile.setTempOverride
671 oldProfile = profile.getGlobalProfileString()
673 if self.printMode == 0:
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]
681 pos = [item.centerX, item.centerY]
682 positionList.append(pos + item.matrix.getA().flatten().tolist())
684 sliceCommand = sliceRun.getSliceCommand(resultFilename, fileList, positionList)
686 self._saveCombinedSTL(resultFilename + "_temp_.stl")
687 sliceCommand = sliceRun.getSliceCommand(resultFilename, [resultFilename + "_temp_.stl"], [profile.getMachineCenterCoords()])
689 pspw = ProjectSliceProgressWindow(sliceCommand, resultFilename, len(self.list))
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])
708 if self.printMode == 1:
709 extraSizeMin = numpy.array([6.0, 6.0, 0])
710 extraSizeMax = numpy.array([6.0, 6.0, 0])
712 return extraSizeMin, extraSizeMax
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)
723 self.view3D = self.parent.alwaysAutoPlace
727 self.zoom = self.parent.machineSize[0] / 2 + 10
730 self.allowDrag = False
731 self.tempMatrix = None
733 self.objColor = profile.getPreferenceColour('model_colour')
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]))
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)
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)
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':
776 self.yaw += e.GetX() - self.oldX
777 self.pitch -= e.GetY() - self.oldY
782 elif not self.parent.alwaysAutoPlace:
783 item = self.parent.selection
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
788 elif self.dragType == 'tool':
789 self.parent.tool.OnDrag(p0, p1)
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()
798 self.allowDrag = False
799 if e.Dragging() and e.RightIsDown():
801 self.zoom += e.GetY() - self.oldY
808 def OnMouseWheel(self,e):
810 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
815 def OnEraseBackground(self,event):
816 #Workaround for windows background redraw flicker.
819 def OnSize(self,event):
822 def OnPaint(self,event):
823 opengl.InitGL(self, self.view3D, self.zoom)
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])
832 self.viewport = glGetIntegerv(GL_VIEWPORT)
833 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
834 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
839 machineSize = self.parent.machineSize
840 extraSizeMin, extraSizeMax = self.parent.getExtraHeadSize()
842 for item in self.parent.list:
843 item.validPlacement = True
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
863 for item in self.parent.list:
864 if item == self.parent.selection:
866 if item.modelDisplayList is None:
867 item.modelDisplayList = glGenLists(1);
869 item.modelDirty = False
870 glNewList(item.modelDisplayList, GL_COMPILE)
871 opengl.DrawMesh(item.mesh)
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))
879 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor)
880 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 2, self.objColor))
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])
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])
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)
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)
907 vMin =-item.getSize() / 2
908 vMax = item.getSize() / 2
911 vMinHead = vMin - extraSizeMin# - self.parent.extruderOffset[item.extruder]
912 vMaxHead = vMax + extraSizeMax# - self.parent.extruderOffset[item.extruder]
914 glDisable(GL_LIGHTING)
916 if not self.parent.alwaysAutoPlace:
918 if self.parent.selection == item:
920 glColor3f(1.0,0.0,0.3)
922 glColor3f(1.0,0.0,1.0)
923 opengl.DrawBox(vMin, vMax)
925 glColor3f(1.0,0.3,0.0)
927 glColor3f(1.0,1.0,0.0)
928 opengl.DrawBox(vMinHead, vMaxHead)
931 glColor3f(0.5,0.0,0.1)
933 glColor3f(0.5,0.0,0.5)
934 opengl.DrawBox(vMinHead, vMaxHead)
937 glColor3f(0.7,0.1,0.0)
939 glColor3f(0.7,0.7,0.0)
940 opengl.DrawBox(vMin, vMax)
944 opengl.DrawMachine(util3d.Vector3(machineSize[0], machineSize[1], machineSize[2]))
946 if self.parent.selection is not None:
948 glTranslate(self.parent.selection.centerX, self.parent.selection.centerY, self.parent.selection.getSize()[2]/2)
949 self.parent.tool.OnDraw()
952 def getObjectSize(self):
953 if self.parent.selection is not None:
954 return self.parent.selection.getSize()
956 def getObjectBoundaryCircle(self):
957 if self.parent.selection is not None:
958 return self.parent.selection.getBoundaryCircle()
960 def getObjectMatrix(self):
961 return self.parent.selection.matrix
962 def getObjectCenterPos(self):
963 if self.parent.selection is None:
965 return [self.parent.selection.centerX, self.parent.selection.centerY, self.getObjectSize()[2] / 2]
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))
972 self.sliceCommand = sliceCommand
973 self.resultFilename = resultFilename
974 self.fileCount = fileCount
976 self.prevStep = 'start'
977 self.totalDoneFactor = 0.0
978 self.startTime = time.time()
979 self.sliceStartTime = time.time()
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)
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)
997 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)
998 self.SetSizer(self.sizer)
1002 threading.Thread(target=self.OnRun).start()
1004 def OnAbort(self, e):
1009 self.abortButton.SetLabel('Close')
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
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)
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)
1039 self.progressLog.append(line)
1040 wx.CallAfter(self.statusText.SetLabel, line)
1043 wx.CallAfter(self.statusText.SetLabel, "Aborted by user.")
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)
1054 gcode = gcodeInterpreter.gcode()
1055 gcode.load(self.resultFilename)
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)
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'
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)
1095 taskbar.setBusy(self, False)
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))
1103 def OnOpenFileLocation(self, e):
1104 explorer.openExplorer(self.resultFilename)
1106 def OnPrint(self, e):
1107 printWindow.printFile(self.resultFilename)
1109 def OnShowLog(self, e):
1110 LogWindow('\n'.join(self.progressLog))
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)
1116 self.parent = parent
1117 wx.EVT_CLOSE(self, self.OnClose)
1119 self.panel = configBase.configPanelBase(self)
1120 extruderAmount = int(profile.getPreference('extruder_amount'))
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)
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)
1141 self.MakeModal(True)
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()
1150 self.MakeModal(False)
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))