1 from __future__ import absolute_import
12 from wx import glcanvas
15 OpenGL.ERROR_CHECKING = False
16 from OpenGL.GLU import *
17 from OpenGL.GL import *
20 print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
23 from Cura.gui.util import opengl
24 from Cura.gui.util import toolbarUtil
25 from Cura.gui import configBase
26 from Cura.gui import printWindow
27 from Cura.gui.util import dropTarget
28 from Cura.gui.util import taskbar
29 from Cura.util import validators
30 from Cura.util import profile
31 from Cura.util import util3d
32 from Cura.util import meshLoader
33 from Cura.util import stl
34 from Cura.util import mesh
35 from Cura.util import sliceRun
36 from Cura.util import gcodeInterpreter
37 from Cura.util import exporer
42 class ProjectObject(object):
43 def __init__(self, parent, filename):
44 super(ProjectObject, self).__init__()
46 self.mesh = meshLoader.loadMesh(filename)
49 self.filename = filename
60 self.modelDisplayList = None
61 self.modelDirty = False
63 self.mesh.getMinimumZ()
65 self.centerX = -self.getMinimum()[0] + 5
66 self.centerY = -self.getMinimum()[1] + 5
68 self.updateModelTransform()
70 self.centerX = -self.getMinimum()[0] + 5
71 self.centerY = -self.getMinimum()[1] + 5
73 def isSameExceptForPosition(self, other):
74 if self.filename != other.filename:
76 if self.scale != other.scale:
78 if self.rotate != other.rotate:
80 if self.flipX != other.flipX:
82 if self.flipY != other.flipY:
84 if self.flipZ != other.flipZ:
86 if self.swapXZ != other.swapXZ:
88 if self.swapYZ != other.swapYZ:
90 if self.extruder != other.extruder:
92 if self.profile != other.profile:
96 def updateModelTransform(self):
97 self.mesh.setRotateMirror(self.rotate, self.flipX, self.flipY, self.flipZ, self.swapXZ, self.swapYZ)
98 minZ = self.mesh.getMinimumZ()
99 minV = self.getMinimum()
100 maxV = self.getMaximum()
101 self.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minZ])
102 minZ = self.mesh.getMinimumZ()
103 self.modelDirty = True
105 def getMinimum(self):
106 return self.mesh.getMinimum()
107 def getMaximum(self):
108 return self.mesh.getMaximum()
110 return self.mesh.getSize()
113 p = ProjectObject(self.parent, self.filename)
115 p.centerX = self.centerX + 5
116 p.centerY = self.centerY + 5
118 p.filename = self.filename
120 p.rotate = self.rotate
124 p.swapXZ = self.swapXZ
125 p.swapYZ = self.swapYZ
126 p.extruder = self.extruder
127 p.profile = self.profile
129 p.updateModelTransform()
134 if self.centerX < -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]:
135 self.centerX = -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]
136 if self.centerY < -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]:
137 self.centerY = -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]
138 if self.centerX > self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale:
139 self.centerX = self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale
140 if self.centerY > self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale:
141 self.centerY = self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale
143 class projectPlanner(wx.Frame):
144 "Main user interface window"
146 super(projectPlanner, self).__init__(None, title='Cura - Project Planner')
148 wx.EVT_CLOSE(self, self.OnClose)
149 self.panel = wx.Panel(self, -1)
150 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
151 self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
153 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
156 self.selection = None
158 self.alwaysAutoPlace = True
160 self.machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
161 self.headSizeMin = numpy.array([profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0])
162 self.headSizeMax = numpy.array([profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0])
164 self.extruderOffset = [
165 numpy.array([0,0,0]),
166 numpy.array([profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1'), 0]),
167 numpy.array([profile.getPreferenceFloat('extruder_offset_x2'), profile.getPreferenceFloat('extruder_offset_y2'), 0]),
168 numpy.array([profile.getPreferenceFloat('extruder_offset_x3'), profile.getPreferenceFloat('extruder_offset_y3'), 0])]
170 self.toolbar = toolbarUtil.Toolbar(self.panel)
172 toolbarUtil.NormalButton(self.toolbar, self.OnLoadProject, 'open.png', 'Open project')
173 toolbarUtil.NormalButton(self.toolbar, self.OnSaveProject, 'save.png', 'Save project')
174 self.toolbar.AddSeparator()
176 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick).SetValue(self.alwaysAutoPlace)
177 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick).SetValue(not self.alwaysAutoPlace)
178 self.toolbar.AddSeparator()
179 toolbarUtil.NormalButton(self.toolbar, self.OnPreferences, 'preferences.png', 'Project planner preferences')
180 self.toolbar.AddSeparator()
181 toolbarUtil.NormalButton(self.toolbar, self.OnCutMesh, 'cut-mesh.png', 'Cut a plate STL into multiple STL files, and add those files to the project.\nNote: Splitting up plates sometimes takes a few minutes.')
182 toolbarUtil.NormalButton(self.toolbar, self.OnSaveCombinedSTL, 'save-combination.png', 'Save all the combined STL files into a single STL file as a plate.')
183 self.toolbar.AddSeparator()
185 self.printOneAtATime = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Print one object at a time', callback=self.OnPrintTypeChange)
186 self.printAllAtOnce = toolbarUtil.RadioButton(self.toolbar, group, 'all-at-once-on.png', 'all-at-once-off.png', 'Print all the objects at once', callback=self.OnPrintTypeChange)
187 self.toolbar.AddSeparator()
188 toolbarUtil.NormalButton(self.toolbar, self.OnQuit, 'exit.png', 'Close project planner')
190 self.toolbar.Realize()
192 self.toolbar2 = toolbarUtil.Toolbar(self.panel)
194 toolbarUtil.NormalButton(self.toolbar2, self.OnAddModel, 'object-add.png', 'Add model')
195 toolbarUtil.NormalButton(self.toolbar2, self.OnRemModel, 'object-remove.png', 'Remove model')
196 self.toolbar2.AddSeparator()
197 toolbarUtil.NormalButton(self.toolbar2, self.OnMoveUp, 'move-up.png', 'Move model up in print list')
198 toolbarUtil.NormalButton(self.toolbar2, self.OnMoveDown, 'move-down.png', 'Move model down in print list')
199 toolbarUtil.NormalButton(self.toolbar2, self.OnCopy, 'copy.png', 'Make a copy of the current selected object')
200 toolbarUtil.NormalButton(self.toolbar2, self.OnSetCustomProfile, 'set-profile.png', 'Set a custom profile to be used to prepare a specific object.')
201 self.toolbar2.AddSeparator()
202 if not self.alwaysAutoPlace:
203 toolbarUtil.NormalButton(self.toolbar2, self.OnAutoPlace, 'autoplace.png', 'Automaticly organize the objects on the platform.')
204 toolbarUtil.NormalButton(self.toolbar2, self.OnSlice, 'slice.png', 'Prepare to project into a gcode file.')
205 self.toolbar2.Realize()
207 self.toolbar3 = toolbarUtil.Toolbar(self.panel)
208 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar3, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.OnMirrorChange)
209 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar3, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.OnMirrorChange)
210 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar3, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.OnMirrorChange)
211 self.toolbar3.AddSeparator()
214 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.OnMirrorChange)
215 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.OnMirrorChange)
216 self.toolbar3.Realize()
218 sizer = wx.GridBagSizer(2,2)
219 self.panel.SetSizer(sizer)
220 self.preview = PreviewGLCanvas(self.panel, self)
221 self.listbox = wx.ListBox(self.panel, -1, choices=[])
222 self.addButton = wx.Button(self.panel, -1, "Add")
223 self.remButton = wx.Button(self.panel, -1, "Remove")
224 self.sliceButton = wx.Button(self.panel, -1, "Prepare")
225 if not self.alwaysAutoPlace:
226 self.autoPlaceButton = wx.Button(self.panel, -1, "Auto Place")
228 sizer.Add(self.toolbar, (0,0), span=(1,1), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
229 sizer.Add(self.toolbar2, (0,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
230 sizer.Add(self.preview, (1,0), span=(5,1), flag=wx.EXPAND)
231 sizer.Add(self.listbox, (1,1), span=(1,2), flag=wx.EXPAND)
232 sizer.Add(self.toolbar3, (2,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
233 sizer.Add(self.addButton, (3,1), span=(1,1))
234 sizer.Add(self.remButton, (3,2), span=(1,1))
235 sizer.Add(self.sliceButton, (4,1), span=(1,1))
236 if not self.alwaysAutoPlace:
237 sizer.Add(self.autoPlaceButton, (4,2), span=(1,1))
238 sizer.AddGrowableCol(0)
239 sizer.AddGrowableRow(1)
241 self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)
242 self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)
243 self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)
244 if not self.alwaysAutoPlace:
245 self.autoPlaceButton.Bind(wx.EVT_BUTTON, self.OnAutoPlace)
246 self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)
248 panel = wx.Panel(self.panel, -1)
249 sizer.Add(panel, (5,1), span=(1,2))
251 sizer = wx.GridBagSizer(2,2)
252 panel.SetSizer(sizer)
254 self.scaleCtrl = wx.TextCtrl(panel, -1, '')
255 self.rotateCtrl = wx.SpinCtrl(panel, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
256 self.rotateCtrl.SetRange(0, 360)
258 sizer.Add(wx.StaticText(panel, -1, 'Scale'), (0,0), flag=wx.ALIGN_CENTER_VERTICAL)
259 sizer.Add(self.scaleCtrl, (0,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
260 sizer.Add(wx.StaticText(panel, -1, 'Rotate'), (1,0), flag=wx.ALIGN_CENTER_VERTICAL)
261 sizer.Add(self.rotateCtrl, (1,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
263 if int(profile.getPreference('extruder_amount')) > 1:
264 self.extruderCtrl = wx.ComboBox(panel, -1, '1', choices=map(str, range(1, int(profile.getPreference('extruder_amount'))+1)), style=wx.CB_DROPDOWN|wx.CB_READONLY)
265 sizer.Add(wx.StaticText(panel, -1, 'Extruder'), (2,0), flag=wx.ALIGN_CENTER_VERTICAL)
266 sizer.Add(self.extruderCtrl, (2,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
267 self.extruderCtrl.Bind(wx.EVT_COMBOBOX, self.OnExtruderChange)
269 self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange)
270 self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange)
272 self.SetSize((800,600))
274 def OnClose(self, e):
280 def OnPreferences(self, e):
281 prefDialog = preferencesDialog(self)
283 prefDialog.Show(True)
285 def OnCutMesh(self, e):
286 dlg=wx.FileDialog(self, "Open file to cut", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
287 dlg.SetWildcard(meshLoader.wildcardFilter())
288 if dlg.ShowModal() == wx.ID_OK:
289 filename = dlg.GetPath()
290 model = meshLoader.loadMesh(filename)
291 pd = wx.ProgressDialog('Splitting model.', 'Splitting model into multiple parts.', model.vertexCount, self, wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_SMOOTH)
292 parts = model.splitToParts(pd.Update)
294 partFilename = filename[:filename.rfind('.')] + "_part%d.stl" % (parts.index(part))
295 stl.saveAsSTL(part, partFilename)
296 item = ProjectObject(self, partFilename)
297 self.list.append(item)
298 self.selection = item
299 self._updateListbox()
300 self.OnListSelect(None)
302 self.preview.Refresh()
305 def OnDropFiles(self, filenames):
306 for filename in filenames:
307 item = ProjectObject(self, filename)
308 profile.putPreference('lastFile', item.filename)
309 self.list.append(item)
310 self.selection = item
311 self._updateListbox()
312 self.OnListSelect(None)
313 self.preview.Refresh()
315 def OnPrintTypeChange(self):
317 if self.printAllAtOnce.GetValue():
319 if self.alwaysAutoPlace:
320 self.OnAutoPlace(None)
321 self.preview.Refresh()
323 def OnSaveCombinedSTL(self, e):
324 dlg=wx.FileDialog(self, "Save as STL", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
325 dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")
326 if dlg.ShowModal() == wx.ID_OK:
327 self._saveCombinedSTL(dlg.GetPath())
330 def _saveCombinedSTL(self, filename):
332 for item in self.list:
333 totalCount += item.mesh.vertexCount
335 output._prepareVertexCount(totalCount)
336 for item in self.list:
337 offset = numpy.array([item.centerX, item.centerY, 0])
338 for v in item.mesh.vertexes:
339 v0 = v * item.scale + offset
340 output.addVertex(v0[0], v0[1], v0[2])
341 stl.saveAsSTL(output, filename)
343 def OnSaveProject(self, e):
344 dlg=wx.FileDialog(self, "Save project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
345 dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")
346 if dlg.ShowModal() == wx.ID_OK:
347 cp = ConfigParser.ConfigParser()
349 for item in self.list:
350 section = 'model_%d' % (i)
351 cp.add_section(section)
352 cp.set(section, 'filename', item.filename.encode("utf-8"))
353 cp.set(section, 'centerX', str(item.centerX))
354 cp.set(section, 'centerY', str(item.centerY))
355 cp.set(section, 'scale', str(item.scale))
356 cp.set(section, 'rotate', str(item.rotate))
357 cp.set(section, 'flipX', str(item.flipX))
358 cp.set(section, 'flipY', str(item.flipY))
359 cp.set(section, 'flipZ', str(item.flipZ))
360 cp.set(section, 'swapXZ', str(item.swapXZ))
361 cp.set(section, 'swapYZ', str(item.swapYZ))
362 cp.set(section, 'extruder', str(item.extruder+1))
363 if item.profile != None:
364 cp.set(section, 'profile', item.profile)
366 cp.write(open(dlg.GetPath(), "w"))
369 def OnLoadProject(self, e):
370 dlg=wx.FileDialog(self, "Open project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
371 dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")
372 if dlg.ShowModal() == wx.ID_OK:
373 cp = ConfigParser.ConfigParser()
374 cp.read(dlg.GetPath())
377 while cp.has_section('model_%d' % (i)):
378 section = 'model_%d' % (i)
380 item = ProjectObject(self, unicode(cp.get(section, 'filename'), "utf-8"))
381 item.centerX = float(cp.get(section, 'centerX'))
382 item.centerY = float(cp.get(section, 'centerY'))
383 item.scale = float(cp.get(section, 'scale'))
384 item.rotate = float(cp.get(section, 'rotate'))
385 item.flipX = cp.get(section, 'flipX') == 'True'
386 item.flipY = cp.get(section, 'flipY') == 'True'
387 item.flipZ = cp.get(section, 'flipZ') == 'True'
388 item.swapXZ = cp.get(section, 'swapXZ') == 'True'
389 item.swapYZ = cp.get(section, 'swapYZ') == 'True'
390 if cp.has_option(section, 'extruder'):
391 item.extuder = int(cp.get(section, 'extruder')) - 1
392 if cp.has_option(section, 'profile'):
393 item.profile = cp.get(section, 'profile')
394 item.updateModelTransform()
397 self.list.append(item)
399 self.selected = self.list[0]
400 self._updateListbox()
401 self.OnListSelect(None)
402 self.preview.Refresh()
407 self.preview.yaw = 30
408 self.preview.pitch = 60
409 self.preview.zoom = 300
410 self.preview.view3D = True
411 self.preview.Refresh()
413 def OnTopClick(self):
414 self.preview.view3D = False
415 self.preview.zoom = self.machineSize[0] / 2 + 10
416 self.preview.offsetX = 0
417 self.preview.offsetY = 0
418 self.preview.Refresh()
420 def OnListSelect(self, e):
421 if self.listbox.GetSelection() == -1:
423 self.selection = self.list[self.listbox.GetSelection()]
424 self.scaleCtrl.SetValue(str(self.selection.scale))
425 self.rotateCtrl.SetValue(int(self.selection.rotate))
426 if int(profile.getPreference('extruder_amount')) > 1:
428 self.extruderCtrl.SetValue(str(self.selection.extruder+1))
430 self.mirrorX.SetValue(self.selection.flipX)
431 self.mirrorY.SetValue(self.selection.flipY)
432 self.mirrorZ.SetValue(self.selection.flipZ)
433 self.swapXZ.SetValue(self.selection.swapXZ)
434 self.swapYZ.SetValue(self.selection.swapYZ)
436 self.preview.Refresh()
438 def OnAddModel(self, e):
439 dlg=wx.FileDialog(self, "Open file to print", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
440 dlg.SetWildcard(meshLoader.wildcardFilter())
441 if dlg.ShowModal() == wx.ID_OK:
442 for filename in dlg.GetPaths():
443 item = ProjectObject(self, filename)
444 profile.putPreference('lastFile', item.filename)
445 self.list.append(item)
446 self.selection = item
447 self._updateListbox()
448 self.OnListSelect(None)
449 self.preview.Refresh()
452 def OnRemModel(self, e):
453 if self.selection == None:
455 self.list.remove(self.selection)
456 self._updateListbox()
457 self.preview.Refresh()
459 def OnMoveUp(self, e):
460 if self.selection == None:
462 i = self.listbox.GetSelection()
465 self.list.remove(self.selection)
466 self.list.insert(i-1, self.selection)
467 self._updateListbox()
468 self.preview.Refresh()
470 def OnMoveDown(self, e):
471 if self.selection is None:
473 i = self.listbox.GetSelection()
474 if i == len(self.list) - 1:
476 self.list.remove(self.selection)
477 self.list.insert(i+1, self.selection)
478 self._updateListbox()
479 self.preview.Refresh()
482 if self.selection is None:
485 item = self.selection.clone()
486 self.list.insert(self.list.index(self.selection), item)
487 self.selection = item
489 self._updateListbox()
490 self.preview.Refresh()
492 def OnSetCustomProfile(self, e):
493 if self.selection is None:
496 dlg=wx.FileDialog(self, "Select profile", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
497 dlg.SetWildcard("Profile files (*.ini)|*.ini;*.INI")
498 if dlg.ShowModal() == wx.ID_OK:
499 self.selection.profile = dlg.GetPath()
501 self.selection.profile = None
502 self._updateListbox()
505 def _updateListbox(self):
507 for item in self.list:
508 if item.profile != None:
509 self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1] + " *")
511 self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1])
512 if self.selection in self.list:
513 self.listbox.SetSelection(self.list.index(self.selection))
514 elif len(self.list) > 0:
515 self.selection = self.list[0]
516 self.listbox.SetSelection(0)
518 self.selection = None
519 self.listbox.SetSelection(-1)
520 if self.alwaysAutoPlace:
521 self.OnAutoPlace(None)
523 def OnAutoPlace(self, e):
524 bestAllowedSize = int(self.machineSize[1])
525 bestArea = self._doAutoPlace(bestAllowedSize)
526 for i in xrange(10, int(self.machineSize[1]), 10):
527 area = self._doAutoPlace(i)
531 self._doAutoPlace(bestAllowedSize)
532 if not self.alwaysAutoPlace:
533 for item in self.list:
535 self.preview.Refresh()
537 def _doAutoPlace(self, allowedSizeY):
538 extraSizeMin, extraSizeMax = self.getExtraHeadSize()
540 if extraSizeMin[0] > extraSizeMax[0]:
541 posX = self.machineSize[0]
549 minX = self.machineSize[0]
550 minY = self.machineSize[1]
553 for item in self.list:
554 item.centerX = posX + item.getMaximum()[0] * item.scale * dirX
555 item.centerY = posY + item.getMaximum()[1] * item.scale * dirY
556 if item.centerY + item.getSize()[1] >= allowedSizeY:
558 posX = minX - extraSizeMax[0] - 1
560 posX = maxX + extraSizeMin[0] + 1
562 item.centerX = posX + item.getMaximum()[0] * item.scale * dirX
563 item.centerY = posY + item.getMaximum()[1] * item.scale * dirY
564 posY += item.getSize()[1] * item.scale * dirY + extraSizeMin[1] + 1
565 minX = min(minX, item.centerX - item.getSize()[0] * item.scale / 2)
566 minY = min(minY, item.centerY - item.getSize()[1] * item.scale / 2)
567 maxX = max(maxX, item.centerX + item.getSize()[0] * item.scale / 2)
568 maxY = max(maxY, item.centerY + item.getSize()[1] * item.scale / 2)
570 for item in self.list:
572 item.centerX -= minX / 2
574 item.centerX += (self.machineSize[0] - maxX) / 2
575 item.centerY += (self.machineSize[1] - maxY) / 2
577 if minX < 0 or maxX > self.machineSize[0]:
578 return ((maxX - minX) + (maxY - minY)) * 100
580 return (maxX - minX) + (maxY - minY)
582 def OnSlice(self, e):
583 dlg=wx.FileDialog(self, "Save project gcode file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
584 dlg.SetWildcard("GCode file (*.gcode)|*.gcode")
585 if dlg.ShowModal() != wx.ID_OK:
588 resultFilename = dlg.GetPath()
591 put = profile.setTempOverride
592 oldProfile = profile.getGlobalProfileString()
594 put('add_start_end_gcode', 'False')
595 put('gcode_extension', 'project_tmp')
596 if self.printMode == 0:
599 for item in self.list:
600 if item.profile is not None and os.path.isfile(item.profile):
601 profile.loadGlobalProfile(item.profile)
602 put('object_center_x', item.centerX - self.extruderOffset[item.extruder][0])
603 put('object_center_y', item.centerY - self.extruderOffset[item.extruder][1])
604 put('model_scale', item.scale)
605 put('flip_x', item.flipX)
606 put('flip_y', item.flipY)
607 put('flip_z', item.flipZ)
608 put('model_rotate_base', item.rotate)
609 put('swap_xz', item.swapXZ)
610 put('swap_yz', item.swapYZ)
613 action.sliceCmd = sliceRun.getSliceCommand(item.filename)
614 action.centerX = item.centerX
615 action.centerY = item.centerY
616 action.temperature = profile.getProfileSettingFloat('print_temperature')
617 action.extruder = item.extruder
618 action.filename = item.filename
619 clearZ = max(clearZ, item.getSize()[2] * item.scale + 5.0)
620 action.clearZ = clearZ
621 action.leaveResultForNextSlice = False
622 action.usePreviousSlice = False
623 actionList.append(action)
625 if self.list.index(item) > 0 and item.isSameExceptForPosition(self.list[self.list.index(item)-1]):
626 actionList[-2].leaveResultForNextSlice = True
627 actionList[-1].usePreviousSlice = True
629 if item.profile is not None:
630 profile.loadGlobalProfileFromString(oldProfile)
633 self._saveCombinedSTL(resultFilename + "_temp_.stl")
634 put('model_scale', 1.0)
638 put('model_rotate_base', 0)
639 put('swap_xz', False)
640 put('swap_yz', False)
644 action.sliceCmd = sliceRun.getSliceCommand(resultFilename, [resultFilename + "_temp_.stl"], [profile.getMachineCenterCoords()])
645 action.centerX = profile.getMachineCenterCoords()[0]
646 action.centerY = profile.getMachineCenterCoords()[1]
647 action.temperature = profile.getProfileSettingFloat('print_temperature')
649 action.filename = resultFilename + "_temp_.stl"
651 action.leaveResultForNextSlice = False
652 action.usePreviousSlice = False
654 actionList.append(action)
656 #Restore the old profile.
657 profile.resetTempOverride()
659 pspw = ProjectSliceProgressWindow(actionList, resultFilename)
660 pspw.extruderOffset = self.extruderOffset
664 def OnScaleChange(self, e):
665 if self.selection is None:
668 self.selection.scale = float(self.scaleCtrl.GetValue())
670 self.selection.scale = 1.0
671 if self.alwaysAutoPlace:
672 self.OnAutoPlace(None)
673 self.preview.Refresh()
675 def OnRotateChange(self, e):
676 if self.selection is None:
678 self.selection.rotate = float(self.rotateCtrl.GetValue())
679 self.selection.updateModelTransform()
680 if self.alwaysAutoPlace:
681 self.OnAutoPlace(None)
682 self.preview.Refresh()
684 def OnExtruderChange(self, e):
685 if self.selection == None:
687 self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1
688 self.preview.Refresh()
690 def OnMirrorChange(self):
691 if self.selection == None:
693 self.selection.flipX = self.mirrorX.GetValue()
694 self.selection.flipY = self.mirrorY.GetValue()
695 self.selection.flipZ = self.mirrorZ.GetValue()
696 self.selection.swapXZ = self.swapXZ.GetValue()
697 self.selection.swapYZ = self.swapYZ.GetValue()
698 self.selection.updateModelTransform()
699 if self.alwaysAutoPlace:
700 self.OnAutoPlace(None)
701 self.preview.Refresh()
703 def getExtraHeadSize(self):
704 extraSizeMin = self.headSizeMin
705 extraSizeMax = self.headSizeMax
706 if profile.getProfileSettingFloat('skirt_line_count') > 0:
707 skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
708 extraSizeMin = extraSizeMin + numpy.array([skirtSize, skirtSize, 0])
709 extraSizeMax = extraSizeMax + numpy.array([skirtSize, skirtSize, 0])
710 if profile.getProfileSetting('enable_raft') != 'False':
711 raftSize = profile.getProfileSettingFloat('raft_margin') * 2
712 extraSizeMin = extraSizeMin + numpy.array([raftSize, raftSize, 0])
713 extraSizeMax = extraSizeMax + numpy.array([raftSize, raftSize, 0])
714 if profile.getProfileSetting('support') != 'None':
715 extraSizeMin = extraSizeMin + numpy.array([3.0, 0, 0])
716 extraSizeMax = extraSizeMax + numpy.array([3.0, 0, 0])
718 if self.printMode == 1:
719 extraSizeMin = numpy.array([6.0, 6.0, 0])
720 extraSizeMax = numpy.array([6.0, 6.0, 0])
722 return extraSizeMin, extraSizeMax
724 class PreviewGLCanvas(glcanvas.GLCanvas):
725 def __init__(self, parent, projectPlannerWindow):
726 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
727 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
728 self.parent = projectPlannerWindow
729 self.context = glcanvas.GLContext(self)
730 wx.EVT_PAINT(self, self.OnPaint)
731 wx.EVT_SIZE(self, self.OnSize)
732 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
733 wx.EVT_LEFT_DOWN(self, self.OnMouseLeftDown)
734 wx.EVT_MOTION(self, self.OnMouseMotion)
735 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
740 self.view3D = self.parent.alwaysAutoPlace
744 self.zoom = self.parent.machineSize[0] / 2 + 10
745 self.allowDrag = False
747 self.objColor = profile.getPreferenceColour('model_colour')
749 def OnMouseLeftDown(self,e):
750 self.allowDrag = True
751 if not self.parent.alwaysAutoPlace and not self.view3D:
752 #TODO: Translate mouse X/Y to 3D X/Y/Z
753 for item in self.parent.list:
754 iMin = (item.getMinimum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - self.parent.extruderOffset[item.extruder]
755 iMax = (item.getMaximum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - self.parent.extruderOffset[item.extruder]
757 def OnMouseMotion(self,e):
758 if self.allowDrag and e.Dragging() and e.LeftIsDown():
760 self.yaw += e.GetX() - self.oldX
761 self.pitch -= e.GetY() - self.oldY
766 elif not self.parent.alwaysAutoPlace:
767 item = self.parent.selection
769 item.centerX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
770 item.centerY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
774 self.allowDrag = False
775 if e.Dragging() and e.RightIsDown():
777 self.zoom += e.GetY() - self.oldY
784 def OnMouseWheel(self,e):
786 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
791 def OnEraseBackground(self,event):
792 #Workaround for windows background redraw flicker.
795 def OnSize(self,event):
798 def OnPaint(self,event):
799 dc = wx.PaintDC(self)
800 if not hasOpenGLlibs:
802 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
804 self.SetCurrent(self.context)
805 opengl.InitGL(self, self.view3D, self.zoom)
807 glTranslate(0,0,-self.zoom)
808 glRotate(-self.pitch, 1,0,0)
809 glRotate(self.yaw, 0,0,1)
811 glTranslate(self.offsetX, self.offsetY, 0.0)
812 glTranslate(-self.parent.machineSize[0]/2, -self.parent.machineSize[1]/2, 0)
818 machineSize = self.parent.machineSize
819 extraSizeMin, extraSizeMax = self.parent.getExtraHeadSize()
821 for item in self.parent.list:
822 item.validPlacement = True
825 for idx1 in xrange(0, len(self.parent.list)):
826 item = self.parent.list[idx1]
827 iMin1 = (item.getMinimum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - extraSizeMin - self.parent.extruderOffset[item.extruder]
828 iMax1 = (item.getMaximum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) + extraSizeMax - self.parent.extruderOffset[item.extruder]
829 if iMin1[0] < -self.parent.headSizeMin[0] or iMin1[1] < -self.parent.headSizeMin[1]:
830 item.validPlacement = False
831 if iMax1[0] > machineSize[0] + self.parent.headSizeMax[0] or iMax1[1] > machineSize[1] + self.parent.headSizeMax[1]:
832 item.validPlacement = False
833 for idx2 in xrange(0, idx1):
834 item2 = self.parent.list[idx2]
835 iMin2 = (item2.getMinimum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])
836 iMax2 = (item2.getMaximum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])
837 if item != item2 and iMax1[0] >= iMin2[0] and iMin1[0] <= iMax2[0] and iMax1[1] >= iMin2[1] and iMin1[1] <= iMax2[1]:
838 item.validPlacement = False
842 for item in self.parent.list:
843 if item == self.parent.selection:
845 if item.modelDisplayList is None:
846 item.modelDisplayList = glGenLists(1);
848 item.modelDirty = False
849 modelSize = item.getMaximum() - item.getMinimum()
850 glNewList(item.modelDisplayList, GL_COMPILE)
851 opengl.DrawMesh(item.mesh)
854 if item.validPlacement:
855 if self.parent.selection == item:
856 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x + 0.2, self.objColor))
857 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 2, self.objColor))
859 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor)
860 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 2, self.objColor))
862 if self.parent.selection == item:
863 glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 0.0, 0.0, 0.0])
864 glLightfv(GL_LIGHT0, GL_AMBIENT, [0.2, 0.0, 0.0, 0.0])
866 glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 0.0, 0.0, 0.0])
867 glLightfv(GL_LIGHT0, GL_AMBIENT, [0.2, 0.0, 0.0, 0.0])
870 glEnable(GL_LIGHTING)
871 glTranslate(item.centerX, item.centerY, 0)
873 glScalef(item.scale, item.scale, item.scale)
874 glCallList(item.modelDisplayList)
877 vMin = item.getMinimum() * item.scale
878 vMax = item.getMaximum() * item.scale
879 vMinHead = vMin - extraSizeMin - self.parent.extruderOffset[item.extruder]
880 vMaxHead = vMax + extraSizeMax - self.parent.extruderOffset[item.extruder]
882 glDisable(GL_LIGHTING)
884 if not self.parent.alwaysAutoPlace:
885 if self.parent.selection == item:
887 glColor3f(1.0,0.0,0.3)
889 glColor3f(1.0,0.0,1.0)
890 opengl.DrawBox(vMin, vMax)
892 glColor3f(1.0,0.3,0.0)
894 glColor3f(1.0,1.0,0.0)
895 opengl.DrawBox(vMinHead, vMaxHead)
898 glColor3f(0.5,0.0,0.1)
900 glColor3f(0.5,0.0,0.5)
901 opengl.DrawBox(vMinHead, vMaxHead)
904 glColor3f(0.7,0.1,0.0)
906 glColor3f(0.7,0.7,0.0)
907 opengl.DrawBox(vMin, vMax)
911 opengl.DrawMachine(util3d.Vector3(machineSize[0], machineSize[1], machineSize[2]))
914 class ProjectSliceProgressWindow(wx.Frame):
915 def __init__(self, actionList, resultFilename):
916 super(ProjectSliceProgressWindow, self).__init__(None, title='Cura')
917 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
919 self.actionList = actionList
920 self.resultFilename = resultFilename
922 self.prevStep = 'start'
923 self.totalDoneFactor = 0.0
924 self.startTime = time.time()
925 self.sliceStartTime = time.time()
927 self.sizer = wx.GridBagSizer(2, 2)
928 self.statusText = wx.StaticText(self, -1, "Building: %s" % (resultFilename))
929 self.progressGauge = wx.Gauge(self, -1)
930 self.progressGauge.SetRange(10000)
931 self.progressGauge2 = wx.Gauge(self, -1)
932 self.progressGauge2.SetRange(len(self.actionList))
933 self.abortButton = wx.Button(self, -1, "Abort")
934 self.sizer.Add(self.statusText, (0,0), span=(1,5))
935 self.sizer.Add(self.progressGauge, (1, 0), span=(1,5), flag=wx.EXPAND)
936 self.sizer.Add(self.progressGauge2, (2, 0), span=(1,5), flag=wx.EXPAND)
938 self.sizer.Add(self.abortButton, (3,0), span=(1,5), flag=wx.ALIGN_CENTER)
939 self.sizer.AddGrowableCol(0)
940 self.sizer.AddGrowableRow(0)
942 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)
943 self.SetSizer(self.sizer)
947 threading.Thread(target=self.OnRun).start()
949 def OnAbort(self, e):
954 self.abortButton.SetLabel('Close')
956 def SetProgress(self, stepName, layer, maxLayer):
957 if self.prevStep != stepName:
958 self.totalDoneFactor += sliceRun.sliceStepTimeFactor[self.prevStep]
959 newTime = time.time()
960 #print "#####" + str(newTime-self.startTime) + " " + self.prevStep + " -> " + stepName
961 self.startTime = newTime
962 self.prevStep = stepName
964 progresValue = ((self.totalDoneFactor + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000
965 self.progressGauge.SetValue(int(progresValue))
966 self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")
967 taskbar.setProgress(self, 10000 * self.progressGauge2.GetValue() + int(progresValue), 10000 * len(self.actionList))
970 resultFile = open(self.resultFilename, "w")
971 put = profile.setTempOverride
972 self.progressLog = []
973 for action in self.actionList:
974 wx.CallAfter(self.SetTitle, "Building: [%d/%d]" % (self.actionList.index(action) + 1, len(self.actionList)))
975 if not action.usePreviousSlice:
976 p = sliceRun.startSliceCommandProcess(action.sliceCmd)
977 line = p.stdout.readline()
980 while(len(line) > 0):
982 if line[0:9] == "Progress[" and line[-1:] == "]":
983 progress = line[9:-1].split(":")
984 if len(progress) > 2:
985 maxValue = int(progress[2])
986 wx.CallAfter(self.SetProgress, progress[0], int(progress[1]), maxValue)
988 self.progressLog.append(line)
989 wx.CallAfter(self.statusText.SetLabel, line)
992 wx.CallAfter(self.statusText.SetLabel, "Aborted by user.")
995 line = p.stdout.readline()
996 self.returnCode = p.wait()
998 put('object_center_x', action.centerX - self.extruderOffset[action.extruder][0])
999 put('object_center_y', action.centerY - self.extruderOffset[action.extruder][1])
1000 put('clear_z', action.clearZ)
1001 put('extruder', action.extruder)
1002 put('print_temperature', action.temperature)
1004 if action == self.actionList[0]:
1005 resultFile.write(';TYPE:CUSTOM\n')
1006 resultFile.write('T%d\n' % (action.extruder))
1007 currentExtruder = action.extruder
1008 prevTemp = action.temperature
1009 startGCode = profile.getAlterationFileContents('start.gcode')
1010 startGCode = startGCode.replace('?filename?', 'Multiple files')
1011 resultFile.write(startGCode)
1013 #reset the extrusion length, and move to the next object center.
1014 resultFile.write(';TYPE:CUSTOM\n')
1015 if prevTemp != action.temperature and action.temperature > 0:
1016 resultFile.write('M104 S%d\n' % (int(action.temperature)))
1017 prevTemp = action.temperature
1018 resultFile.write(profile.getAlterationFileContents('nextobject.gcode'))
1019 resultFile.write(';PRINTNR:%d\n' % self.actionList.index(action))
1020 profile.resetTempOverride()
1022 if not action.usePreviousSlice:
1023 f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")
1026 resultFile.write(data)
1029 savedCenterX = action.centerX
1030 savedCenterY = action.centerY
1032 f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")
1036 line = self._adjustNumberInLine(line, 'X', action.centerX - savedCenterX)
1038 line = self._adjustNumberInLine(line, 'Y', action.centerY - savedCenterY)
1039 resultFile.write(line)
1042 if not action.leaveResultForNextSlice:
1043 os.remove(sliceRun.getExportFilename(action.filename, "project_tmp"))
1045 wx.CallAfter(self.progressGauge.SetValue, 10000)
1046 self.totalDoneFactor = 0.0
1047 wx.CallAfter(self.progressGauge2.SetValue, self.actionList.index(action) + 1)
1049 resultFile.write(';TYPE:CUSTOM\n')
1050 if len(self.actionList) > 1 and self.actionList[-1].clearZ > 1:
1051 #only move to higher Z if we have sliced more then 1 object. This solves the "move into print after printing" problem with the print-all-at-once option.
1052 resultFile.write('G1 Z%f F%f\n' % (self.actionList[-1].clearZ, profile.getProfileSettingFloat('max_z_speed') * 60))
1053 resultFile.write(profile.getAlterationFileContents('end.gcode'))
1056 gcode = gcodeInterpreter.gcode()
1057 gcode.load(self.resultFilename)
1060 sliceTime = time.time() - self.sliceStartTime
1061 status = "Build: %s" % (self.resultFilename)
1062 status += "\nSlicing took: %02d:%02d" % (sliceTime / 60, sliceTime % 60)
1063 status += "\nFilament: %.2fm %.2fg" % (gcode.extrusionAmount / 1000, gcode.calculateWeight() * 1000)
1064 status += "\nPrint time: %02d:%02d" % (int(gcode.totalMoveTimeMinute / 60), int(gcode.totalMoveTimeMinute % 60))
1065 cost = gcode.calculateCost()
1067 status += "\nCost: %s" % (cost)
1068 profile.replaceGCodeTags(self.resultFilename, gcode)
1069 wx.CallAfter(self.statusText.SetLabel, status)
1070 wx.CallAfter(self.OnSliceDone)
1072 def _adjustNumberInLine(self, line, tag, f):
1073 m = re.search('^(.*'+tag+')([0-9\.]*)(.*)$', line)
1074 return m.group(1) + str(float(m.group(2)) + f) + m.group(3) + '\n'
1076 def OnSliceDone(self):
1077 self.abortButton.Destroy()
1078 self.closeButton = wx.Button(self, -1, "Close")
1079 self.printButton = wx.Button(self, -1, "Print")
1080 self.logButton = wx.Button(self, -1, "Show log")
1081 self.sizer.Add(self.closeButton, (3,0), span=(1,1))
1082 self.sizer.Add(self.printButton, (3,1), span=(1,1))
1083 self.sizer.Add(self.logButton, (3,2), span=(1,1))
1084 if exporer.hasExporer():
1085 self.openFileLocationButton = wx.Button(self, -1, "Open file location")
1086 self.Bind(wx.EVT_BUTTON, self.OnOpenFileLocation, self.openFileLocationButton)
1087 self.sizer.Add(self.openFileLocationButton, (3,3), span=(1,1))
1088 if profile.getPreference('sdpath') != '':
1089 self.copyToSDButton = wx.Button(self, -1, "To SDCard")
1090 self.Bind(wx.EVT_BUTTON, self.OnCopyToSD, self.copyToSDButton)
1091 self.sizer.Add(self.copyToSDButton, (3,4), span=(1,1))
1092 self.Bind(wx.EVT_BUTTON, self.OnAbort, self.closeButton)
1093 self.Bind(wx.EVT_BUTTON, self.OnPrint, self.printButton)
1094 self.Bind(wx.EVT_BUTTON, self.OnShowLog, self.logButton)
1097 taskbar.setBusy(self, False)
1099 def OnCopyToSD(self, e):
1100 filename = os.path.basename(self.resultFilename)
1101 if profile.getPreference('sdshortnames') == 'True':
1102 filename = sliceRun.getShortFilename(filename)
1103 shutil.copy(self.resultFilename, os.path.join(profile.getPreference('sdpath'), filename))
1105 def OnOpenFileLocation(self, e):
1106 exporer.openExporer(self.resultFilename)
1108 def OnPrint(self, e):
1109 printWindow.printFile(self.resultFilename)
1111 def OnShowLog(self, e):
1112 LogWindow('\n'.join(self.progressLog))
1114 class preferencesDialog(wx.Frame):
1115 def __init__(self, parent):
1116 super(preferencesDialog, self).__init__(None, title="Project Planner Preferences", style=wx.DEFAULT_DIALOG_STYLE)
1118 self.parent = parent
1119 wx.EVT_CLOSE(self, self.OnClose)
1121 self.panel = configBase.configPanelBase(self)
1122 extruderAmount = int(profile.getPreference('extruder_amount'))
1124 left, right, main = self.panel.CreateConfigPanel(self)
1125 configBase.TitleRow(left, 'Machine head size')
1126 c = configBase.SettingRow(left, 'Head size - X towards home (mm)', 'extruder_head_size_min_x', '0', 'Size of your printer head in the X direction, on the Ultimaker your fan is in this direction.', type = 'preference')
1127 validators.validFloat(c, 0.1)
1128 c = configBase.SettingRow(left, 'Head size - X towards end (mm)', 'extruder_head_size_max_x', '0', 'Size of your printer head in the X direction.', type = 'preference')
1129 validators.validFloat(c, 0.1)
1130 c = configBase.SettingRow(left, 'Head size - Y towards home (mm)', 'extruder_head_size_min_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')
1131 validators.validFloat(c, 0.1)
1132 c = configBase.SettingRow(left, 'Head size - Y towards end (mm)', 'extruder_head_size_max_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')
1133 validators.validFloat(c, 0.1)
1134 c = configBase.SettingRow(left, 'Head gantry height (mm)', 'extruder_head_size_height', '0', 'The tallest object height that will always fit under your printers gantry system when the printer head is at the lowest Z position.', type = 'preference')
1135 validators.validFloat(c)
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))