1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
18 from Cura.gui import printWindow
19 from Cura.util import profile
20 from Cura.util import meshLoader
21 from Cura.util import objectScene
22 from Cura.util import resources
23 from Cura.util import sliceEngine
24 from Cura.util import machineCom
25 from Cura.util import removableStorage
26 from Cura.util import gcodeInterpreter
27 from Cura.gui.util import previewTools
28 from Cura.gui.util import opengl
29 from Cura.gui.util import openglGui
30 from Cura.gui.tools import youmagineGui
31 from Cura.gui.tools import imageToMesh
33 class SceneView(openglGui.glGuiPanel):
34 def __init__(self, parent):
35 super(SceneView, self).__init__(parent)
40 self._scene = objectScene.Scene()
43 self._gcodeFilename = None
44 self._gcodeLoadThread = None
45 self._objectShader = None
46 self._objectLoadShader = None
48 self._selectedObj = None
49 self._objColors = [None,None,None,None]
52 self._mouseState = None
53 self._viewTarget = numpy.array([0,0,0], numpy.float32)
56 self._platformMesh = {}
57 self._isSimpleMode = True
58 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
61 self._modelMatrix = None
62 self._projMatrix = None
63 self.tempMatrix = None
65 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
66 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
67 self.printButton.setDisabled(True)
70 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
71 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
72 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
74 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
75 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
77 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
78 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
80 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
81 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
82 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
84 self.rotateToolButton.setExpandArrow(True)
85 self.scaleToolButton.setExpandArrow(True)
86 self.mirrorToolButton.setExpandArrow(True)
88 self.scaleForm = openglGui.glFrame(self, (2, -2))
89 openglGui.glGuiLayoutGrid(self.scaleForm)
90 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
91 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
92 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
93 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
94 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
95 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
96 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
97 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
98 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
99 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
100 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
101 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
102 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
103 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
105 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
106 self.layerSelect = openglGui.glSlider(self, 10000, 0, 1, (-1,-2), lambda : self.QueueRefresh())
108 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
109 self.youMagineButton.setDisabled(True)
111 self.notification = openglGui.glNotification(self, (0, 0))
113 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
114 self._sceneUpdateTimer = wx.Timer(self)
115 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
116 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
117 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
121 self.updateToolButtons()
122 self.updateProfileToControls()
124 def loadGCodeFile(self, filename):
125 self.OnDeleteAll(None)
126 if self._gcode is not None:
128 for layerVBOlist in self._gcodeVBOs:
129 for vbo in layerVBOlist:
130 self.glReleaseList.append(vbo)
132 self._gcode = gcodeInterpreter.gcode()
133 self._gcodeFilename = filename
134 self.printButton.setBottomText('')
135 self.viewSelection.setValue(4)
136 self.printButton.setDisabled(False)
137 self.youMagineButton.setDisabled(True)
140 def loadSceneFiles(self, filenames):
141 self.youMagineButton.setDisabled(False)
142 #if self.viewSelection.getValue() == 4:
143 # self.viewSelection.setValue(0)
144 # self.OnViewChange()
145 self.loadScene(filenames)
147 def loadFiles(self, filenames):
148 mainWindow = self.GetParent().GetParent().GetParent()
149 # only one GCODE file can be active
150 # so if single gcode file, process this
151 # otherwise ignore all gcode files
153 if len(filenames) == 1:
154 filename = filenames[0]
155 ext = os.path.splitext(filename)[1].lower()
156 if ext == '.g' or ext == '.gcode':
157 gcodeFilename = filename
158 mainWindow.addToModelMRU(filename)
159 if gcodeFilename is not None:
160 self.loadGCodeFile(gcodeFilename)
162 # process directories and special file types
163 # and keep scene files for later processing
165 ignored_types = dict()
166 # use file list as queue
167 # pop first entry for processing and append new files at end
169 filename = filenames.pop(0)
170 if os.path.isdir(filename):
171 # directory: queue all included files and directories
172 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
174 ext = os.path.splitext(filename)[1].lower()
176 profile.loadProfile(filename)
177 mainWindow.addToProfileMRU(filename)
178 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
179 scene_filenames.append(filename)
180 mainWindow.addToModelMRU(filename)
182 ignored_types[ext] = 1
184 ignored_types = ignored_types.keys()
186 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
187 mainWindow.updateProfileToAllControls()
188 # now process all the scene files
190 self.loadSceneFiles(scene_filenames)
191 self._selectObject(None)
193 newZoom = numpy.max(self._machineSize)
194 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
195 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
197 def showLoadModel(self, button = 1):
199 dlg=wx.FileDialog(self, _("Open 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
200 dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
201 if dlg.ShowModal() != wx.ID_OK:
204 filenames = dlg.GetPaths()
206 if len(filenames) < 1:
208 profile.putPreference('lastFile', filenames[0])
209 self.loadFiles(filenames)
211 def showSaveModel(self):
212 if len(self._scene.objects()) < 1:
214 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
215 dlg.SetWildcard(meshLoader.saveWildcardFilter())
216 if dlg.ShowModal() != wx.ID_OK:
219 filename = dlg.GetPath()
221 meshLoader.saveMeshes(filename, self._scene.objects())
223 def OnPrintButton(self, button):
225 if machineCom.machineIsConnected():
226 self.showPrintWindow()
227 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
228 drives = removableStorage.getPossibleSDcardDrives()
230 dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
231 if dlg.ShowModal() != wx.ID_OK:
234 drive = drives[dlg.GetSelection()]
238 filename = self._scene._objectList[0].getName() + '.gcode'
239 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
244 self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
245 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
246 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
250 def showPrintWindow(self):
251 if self._gcodeFilename is None:
253 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
254 wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
256 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
257 if self._gcodeFilename == self._slicer.getGCodeFilename():
258 self._slicer.submitSliceInfoOnline()
260 def showSaveGCode(self):
261 if len(self._scene._objectList) < 1:
263 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
264 filename = self._scene._objectList[0].getName() + '.gcode'
265 dlg.SetFilename(filename)
266 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
267 if dlg.ShowModal() != wx.ID_OK:
270 filename = dlg.GetPath()
273 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
275 def _copyFile(self, fileA, fileB, allowEject = False):
277 size = float(os.stat(fileA).st_size)
278 with open(fileA, 'rb') as fsrc:
279 with open(fileB, 'wb') as fdst:
281 buf = fsrc.read(16*1024)
285 self.printButton.setProgressBar(float(fsrc.tell()) / size)
290 self.notification.message("Failed to save")
293 self.notification.message("Saved as %s" % (fileB), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(allowEject) else self.notification.message('Safe remove failed...'))
295 self.notification.message("Saved as %s" % (fileB))
296 self.printButton.setProgressBar(None)
297 if fileA == self._slicer.getGCodeFilename():
298 self._slicer.submitSliceInfoOnline()
300 def _showSliceLog(self):
301 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
305 def OnToolSelect(self, button):
306 if self.rotateToolButton.getSelected():
307 self.tool = previewTools.toolRotate(self)
308 elif self.scaleToolButton.getSelected():
309 self.tool = previewTools.toolScale(self)
310 elif self.mirrorToolButton.getSelected():
311 self.tool = previewTools.toolNone(self)
313 self.tool = previewTools.toolNone(self)
314 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
315 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
316 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
317 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
318 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
319 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
320 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
321 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
323 def updateToolButtons(self):
324 if self._selectedObj is None:
328 self.rotateToolButton.setHidden(hidden)
329 self.scaleToolButton.setHidden(hidden)
330 self.mirrorToolButton.setHidden(hidden)
332 self.rotateToolButton.setSelected(False)
333 self.scaleToolButton.setSelected(False)
334 self.mirrorToolButton.setSelected(False)
337 def OnViewChange(self):
338 if self.viewSelection.getValue() == 4:
339 self.viewMode = 'gcode'
340 if self._gcode is not None and self._gcode.layerList is not None:
341 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
342 self._selectObject(None)
343 elif self.viewSelection.getValue() == 1:
344 self.viewMode = 'overhang'
345 elif self.viewSelection.getValue() == 2:
346 self.viewMode = 'transparent'
347 elif self.viewSelection.getValue() == 3:
348 self.viewMode = 'xray'
350 self.viewMode = 'normal'
351 self.layerSelect.setHidden(self.viewMode != 'gcode')
354 def OnRotateReset(self, button):
355 if self._selectedObj is None:
357 self._selectedObj.resetRotation()
358 self._scene.pushFree()
359 self._selectObject(self._selectedObj)
362 def OnLayFlat(self, button):
363 if self._selectedObj is None:
365 self._selectedObj.layFlat()
366 self._scene.pushFree()
367 self._selectObject(self._selectedObj)
370 def OnScaleReset(self, button):
371 if self._selectedObj is None:
373 self._selectedObj.resetScale()
374 self._selectObject(self._selectedObj)
375 self.updateProfileToControls()
378 def OnScaleMax(self, button):
379 if self._selectedObj is None:
381 machine = profile.getMachineSetting('machine_type')
382 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
383 self._scene.pushFree()
385 if machine == "ultimaker2":
386 #This is bad and Jaime should feel bad!
387 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
388 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
389 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
390 self._scene.pushFree()
392 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
393 self._scene.pushFree()
394 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
395 self._scene.pushFree()
396 self._selectObject(self._selectedObj)
397 self.updateProfileToControls()
400 def OnMirror(self, axis):
401 if self._selectedObj is None:
403 self._selectedObj.mirror(axis)
406 def OnScaleEntry(self, value, axis):
407 if self._selectedObj is None:
413 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
414 self.updateProfileToControls()
415 self._scene.pushFree()
416 self._selectObject(self._selectedObj)
419 def OnScaleEntryMM(self, value, axis):
420 if self._selectedObj is None:
426 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
427 self.updateProfileToControls()
428 self._scene.pushFree()
429 self._selectObject(self._selectedObj)
432 def OnDeleteAll(self, e):
433 while len(self._scene.objects()) > 0:
434 self._deleteObject(self._scene.objects()[0])
435 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
437 def OnMultiply(self, e):
438 if self._focusObj is None:
441 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
442 if dlg.ShowModal() != wx.ID_OK:
451 self._scene.add(newObj)
452 self._scene.centerAll()
453 if not self._scene.checkPlatform(newObj):
458 self.notification.message("Could not create more then %d items" % (n - 1))
459 self._scene.remove(newObj)
460 self._scene.centerAll()
463 def OnSplitObject(self, e):
464 if self._focusObj is None:
466 self._scene.remove(self._focusObj)
467 for obj in self._focusObj.split(self._splitCallback):
468 if numpy.max(obj.getSize()) > 2.0:
470 self._scene.centerAll()
471 self._selectObject(None)
474 def OnCenter(self, e):
475 if self._focusObj is None:
477 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
478 self._scene.pushFree()
479 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
480 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
483 def _splitCallback(self, progress):
486 def OnMergeObjects(self, e):
487 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
488 if len(self._scene.objects()) == 2:
489 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
492 self._scene.merge(self._selectedObj, self._focusObj)
495 def sceneUpdated(self):
496 self._sceneUpdateTimer.Start(500, True)
497 self._slicer.abortSlicer()
498 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
501 def _onRunSlicer(self, e):
502 if self._isSimpleMode:
503 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
504 self._slicer.runSlicer(self._scene)
505 if self._isSimpleMode:
506 profile.resetTempOverride()
508 def _updateSliceProgress(self, progressValue, ready):
510 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
512 self.printButton.setDisabled(not ready)
513 if progressValue >= 0.0:
514 self.printButton.setProgressBar(progressValue)
516 self.printButton.setProgressBar(None)
517 if self._gcode is not None:
519 for layerVBOlist in self._gcodeVBOs:
520 for vbo in layerVBOlist:
521 self.glReleaseList.append(vbo)
524 self.printButton.setProgressBar(None)
525 text = '%s' % (self._slicer.getPrintTime())
526 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
527 amount = self._slicer.getFilamentAmount(e)
530 text += '\n%s' % (amount)
531 cost = self._slicer.getFilamentCost(e)
533 text += '\n%s' % (cost)
534 self.printButton.setBottomText(text)
535 self._gcode = gcodeInterpreter.gcode()
536 self._gcodeFilename = self._slicer.getGCodeFilename()
538 self.printButton.setBottomText('')
541 def _loadGCode(self):
542 self._gcode.progressCallback = self._gcodeLoadCallback
543 self._gcode.load(self._gcodeFilename)
545 def _gcodeLoadCallback(self, progress):
546 if not self or self._gcode is None:
548 if len(self._gcode.layerList) % 15 == 0:
550 if self._gcode is None:
552 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
553 if self.viewMode == 'gcode':
557 def loadScene(self, fileList):
558 for filename in fileList:
560 ext = os.path.splitext(filename)[1].lower()
561 if ext in imageToMesh.supportedExtensions():
562 imageToMesh.convertImageDialog(self, filename).Show()
565 objList = meshLoader.loadMeshes(filename)
567 traceback.print_exc()
570 if self._objectLoadShader is not None:
571 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
575 self._scene.centerAll()
576 self._selectObject(obj)
577 if obj.getScale()[0] < 1.0:
578 self.notification.message("Warning: Object scaled down.")
581 def _deleteObject(self, obj):
582 if obj == self._selectedObj:
583 self._selectObject(None)
584 if obj == self._focusObj:
585 self._focusObj = None
586 self._scene.remove(obj)
587 for m in obj._meshList:
588 if m.vbo is not None and m.vbo.decRef():
589 self.glReleaseList.append(m.vbo)
594 def _selectObject(self, obj, zoom = True):
595 if obj != self._selectedObj:
596 self._selectedObj = obj
597 self.updateProfileToControls()
598 self.updateToolButtons()
599 if zoom and obj is not None:
600 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
601 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
602 newZoom = obj.getBoundaryCircle() * 6
603 if newZoom > numpy.max(self._machineSize) * 3:
604 newZoom = numpy.max(self._machineSize) * 3
605 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
607 def updateProfileToControls(self):
608 oldSimpleMode = self._isSimpleMode
609 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
610 if self._isSimpleMode != oldSimpleMode:
611 self._scene.arrangeAll()
613 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
614 self._objColors[0] = profile.getPreferenceColour('model_colour')
615 self._objColors[1] = profile.getPreferenceColour('model_colour2')
616 self._objColors[2] = profile.getPreferenceColour('model_colour3')
617 self._objColors[3] = profile.getPreferenceColour('model_colour4')
618 self._scene.setMachineSize(self._machineSize)
619 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
620 self._scene.setHeadSize(profile.getMachineSettingFloat('extruder_head_size_min_x'), profile.getMachineSettingFloat('extruder_head_size_max_x'), profile.getMachineSettingFloat('extruder_head_size_min_y'), profile.getMachineSettingFloat('extruder_head_size_max_y'), profile.getMachineSettingFloat('extruder_head_size_height'))
621 for n in xrange(1, 4):
622 self._scene.setExtruderOffset(n, profile.getMachineSettingFloat('extruder_offset_x%d' % (n)), profile.getMachineSettingFloat('extruder_offset_y%d' % (n)))
624 if self._selectedObj is not None:
625 scale = self._selectedObj.getScale()
626 size = self._selectedObj.getSize()
627 self.scaleXctrl.setValue(round(scale[0], 2))
628 self.scaleYctrl.setValue(round(scale[1], 2))
629 self.scaleZctrl.setValue(round(scale[2], 2))
630 self.scaleXmmctrl.setValue(round(size[0], 2))
631 self.scaleYmmctrl.setValue(round(size[1], 2))
632 self.scaleZmmctrl.setValue(round(size[2], 2))
634 def OnKeyChar(self, keyCode):
635 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
636 if self._selectedObj is not None:
637 self._deleteObject(self._selectedObj)
639 if keyCode == wx.WXK_UP:
640 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
642 elif keyCode == wx.WXK_DOWN:
643 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
645 elif keyCode == wx.WXK_PAGEUP:
646 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
648 elif keyCode == wx.WXK_PAGEDOWN:
649 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
652 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
653 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
654 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
655 from collections import defaultdict
656 from gc import get_objects
657 self._beforeLeakTest = defaultdict(int)
658 for i in get_objects():
659 self._beforeLeakTest[type(i)] += 1
660 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
661 from collections import defaultdict
662 from gc import get_objects
663 self._afterLeakTest = defaultdict(int)
664 for i in get_objects():
665 self._afterLeakTest[type(i)] += 1
666 for k in self._afterLeakTest:
667 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
668 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
670 def ShaderUpdate(self, v, f):
671 s = opengl.GLShader(v, f)
673 self._objectLoadShader.release()
674 self._objectLoadShader = s
675 for obj in self._scene.objects():
676 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
679 def OnMouseDown(self,e):
680 self._mouseX = e.GetX()
681 self._mouseY = e.GetY()
682 self._mouseClick3DPos = self._mouse3Dpos
683 self._mouseClickFocus = self._focusObj
685 self._mouseState = 'doubleClick'
687 self._mouseState = 'dragOrClick'
688 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
689 p0 -= self.getObjectCenterPos() - self._viewTarget
690 p1 -= self.getObjectCenterPos() - self._viewTarget
691 if self.tool.OnDragStart(p0, p1):
692 self._mouseState = 'tool'
693 if self._mouseState == 'dragOrClick':
694 if e.GetButton() == 1:
695 if self._focusObj is not None:
696 self._selectObject(self._focusObj, False)
699 def OnMouseUp(self, e):
700 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
702 if self._mouseState == 'dragOrClick':
703 if e.GetButton() == 1:
704 self._selectObject(self._focusObj)
705 if e.GetButton() == 3:
707 if self._focusObj is not None:
708 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
709 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
710 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
711 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
712 if ((self._selectedObj != self._focusObj and self._focusObj is not None and self._selectedObj is not None) or len(self._scene.objects()) == 2) and int(profile.getMachineSetting('extruder_amount')) > 1:
713 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
714 if len(self._scene.objects()) > 0:
715 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
716 if menu.MenuItemCount > 0:
719 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
720 self._scene.pushFree()
722 elif self._mouseState == 'tool':
723 if self.tempMatrix is not None and self._selectedObj is not None:
724 self._selectedObj.applyMatrix(self.tempMatrix)
725 self._scene.pushFree()
726 self._selectObject(self._selectedObj)
727 self.tempMatrix = None
728 self.tool.OnDragEnd()
730 self._mouseState = None
732 def OnMouseMotion(self,e):
733 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
734 p0 -= self.getObjectCenterPos() - self._viewTarget
735 p1 -= self.getObjectCenterPos() - self._viewTarget
737 if e.Dragging() and self._mouseState is not None:
738 if self._mouseState == 'tool':
739 self.tool.OnDrag(p0, p1)
740 elif not e.LeftIsDown() and e.RightIsDown():
741 self._mouseState = 'drag'
742 if wx.GetKeyState(wx.WXK_SHIFT):
743 a = math.cos(math.radians(self._yaw)) / 3.0
744 b = math.sin(math.radians(self._yaw)) / 3.0
745 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
746 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
747 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
748 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
750 self._yaw += e.GetX() - self._mouseX
751 self._pitch -= e.GetY() - self._mouseY
752 if self._pitch > 170:
756 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
757 self._mouseState = 'drag'
758 self._zoom += e.GetY() - self._mouseY
761 if self._zoom > numpy.max(self._machineSize) * 3:
762 self._zoom = numpy.max(self._machineSize) * 3
763 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
764 self._mouseState = 'dragObject'
765 z = max(0, self._mouseClick3DPos[2])
766 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
767 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
772 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
773 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
774 diff = cursorZ1 - cursorZ0
775 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
776 if not e.Dragging() or self._mouseState != 'tool':
777 self.tool.OnMouseMove(p0, p1)
779 self._mouseX = e.GetX()
780 self._mouseY = e.GetY()
782 def OnMouseWheel(self, e):
783 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
784 delta = max(min(delta,4),-4)
785 self._zoom *= 1.0 - delta / 10.0
788 if self._zoom > numpy.max(self._machineSize) * 3:
789 self._zoom = numpy.max(self._machineSize) * 3
792 def OnMouseLeave(self, e):
796 def getMouseRay(self, x, y):
797 if self._viewport is None:
798 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
799 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
800 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
801 p0 -= self._viewTarget
802 p1 -= self._viewTarget
805 def _init3DView(self):
806 # set viewing projection
807 size = self.GetSize()
808 glViewport(0, 0, size.GetWidth(), size.GetHeight())
811 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
813 glDisable(GL_RESCALE_NORMAL)
814 glDisable(GL_LIGHTING)
816 glEnable(GL_DEPTH_TEST)
817 glDisable(GL_CULL_FACE)
819 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
821 glClearColor(0.8, 0.8, 0.8, 1.0)
825 glMatrixMode(GL_PROJECTION)
827 aspect = float(size.GetWidth()) / float(size.GetHeight())
828 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
830 glMatrixMode(GL_MODELVIEW)
832 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
835 if machineCom.machineIsConnected():
836 self.printButton._imageID = 6
837 self.printButton._tooltip = _("Print")
838 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
839 self.printButton._imageID = 2
840 self.printButton._tooltip = _("Toolpath to SD")
842 self.printButton._imageID = 3
843 self.printButton._tooltip = _("Save toolpath")
845 if self._animView is not None:
846 self._viewTarget = self._animView.getPosition()
847 if self._animView.isDone():
848 self._animView = None
849 if self._animZoom is not None:
850 self._zoom = self._animZoom.getPosition()
851 if self._animZoom.isDone():
852 self._animZoom = None
853 if self.viewMode == 'gcode' and self._gcode is not None:
855 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
858 if self._objectShader is None:
859 if opengl.hasShaderSupport():
860 self._objectShader = opengl.GLShader("""
861 varying float light_amount;
865 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
866 gl_FrontColor = gl_Color;
868 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
872 varying float light_amount;
876 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
879 self._objectOverhangShader = opengl.GLShader("""
880 uniform float cosAngle;
881 uniform mat3 rotMatrix;
882 varying float light_amount;
886 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
887 gl_FrontColor = gl_Color;
889 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
891 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
893 light_amount = -10.0;
897 varying float light_amount;
901 if (light_amount == -10.0)
903 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
905 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
909 self._objectLoadShader = opengl.GLShader("""
910 uniform float intensity;
912 varying float light_amount;
916 vec4 tmp = gl_Vertex;
917 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
918 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
919 gl_Position = gl_ModelViewProjectionMatrix * tmp;
920 gl_FrontColor = gl_Color;
922 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
926 uniform float intensity;
927 varying float light_amount;
931 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
934 if self._objectShader is None or not self._objectShader.isValid():
935 self._objectShader = opengl.GLFakeShader()
936 self._objectOverhangShader = opengl.GLFakeShader()
937 self._objectLoadShader = None
939 glTranslate(0,0,-self._zoom)
940 glRotate(-self._pitch, 1,0,0)
941 glRotate(self._yaw, 0,0,1)
942 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
944 self._viewport = glGetIntegerv(GL_VIEWPORT)
945 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
946 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
948 glClearColor(1,1,1,1)
949 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
951 if self.viewMode != 'gcode':
952 for n in xrange(0, len(self._scene.objects())):
953 obj = self._scene.objects()[n]
954 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
955 self._renderObject(obj)
957 if self._mouseX > -1:
959 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
960 if n < len(self._scene.objects()):
961 self._focusObj = self._scene.objects()[n]
963 self._focusObj = None
964 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
965 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
966 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
967 self._mouse3Dpos -= self._viewTarget
970 glTranslate(0,0,-self._zoom)
971 glRotate(-self._pitch, 1,0,0)
972 glRotate(self._yaw, 0,0,1)
973 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
975 if self.viewMode == 'gcode':
976 if self._gcode is not None and self._gcode.layerList is None:
977 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
978 self._gcodeLoadThread.daemon = True
979 self._gcodeLoadThread.start()
980 if self._gcode is not None and self._gcode.layerList is not None:
982 if profile.getMachineSetting('machine_center_is_zero') != 'True':
983 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
985 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
986 for n in xrange(0, drawUpTill):
987 c = 1.0 - float(drawUpTill - n) / 15
989 if len(self._gcodeVBOs) < n + 1:
990 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
991 if time.time() - t > 0.5:
994 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
995 if n == drawUpTill - 1:
996 if len(self._gcodeVBOs[n]) < 9:
997 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
999 self._gcodeVBOs[n][8].render(GL_QUADS)
1000 glColor3f(c/2, 0, c)
1001 self._gcodeVBOs[n][9].render(GL_QUADS)
1002 glColor3f(0, c, c/2)
1003 self._gcodeVBOs[n][10].render(GL_QUADS)
1005 self._gcodeVBOs[n][11].render(GL_QUADS)
1008 self._gcodeVBOs[n][12].render(GL_QUADS)
1009 glColor3f(c/2, c/2, 0.0)
1010 self._gcodeVBOs[n][13].render(GL_QUADS)
1012 self._gcodeVBOs[n][14].render(GL_QUADS)
1013 self._gcodeVBOs[n][15].render(GL_QUADS)
1015 self._gcodeVBOs[n][16].render(GL_LINES)
1018 self._gcodeVBOs[n][0].render(GL_LINES)
1019 glColor3f(c/2, 0, c)
1020 self._gcodeVBOs[n][1].render(GL_LINES)
1021 glColor3f(0, c, c/2)
1022 self._gcodeVBOs[n][2].render(GL_LINES)
1024 self._gcodeVBOs[n][3].render(GL_LINES)
1027 self._gcodeVBOs[n][4].render(GL_LINES)
1028 glColor3f(c/2, c/2, 0.0)
1029 self._gcodeVBOs[n][5].render(GL_LINES)
1031 self._gcodeVBOs[n][6].render(GL_LINES)
1032 self._gcodeVBOs[n][7].render(GL_LINES)
1035 glStencilFunc(GL_ALWAYS, 1, 1)
1036 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1038 if self.viewMode == 'overhang':
1039 self._objectOverhangShader.bind()
1040 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1042 self._objectShader.bind()
1043 for obj in self._scene.objects():
1044 if obj._loadAnim is not None:
1045 if obj._loadAnim.isDone():
1046 obj._loadAnim = None
1050 if self._focusObj == obj:
1052 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1055 if self._selectedObj == obj or self._selectedObj is None:
1056 #If we want transparent, then first render a solid black model to remove the printer size lines.
1057 if self.viewMode == 'transparent':
1058 glColor4f(0, 0, 0, 0)
1059 self._renderObject(obj)
1061 glBlendFunc(GL_ONE, GL_ONE)
1062 glDisable(GL_DEPTH_TEST)
1064 if self.viewMode == 'xray':
1065 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1066 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1067 glEnable(GL_STENCIL_TEST)
1069 if self.viewMode == 'overhang':
1070 if self._selectedObj == obj and self.tempMatrix is not None:
1071 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1073 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1075 if not self._scene.checkPlatform(obj):
1076 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1077 self._renderObject(obj)
1079 self._renderObject(obj, brightness)
1080 glDisable(GL_STENCIL_TEST)
1082 glEnable(GL_DEPTH_TEST)
1083 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1085 if self.viewMode == 'xray':
1088 glEnable(GL_STENCIL_TEST)
1089 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1090 glDisable(GL_DEPTH_TEST)
1091 for i in xrange(2, 15, 2):
1092 glStencilFunc(GL_EQUAL, i, 0xFF)
1093 glColor(float(i)/10, float(i)/10, float(i)/5)
1095 glVertex3f(-1000,-1000,-10)
1096 glVertex3f( 1000,-1000,-10)
1097 glVertex3f( 1000, 1000,-10)
1098 glVertex3f(-1000, 1000,-10)
1100 for i in xrange(1, 15, 2):
1101 glStencilFunc(GL_EQUAL, i, 0xFF)
1102 glColor(float(i)/10, 0, 0)
1104 glVertex3f(-1000,-1000,-10)
1105 glVertex3f( 1000,-1000,-10)
1106 glVertex3f( 1000, 1000,-10)
1107 glVertex3f(-1000, 1000,-10)
1110 glDisable(GL_STENCIL_TEST)
1111 glEnable(GL_DEPTH_TEST)
1113 self._objectShader.unbind()
1115 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1117 if self._objectLoadShader is not None:
1118 self._objectLoadShader.bind()
1119 glColor4f(0.2, 0.6, 1.0, 1.0)
1120 for obj in self._scene.objects():
1121 if obj._loadAnim is None:
1123 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1124 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1125 self._renderObject(obj)
1126 self._objectLoadShader.unbind()
1131 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1133 z = self._usbPrintMonitor.getZ()
1134 size = self._machineSize
1135 glColor4ub(255,255,0,128)
1137 glVertex3f(-size[0]/2,-size[1]/2, z)
1138 glVertex3f( size[0]/2,-size[1]/2, z)
1139 glVertex3f( size[0]/2, size[1]/2, z)
1140 glVertex3f(-size[0]/2, size[1]/2, z)
1143 if self.viewMode == 'gcode':
1144 if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1145 glDisable(GL_DEPTH_TEST)
1148 glTranslate(0,-4,-10)
1149 glColor4ub(60,60,60,255)
1150 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1153 #Draw the object box-shadow, so you can see where it will collide with other objects.
1154 if self._selectedObj is not None and len(self._scene.objects()) > 1:
1155 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
1157 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1159 glEnable(GL_CULL_FACE)
1160 glColor4f(0,0,0,0.12)
1162 glVertex3f(-size[0], size[1], 0.1)
1163 glVertex3f(-size[0], -size[1], 0.1)
1164 glVertex3f( size[0], -size[1], 0.1)
1165 glVertex3f( size[0], size[1], 0.1)
1167 glDisable(GL_CULL_FACE)
1170 #Draw the outline of the selected object, on top of everything else except the GUI.
1171 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1172 glDisable(GL_DEPTH_TEST)
1173 glEnable(GL_CULL_FACE)
1174 glEnable(GL_STENCIL_TEST)
1176 glStencilFunc(GL_EQUAL, 0, 255)
1178 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1180 glColor4f(1,1,1,0.5)
1181 self._renderObject(self._selectedObj)
1182 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1184 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1185 glDisable(GL_STENCIL_TEST)
1186 glDisable(GL_CULL_FACE)
1187 glEnable(GL_DEPTH_TEST)
1189 if self._selectedObj is not None:
1191 pos = self.getObjectCenterPos()
1192 glTranslate(pos[0], pos[1], pos[2])
1195 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1196 glDisable(GL_DEPTH_TEST)
1199 glTranslate(0,-4,-10)
1200 glColor4ub(60,60,60,255)
1201 opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1204 def _renderObject(self, obj, brightness = False, addSink = True):
1207 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1209 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1211 if self.tempMatrix is not None and obj == self._selectedObj:
1212 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1213 glMultMatrixf(tempMatrix)
1215 offset = obj.getDrawOffset()
1216 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1218 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1219 glMultMatrixf(tempMatrix)
1222 for m in obj._meshList:
1224 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1226 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1231 def _drawMachine(self):
1232 glEnable(GL_CULL_FACE)
1235 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1237 machine = profile.getMachineSetting('machine_type')
1238 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
1239 if machine not in self._platformMesh:
1240 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1242 self._platformMesh[machine] = meshes[0]
1244 self._platformMesh[machine] = None
1245 if machine == 'ultimaker2':
1246 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1248 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1249 glColor4f(1,1,1,0.5)
1250 self._objectShader.bind()
1251 self._renderObject(self._platformMesh[machine], False, False)
1252 self._objectShader.unbind()
1254 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1255 if machine == 'ultimaker2':
1256 if not hasattr(self._platformMesh[machine], 'texture'):
1257 self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1258 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1259 glEnable(GL_TEXTURE_2D)
1263 glTranslate(0,150,-5)
1268 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1271 glVertex3f( w, 0, h)
1273 glVertex3f(-w, 0, h)
1275 glVertex3f(-w, 0, 0)
1277 glVertex3f( w, 0, 0)
1280 glVertex3f(-w, d, h)
1282 glVertex3f( w, d, h)
1284 glVertex3f( w, d, 0)
1286 glVertex3f(-w, d, 0)
1288 glDisable(GL_TEXTURE_2D)
1289 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1295 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1296 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1297 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1298 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1299 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1300 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1303 #Cornerpoints for big blue square
1304 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1305 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1306 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1307 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1308 v4 = [ size[0] / 2, size[1] / 2, 0]
1309 v5 = [ size[0] / 2,-size[1] / 2, 0]
1310 v6 = [-size[0] / 2, size[1] / 2, 0]
1311 v7 = [-size[0] / 2,-size[1] / 2, 0]
1313 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1314 glEnableClientState(GL_VERTEX_ARRAY)
1315 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1317 glColor4ub(5, 171, 231, 64)
1318 glDrawArrays(GL_QUADS, 0, 4)
1319 glColor4ub(5, 171, 231, 96)
1320 glDrawArrays(GL_QUADS, 4, 8)
1321 glColor4ub(5, 171, 231, 128)
1322 glDrawArrays(GL_QUADS, 12, 8)
1323 glDisableClientState(GL_VERTEX_ARRAY)
1326 sx = self._machineSize[0]
1327 sy = self._machineSize[1]
1328 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1329 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1334 x1 = max(min(x1, sx/2), -sx/2)
1335 y1 = max(min(y1, sy/2), -sy/2)
1336 x2 = max(min(x2, sx/2), -sx/2)
1337 y2 = max(min(y2, sy/2), -sy/2)
1338 #Black or "white" checker
1339 if (x & 1) == (y & 1):
1340 glColor4ub(5, 171, 231, 127)
1342 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1344 glVertex3f(x1, y1, -0.02) #Draw bit below z0 to prevent zfighting.
1345 glVertex3f(x2, y1, -0.02)
1346 glVertex3f(x2, y2, -0.02)
1347 glVertex3f(x1, y2, -0.02)
1350 if machine == 'ultimaker2':
1352 glColor4ub(127, 127, 127, 200)
1353 #if UM2, draw bat-area zone for head. THe head can't stop there, because its bat-area.
1357 posX = sx / 2 - clipWidth
1358 posY = sy / 2 - clipHeight
1360 glVertex3f(posX, posY, 0.1)
1361 glVertex3f(posX+clipWidth, posY, 0.1)
1362 glVertex3f(posX+clipWidth, posY+clipHeight, 0.1)
1363 glVertex3f(posX, posY+clipHeight, 0.1)
1369 posY = sy / 2 - clipHeight
1371 glVertex3f(posX, posY, 0.1)
1372 glVertex3f(posX+clipWidth, posY, 0.1)
1373 glVertex3f(posX+clipWidth, posY+clipHeight, 0.1)
1374 glVertex3f(posX, posY+clipHeight, 0.1)
1379 posX = sx / 2 - clipWidth
1382 glVertex3f(posX, posY, 0.1)
1383 glVertex3f(posX+clipWidth, posY, 0.1)
1384 glVertex3f(posX+clipWidth, posY+clipHeight, 0.1)
1385 glVertex3f(posX, posY+clipHeight, 0.1)
1393 glVertex3f(posX, posY, 0.1)
1394 glVertex3f(posX+clipWidth, posY, 0.1)
1395 glVertex3f(posX+clipWidth, posY+clipHeight, 0.1)
1396 glVertex3f(posX, posY+clipHeight, 0.1)
1400 glDisable(GL_CULL_FACE)
1402 def _generateGCodeVBOs(self, layer):
1404 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1405 if ':' in extrudeType:
1406 extruder = int(extrudeType[extrudeType.find(':')+1:])
1407 extrudeType = extrudeType[0:extrudeType.find(':')]
1410 pointList = numpy.zeros((0,3), numpy.float32)
1412 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1414 a = numpy.concatenate((a[:-1], a[1:]), 1)
1415 a = a.reshape((len(a) * 2, 3))
1416 pointList = numpy.concatenate((pointList, a))
1417 ret.append(opengl.GLVBO(pointList))
1420 def _generateGCodeVBOs2(self, layer):
1421 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1422 filamentArea = math.pi * filamentRadius * filamentRadius
1423 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1426 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1427 if ':' in extrudeType:
1428 extruder = int(extrudeType[extrudeType.find(':')+1:])
1429 extrudeType = extrudeType[0:extrudeType.find(':')]
1432 pointList = numpy.zeros((0,3), numpy.float32)
1434 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1436 if extrudeType == 'FILL':
1439 normal = a[1:] - a[:-1]
1440 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1441 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1444 ePerDist = path['extrusion'][1:] / lens
1446 lineWidth = ePerDist / path['layerThickness'] / 2.0
1448 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1450 normal[:,0] *= lineWidth
1451 normal[:,1] *= lineWidth
1453 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1454 b = numpy.concatenate((b, a[1:] + normal), 1)
1455 b = numpy.concatenate((b, a[1:] - normal), 1)
1456 b = numpy.concatenate((b, a[:-1] - normal), 1)
1457 b = numpy.concatenate((b, a[:-1] + normal), 1)
1458 b = b.reshape((len(b) * 4, 3))
1461 normal2 = normal[:-1] + normal[1:]
1462 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1463 normal2[:,0] /= lens2
1464 normal2[:,1] /= lens2
1465 normal2[:,0] *= lineWidth[:-1]
1466 normal2[:,1] *= lineWidth[:-1]
1468 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1469 c = numpy.concatenate((c, a[1:-1]), 1)
1470 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1471 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1472 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1474 c = numpy.concatenate((c, a[1:-1]), 1)
1475 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1476 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1477 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1479 c = c.reshape((len(c) * 8, 3))
1481 pointList = numpy.concatenate((pointList, b, c))
1483 pointList = numpy.concatenate((pointList, b))
1484 ret.append(opengl.GLVBO(pointList))
1486 pointList = numpy.zeros((0,3), numpy.float32)
1488 if path['type'] == 'move':
1489 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1490 a = numpy.concatenate((a[:-1], a[1:]), 1)
1491 a = a.reshape((len(a) * 2, 3))
1492 pointList = numpy.concatenate((pointList, a))
1493 if path['type'] == 'retract':
1494 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1495 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1496 a = a.reshape((len(a) * 2, 3))
1497 pointList = numpy.concatenate((pointList, a))
1498 ret.append(opengl.GLVBO(pointList))
1502 def getObjectCenterPos(self):
1503 if self._selectedObj is None:
1504 return [0.0, 0.0, 0.0]
1505 pos = self._selectedObj.getPosition()
1506 size = self._selectedObj.getSize()
1507 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1509 def getObjectBoundaryCircle(self):
1510 if self._selectedObj is None:
1512 return self._selectedObj.getBoundaryCircle()
1514 def getObjectSize(self):
1515 if self._selectedObj is None:
1516 return [0.0, 0.0, 0.0]
1517 return self._selectedObj.getSize()
1519 def getObjectMatrix(self):
1520 if self._selectedObj is None:
1521 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1522 return self._selectedObj.getMatrix()
1524 class shaderEditor(wx.Dialog):
1525 def __init__(self, parent, callback, v, f):
1526 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1527 self._callback = callback
1528 s = wx.BoxSizer(wx.VERTICAL)
1530 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1531 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1532 s.Add(self._vertex, 1, flag=wx.EXPAND)
1533 s.Add(self._fragment, 1, flag=wx.EXPAND)
1535 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1536 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1538 self.SetPosition(self.GetParent().GetPosition())
1539 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1542 def OnText(self, e):
1543 self._callback(self._vertex.GetValue(), self._fragment.GetValue())