1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2 from __future__ import absolute_import
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
31 class SceneView(openglGui.glGuiPanel):
32 def __init__(self, parent):
33 super(SceneView, self).__init__(parent)
38 self._scene = objectScene.Scene()
41 self._objectShader = None
43 self._selectedObj = None
44 self._objColors = [None,None,None,None]
47 self._mouseState = None
48 self._viewTarget = numpy.array([0,0,0], numpy.float32)
51 self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
52 self._platformMesh._drawOffset = numpy.array([0,0,2.5], numpy.float32)
53 self._isSimpleMode = True
56 self._modelMatrix = None
57 self._projMatrix = None
58 self.tempMatrix = None
60 self.openFileButton = openglGui.glButton(self, 4, 'Load', (0,0), self.showLoadModel)
61 self.printButton = openglGui.glButton(self, 6, 'Print', (1,0), self.showPrintWindow)
62 self.printButton.setDisabled(True)
65 self.rotateToolButton = openglGui.glRadioButton(self, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
66 self.scaleToolButton = openglGui.glRadioButton(self, 9, 'Scale', (1,-1), group, self.OnToolSelect)
67 self.mirrorToolButton = openglGui.glRadioButton(self, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
69 self.resetRotationButton = openglGui.glButton(self, 12, 'Reset', (0,-2), self.OnRotateReset)
70 self.layFlatButton = openglGui.glButton(self, 16, 'Lay flat', (0,-3), self.OnLayFlat)
72 self.resetScaleButton = openglGui.glButton(self, 13, 'Reset', (1,-2), self.OnScaleReset)
73 self.scaleMaxButton = openglGui.glButton(self, 17, 'To max', (1,-3), self.OnScaleMax)
75 self.mirrorXButton = openglGui.glButton(self, 14, 'Mirror X', (2,-2), lambda button: self.OnMirror(0))
76 self.mirrorYButton = openglGui.glButton(self, 18, 'Mirror Y', (2,-3), lambda button: self.OnMirror(1))
77 self.mirrorZButton = openglGui.glButton(self, 22, 'Mirror Z', (2,-4), lambda button: self.OnMirror(2))
79 self.rotateToolButton.setExpandArrow(True)
80 self.scaleToolButton.setExpandArrow(True)
81 self.mirrorToolButton.setExpandArrow(True)
83 self.scaleForm = openglGui.glFrame(self, (2, -2))
84 openglGui.glGuiLayoutGrid(self.scaleForm)
85 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
86 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
87 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
88 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
89 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
90 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
91 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
92 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
93 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
94 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
95 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
96 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
97 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
98 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
100 self.viewSelection = openglGui.glComboButton(self, 'View mode', [7,11,15,23], ['Normal', 'Transparent', 'X-Ray', 'Layers'], (-1,0), self.OnViewChange)
101 self.layerSelect = openglGui.glSlider(self, 100, 0, 100, (-1,-2), lambda : self.QueueRefresh())
103 self.notification = openglGui.glNotification(self, (0, 0))
105 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
106 self._sceneUpdateTimer = wx.Timer(self)
107 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
108 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
112 self.updateToolButtons()
113 self.updateProfileToControls()
115 def showLoadModel(self, button = 1):
117 dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
118 dlg.SetWildcard(meshLoader.loadWildcardFilter() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
119 if dlg.ShowModal() != wx.ID_OK:
122 filename = dlg.GetPath()
124 if not(os.path.exists(filename)):
126 profile.putPreference('lastFile', filename)
127 self.GetParent().GetParent().GetParent().addToModelMRU(filename)
128 ext = filename[filename.rfind('.')+1:].upper()
129 if ext == 'G' or ext == 'GCODE':
130 if self._gcode is not None:
132 for layerVBOlist in self._gcodeVBOs:
133 for vbo in layerVBOlist:
134 self.glReleaseList.append(vbo)
136 self._gcode = gcodeInterpreter.gcode()
137 self._gcode.progressCallback = self._gcodeLoadCallback
138 self._thread = threading.Thread(target=self._loadGCode, args=(filename,))
139 self._thread.daemon = True
141 self.printButton.setBottomText('')
142 self.viewSelection.setValue(3)
143 self.printButton.setDisabled(False)
146 if self.viewSelection.getValue() == 3:
147 self.viewSelection.setValue(0)
149 self.loadScene([filename])
151 def showSaveModel(self):
152 if len(self._scene.objects()) < 1:
154 dlg=wx.FileDialog(self, 'Save 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
155 dlg.SetWildcard(meshLoader.saveWildcardFilter())
156 if dlg.ShowModal() != wx.ID_OK:
159 filename = dlg.GetPath()
161 meshLoader.saveMeshes(filename, self._scene.objects())
163 def showPrintWindow(self, button):
165 if machineCom.machineIsConnected():
166 printWindow.printFile(self._gcode.filename)
167 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
168 drives = removableStorage.getPossibleSDcardDrives()
170 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))
171 if dlg.ShowModal() != wx.ID_OK:
174 drive = drives[dlg.GetSelection()]
178 filename = os.path.basename(profile.getPreference('lastFile'))
179 filename = filename[0:filename.rfind('.')] + '.gcode'
180 threading.Thread(target=self._copyFile,args=(self._gcode.filename, drive[1] + filename, drive[1])).start()
185 self.Bind(wx.EVT_MENU, lambda e: printWindow.printFile(self._gcode.filename), menu.Append(-1, 'Print with USB'))
186 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, 'Save GCode...'))
187 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, 'Slice engine log...'))
191 def showSaveGCode(self):
192 defPath = profile.getPreference('lastFile')
193 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
194 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
195 dlg.SetFilename(os.path.basename(defPath))
196 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
197 if dlg.ShowModal() != wx.ID_OK:
200 filename = dlg.GetPath()
203 threading.Thread(target=self._copyFile,args=(self._gcode.filename, filename)).start()
205 def _copyFile(self, fileA, fileB, allowEject = False):
207 size = float(os.stat(fileA).st_size)
208 with open(fileA, 'rb') as fsrc:
209 with open(fileB, 'wb') as fdst:
211 buf = fsrc.read(16*1024)
215 self.printButton.setProgressBar(float(fsrc.tell()) / size)
218 self.notification.message("Failed to save")
221 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...'))
223 self.notification.message("Saved as %s" % (fileB))
224 self.printButton.setProgressBar(None)
227 def _showSliceLog(self):
228 dlg = wx.TextEntryDialog(self, "The slicing engine reported the following", "Engine log...", '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
232 def OnToolSelect(self, button):
233 if self.rotateToolButton.getSelected():
234 self.tool = previewTools.toolRotate(self)
235 elif self.scaleToolButton.getSelected():
236 self.tool = previewTools.toolScale(self)
237 elif self.mirrorToolButton.getSelected():
238 self.tool = previewTools.toolNone(self)
240 self.tool = previewTools.toolNone(self)
241 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
242 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
243 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
244 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
245 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
246 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
247 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
248 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
250 def updateToolButtons(self):
251 if self._selectedObj is None:
255 self.rotateToolButton.setHidden(hidden)
256 self.scaleToolButton.setHidden(hidden)
257 self.mirrorToolButton.setHidden(hidden)
259 self.rotateToolButton.setSelected(False)
260 self.scaleToolButton.setSelected(False)
261 self.mirrorToolButton.setSelected(False)
264 def OnViewChange(self):
265 if self.viewSelection.getValue() == 3:
266 self.viewMode = 'gcode'
267 if self._gcode is not None:
268 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
269 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
270 self._selectObject(None)
271 elif self.viewSelection.getValue() == 1:
272 self.viewMode = 'transparent'
273 elif self.viewSelection.getValue() == 2:
274 self.viewMode = 'xray'
276 self.viewMode = 'normal'
277 self.layerSelect.setHidden(self.viewMode != 'gcode')
280 def OnRotateReset(self, button):
281 if self._selectedObj is None:
283 self._selectedObj.resetRotation()
284 self._scene.pushFree()
285 self._selectObject(self._selectedObj)
288 def OnLayFlat(self, button):
289 if self._selectedObj is None:
291 self._selectedObj.layFlat()
292 self._scene.pushFree()
293 self._selectObject(self._selectedObj)
296 def OnScaleReset(self, button):
297 if self._selectedObj is None:
299 self._selectedObj.resetScale()
300 self._selectObject(self._selectedObj)
301 self.updateProfileToControls()
304 def OnScaleMax(self, button):
305 if self._selectedObj is None:
307 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
308 self._scene.pushFree()
309 self._selectObject(self._selectedObj)
310 self.updateProfileToControls()
313 def OnMirror(self, axis):
314 if self._selectedObj is None:
316 self._selectedObj.mirror(axis)
319 def OnScaleEntry(self, value, axis):
320 if self._selectedObj is None:
326 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
327 self.updateProfileToControls()
328 self._scene.pushFree()
329 self._selectObject(self._selectedObj)
332 def OnScaleEntryMM(self, value, axis):
333 if self._selectedObj is None:
339 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
340 self.updateProfileToControls()
341 self._scene.pushFree()
342 self._selectObject(self._selectedObj)
345 def OnDeleteAll(self, e):
346 while len(self._scene.objects()) > 0:
347 self._deleteObject(self._scene.objects()[0])
348 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
350 def OnMultiply(self, e):
351 if self._focusObj is None:
354 dlg = wx.NumberEntryDialog(self, "How many items do you want?", "Copies", "Multiply", 2, 1, 100)
355 if dlg.ShowModal() != wx.ID_OK:
358 cnt = dlg.GetValue() - 1
364 self._scene.add(newObj)
365 self._scene.centerAll()
366 if not self._scene.checkPlatform(newObj):
371 self.notification.message("Could not create more then %d items" % (n))
372 self._scene.remove(newObj)
373 self._scene.centerAll()
376 def OnSplitObject(self, e):
377 if self._focusObj is None:
379 self._scene.remove(self._focusObj)
380 for obj in self._focusObj.split(self._splitCallback):
382 self._scene.centerAll()
383 self._selectObject(None)
386 def _splitCallback(self, progress):
389 def OnMergeObjects(self, e):
390 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
392 self._scene.merge(self._selectedObj, self._focusObj)
395 def sceneUpdated(self):
396 self._sceneUpdateTimer.Start(1, True)
397 self._slicer.abortSlicer()
398 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
401 def _onRunSlicer(self, e):
402 if self._isSimpleMode:
403 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
404 self._slicer.runSlicer(self._scene)
405 if self._isSimpleMode:
406 profile.resetTempOverride()
408 def _updateSliceProgress(self, progressValue, ready):
409 self.printButton.setDisabled(not ready)
410 if progressValue >= 0.0:
411 self.printButton.setProgressBar(progressValue)
413 self.printButton.setProgressBar(None)
414 if self._gcode is not None:
416 for layerVBOlist in self._gcodeVBOs:
417 for vbo in layerVBOlist:
418 self.glReleaseList.append(vbo)
421 self.printButton.setProgressBar(None)
422 cost = self._slicer.getFilamentCost()
424 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
426 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
427 self._gcode = gcodeInterpreter.gcode()
428 self._gcode.progressCallback = self._gcodeLoadCallback
429 self._thread = threading.Thread(target=self._loadGCode)
430 self._thread.daemon = True
433 self.printButton.setBottomText('')
436 def _loadGCode(self, filename = None):
438 filename = self._slicer.getGCodeFilename()
439 self._gcode.load(filename)
441 def _gcodeLoadCallback(self, progress):
442 if self._gcode is None:
444 if len(self._gcode.layerList) % 5 == 0:
446 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
447 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
448 self.layerSelect.setValue(self.layerSelect.getMaxValue())
450 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
453 def loadScene(self, fileList):
454 for filename in fileList:
456 objList = meshLoader.loadMeshes(filename)
458 traceback.print_exc()
461 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
463 self._scene.centerAll()
464 self._selectObject(obj)
467 def _deleteObject(self, obj):
468 if obj == self._selectedObj:
469 self._selectObject(None)
470 if obj == self._focusObj:
471 self._focusObj = None
472 self._scene.remove(obj)
473 for m in obj._meshList:
474 if m.vbo is not None and m.vbo.decRef():
475 self.glReleaseList.append(m.vbo)
480 def _selectObject(self, obj, zoom = True):
481 if obj != self._selectedObj:
482 self._selectedObj = obj
483 self.updateProfileToControls()
484 self.updateToolButtons()
485 if zoom and obj is not None:
486 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
487 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
488 newZoom = obj.getBoundaryCircle() * 6
489 if newZoom > numpy.max(self._machineSize) * 3:
490 newZoom = numpy.max(self._machineSize) * 3
491 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
493 def updateProfileToControls(self):
494 oldSimpleMode = self._isSimpleMode
495 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
496 if self._isSimpleMode and not oldSimpleMode:
497 self._scene.arrangeAll()
499 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
500 self._objColors[0] = profile.getPreferenceColour('model_colour')
501 self._objColors[1] = profile.getPreferenceColour('model_colour2')
502 self._objColors[2] = profile.getPreferenceColour('model_colour3')
503 self._objColors[3] = profile.getPreferenceColour('model_colour4')
504 self._scene.setMachineSize(self._machineSize)
505 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
506 self._scene.setHeadSize(profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_min_y'), profile.getPreferenceFloat('extruder_head_size_max_y'), profile.getPreferenceFloat('extruder_head_size_height'))
508 if self._selectedObj is not None:
509 scale = self._selectedObj.getScale()
510 size = self._selectedObj.getSize()
511 self.scaleXctrl.setValue(round(scale[0], 2))
512 self.scaleYctrl.setValue(round(scale[1], 2))
513 self.scaleZctrl.setValue(round(scale[2], 2))
514 self.scaleXmmctrl.setValue(round(size[0], 2))
515 self.scaleYmmctrl.setValue(round(size[1], 2))
516 self.scaleZmmctrl.setValue(round(size[2], 2))
518 def OnKeyChar(self, keyCode):
519 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
520 if self._selectedObj is not None:
521 self._deleteObject(self._selectedObj)
523 if keyCode == wx.WXK_UP:
524 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
526 elif keyCode == wx.WXK_DOWN:
527 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
529 elif keyCode == wx.WXK_PAGEUP:
530 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
532 elif keyCode == wx.WXK_PAGEDOWN:
533 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
536 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
537 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
538 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
539 from collections import defaultdict
540 from gc import get_objects
541 self._beforeLeakTest = defaultdict(int)
542 for i in get_objects():
543 self._beforeLeakTest[type(i)] += 1
544 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
545 from collections import defaultdict
546 from gc import get_objects
547 self._afterLeakTest = defaultdict(int)
548 for i in get_objects():
549 self._afterLeakTest[type(i)] += 1
550 for k in self._afterLeakTest:
551 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
552 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
554 def ShaderUpdate(self, v, f):
555 s = opengl.GLShader(v, f)
557 self._objectLoadShader.release()
558 self._objectLoadShader = s
559 for obj in self._scene.objects():
560 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
563 def OnMouseDown(self,e):
564 self._mouseX = e.GetX()
565 self._mouseY = e.GetY()
566 self._mouseClick3DPos = self._mouse3Dpos
567 self._mouseClickFocus = self._focusObj
569 self._mouseState = 'doubleClick'
571 self._mouseState = 'dragOrClick'
572 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
573 p0 -= self.getObjectCenterPos() - self._viewTarget
574 p1 -= self.getObjectCenterPos() - self._viewTarget
575 if self.tool.OnDragStart(p0, p1):
576 self._mouseState = 'tool'
577 if self._mouseState == 'dragOrClick':
578 if e.GetButton() == 1:
579 if self._focusObj is not None:
580 self._selectObject(self._focusObj, False)
583 def OnMouseUp(self, e):
584 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
586 if self._mouseState == 'dragOrClick':
587 if e.GetButton() == 1:
588 self._selectObject(self._focusObj)
589 if e.GetButton() == 3:
591 if self._focusObj is not None:
592 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
593 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
594 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
595 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
596 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
597 if len(self._scene.objects()) > 0:
598 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
599 if menu.MenuItemCount > 0:
602 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
603 self._scene.pushFree()
605 elif self._mouseState == 'tool':
606 if self.tempMatrix is not None and self._selectedObj is not None:
607 self._selectedObj.applyMatrix(self.tempMatrix)
608 self._scene.pushFree()
609 self._selectObject(self._selectedObj)
610 self.tempMatrix = None
611 self.tool.OnDragEnd()
613 self._mouseState = None
615 def OnMouseMotion(self,e):
616 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
617 p0 -= self.getObjectCenterPos() - self._viewTarget
618 p1 -= self.getObjectCenterPos() - self._viewTarget
620 if e.Dragging() and self._mouseState is not None:
621 if self._mouseState == 'tool':
622 self.tool.OnDrag(p0, p1)
623 elif not e.LeftIsDown() and e.RightIsDown():
624 self._mouseState = 'drag'
625 if wx.GetKeyState(wx.WXK_SHIFT):
626 a = math.cos(math.radians(self._yaw)) / 3.0
627 b = math.sin(math.radians(self._yaw)) / 3.0
628 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
629 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
630 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
631 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
633 self._yaw += e.GetX() - self._mouseX
634 self._pitch -= e.GetY() - self._mouseY
635 if self._pitch > 170:
639 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
640 self._mouseState = 'drag'
641 self._zoom += e.GetY() - self._mouseY
644 if self._zoom > numpy.max(self._machineSize) * 3:
645 self._zoom = numpy.max(self._machineSize) * 3
646 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
647 self._mouseState = 'dragObject'
648 z = max(0, self._mouseClick3DPos[2])
649 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
650 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
655 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
656 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
657 diff = cursorZ1 - cursorZ0
658 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
659 if not e.Dragging() or self._mouseState != 'tool':
660 self.tool.OnMouseMove(p0, p1)
662 self._mouseX = e.GetX()
663 self._mouseY = e.GetY()
665 def OnMouseWheel(self, e):
666 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
667 delta = max(min(delta,4),-4)
668 self._zoom *= 1.0 - delta / 10.0
671 if self._zoom > numpy.max(self._machineSize) * 3:
672 self._zoom = numpy.max(self._machineSize) * 3
675 def getMouseRay(self, x, y):
676 if self._viewport is None:
677 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
678 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
679 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
680 p0 -= self._viewTarget
681 p1 -= self._viewTarget
684 def _init3DView(self):
685 # set viewing projection
686 size = self.GetSize()
687 glViewport(0, 0, size.GetWidth(), size.GetHeight())
690 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
692 glDisable(GL_RESCALE_NORMAL)
693 glDisable(GL_LIGHTING)
695 glEnable(GL_DEPTH_TEST)
696 glDisable(GL_CULL_FACE)
698 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
700 glClearColor(0.8, 0.8, 0.8, 1.0)
704 glMatrixMode(GL_PROJECTION)
706 aspect = float(size.GetWidth()) / float(size.GetHeight())
707 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
709 glMatrixMode(GL_MODELVIEW)
711 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
714 if machineCom.machineIsConnected():
715 self.printButton._imageID = 6
716 self.printButton._tooltip = 'Print'
717 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
718 self.printButton._imageID = 2
719 self.printButton._tooltip = 'Toolpath to SD'
721 self.printButton._imageID = 3
722 self.printButton._tooltip = 'Save toolpath'
724 if self._animView is not None:
725 self._viewTarget = self._animView.getPosition()
726 if self._animView.isDone():
727 self._animView = None
728 if self._animZoom is not None:
729 self._zoom = self._animZoom.getPosition()
730 if self._animZoom.isDone():
731 self._animZoom = None
732 if self.viewMode == 'gcode' and self._gcode is not None:
734 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
737 if self._objectShader is None:
738 self._objectShader = opengl.GLShader("""
739 varying float light_amount;
743 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
744 gl_FrontColor = gl_Color;
746 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
750 varying float light_amount;
754 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
757 self._objectLoadShader = opengl.GLShader("""
758 uniform float intensity;
760 varying float light_amount;
764 vec4 tmp = gl_Vertex;
765 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
766 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
767 gl_Position = gl_ModelViewProjectionMatrix * tmp;
768 gl_FrontColor = gl_Color;
770 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
774 uniform float intensity;
775 varying float light_amount;
779 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
783 glTranslate(0,0,-self._zoom)
784 glRotate(-self._pitch, 1,0,0)
785 glRotate(self._yaw, 0,0,1)
786 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
788 self._viewport = glGetIntegerv(GL_VIEWPORT)
789 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
790 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
792 glClearColor(1,1,1,1)
793 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
795 if self.viewMode != 'gcode':
796 for n in xrange(0, len(self._scene.objects())):
797 obj = self._scene.objects()[n]
798 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
799 self._renderObject(obj)
801 if self._mouseX > -1:
803 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
804 if n < len(self._scene.objects()):
805 self._focusObj = self._scene.objects()[n]
807 self._focusObj = None
808 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
809 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
810 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
811 self._mouse3Dpos -= self._viewTarget
814 glTranslate(0,0,-self._zoom)
815 glRotate(-self._pitch, 1,0,0)
816 glRotate(self._yaw, 0,0,1)
817 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
819 if self.viewMode == 'gcode':
820 if self._gcode is not None:
822 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
824 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
825 for n in xrange(0, drawUpTill):
826 c = 1.0 - float(drawUpTill - n) / 15
828 if len(self._gcodeVBOs) < n + 1:
829 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
830 if time.time() - t > 0.5:
833 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
834 if n == drawUpTill - 1:
835 if len(self._gcodeVBOs[n]) < 6:
836 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
838 self._gcodeVBOs[n][5].render(GL_QUADS)
840 self._gcodeVBOs[n][6].render(GL_QUADS)
841 glColor3f(c/2, c/2, 0.0)
842 self._gcodeVBOs[n][7].render(GL_QUADS)
844 self._gcodeVBOs[n][8].render(GL_QUADS)
845 self._gcodeVBOs[n][9].render(GL_QUADS)
847 self._gcodeVBOs[n][10].render(GL_LINES)
850 self._gcodeVBOs[n][0].render(GL_LINES)
852 self._gcodeVBOs[n][1].render(GL_LINES)
853 glColor3f(c/2, c/2, 0.0)
854 self._gcodeVBOs[n][2].render(GL_LINES)
856 self._gcodeVBOs[n][3].render(GL_LINES)
857 self._gcodeVBOs[n][4].render(GL_LINES)
860 glStencilFunc(GL_ALWAYS, 1, 1)
861 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
863 self._objectShader.bind()
864 for obj in self._scene.objects():
865 if obj._loadAnim is not None:
866 if obj._loadAnim.isDone():
871 if self._focusObj == obj:
873 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
876 if self._selectedObj == obj or self._selectedObj is None:
877 #If we want transparent, then first render a solid black model to remove the printer size lines.
878 if self.viewMode == 'transparent':
879 glColor4f(0, 0, 0, 0)
880 self._renderObject(obj)
882 glBlendFunc(GL_ONE, GL_ONE)
883 glDisable(GL_DEPTH_TEST)
885 if self.viewMode == 'xray':
886 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
887 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
888 glEnable(GL_STENCIL_TEST)
890 if not self._scene.checkPlatform(obj):
891 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
892 self._renderObject(obj)
894 self._renderObject(obj, brightness)
895 glDisable(GL_STENCIL_TEST)
897 glEnable(GL_DEPTH_TEST)
898 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
900 if self.viewMode == 'xray':
903 glEnable(GL_STENCIL_TEST)
904 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
905 glDisable(GL_DEPTH_TEST)
906 for i in xrange(2, 15, 2):
907 glStencilFunc(GL_EQUAL, i, 0xFF)
908 glColor(float(i)/10, float(i)/10, float(i)/5)
910 glVertex3f(-1000,-1000,-10)
911 glVertex3f( 1000,-1000,-10)
912 glVertex3f( 1000, 1000,-10)
913 glVertex3f(-1000, 1000,-10)
915 for i in xrange(1, 15, 2):
916 glStencilFunc(GL_EQUAL, i, 0xFF)
917 glColor(float(i)/10, 0, 0)
919 glVertex3f(-1000,-1000,-10)
920 glVertex3f( 1000,-1000,-10)
921 glVertex3f( 1000, 1000,-10)
922 glVertex3f(-1000, 1000,-10)
925 glDisable(GL_STENCIL_TEST)
926 glEnable(GL_DEPTH_TEST)
928 self._objectShader.unbind()
930 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
932 self._objectLoadShader.bind()
933 glColor4f(0.2, 0.6, 1.0, 1.0)
934 for obj in self._scene.objects():
935 if obj._loadAnim is None:
937 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
938 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
939 self._renderObject(obj)
940 self._objectLoadShader.unbind()
945 if self.viewMode == 'gcode':
948 #Draw the object box-shadow, so you can see where it will collide with other objects.
949 if self._selectedObj is not None and len(self._scene.objects()) > 1:
950 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
952 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
954 glEnable(GL_CULL_FACE)
955 glColor4f(0,0,0,0.12)
957 glVertex3f(-size[0], size[1], 0.1)
958 glVertex3f(-size[0], -size[1], 0.1)
959 glVertex3f( size[0], -size[1], 0.1)
960 glVertex3f( size[0], size[1], 0.1)
962 glDisable(GL_CULL_FACE)
965 #Draw the outline of the selected object, on top of everything else except the GUI.
966 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
967 glDisable(GL_DEPTH_TEST)
968 glEnable(GL_CULL_FACE)
969 glEnable(GL_STENCIL_TEST)
971 glStencilFunc(GL_EQUAL, 0, 255)
973 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
976 self._renderObject(self._selectedObj)
977 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
979 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
980 glDisable(GL_STENCIL_TEST)
981 glDisable(GL_CULL_FACE)
982 glEnable(GL_DEPTH_TEST)
984 if self._selectedObj is not None:
986 pos = self.getObjectCenterPos()
987 glTranslate(pos[0], pos[1], pos[2])
991 def _renderObject(self, obj, brightness = False, addSink = True):
994 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
996 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
998 if self.tempMatrix is not None and obj == self._selectedObj:
999 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1000 glMultMatrixf(tempMatrix)
1002 offset = obj.getDrawOffset()
1003 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1005 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1006 glMultMatrixf(tempMatrix)
1009 for m in obj._meshList:
1011 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1013 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1018 def _drawMachine(self):
1019 glEnable(GL_CULL_FACE)
1022 if profile.getPreference('machine_type') == 'ultimaker':
1023 glColor4f(1,1,1,0.5)
1024 self._objectShader.bind()
1025 self._renderObject(self._platformMesh, False, False)
1026 self._objectShader.unbind()
1028 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1029 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1030 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1031 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1032 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1033 v4 = [ size[0] / 2, size[1] / 2, 0]
1034 v5 = [ size[0] / 2,-size[1] / 2, 0]
1035 v6 = [-size[0] / 2, size[1] / 2, 0]
1036 v7 = [-size[0] / 2,-size[1] / 2, 0]
1038 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1039 glEnableClientState(GL_VERTEX_ARRAY)
1040 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1042 glColor4ub(5, 171, 231, 64)
1043 glDrawArrays(GL_QUADS, 0, 4)
1044 glColor4ub(5, 171, 231, 96)
1045 glDrawArrays(GL_QUADS, 4, 8)
1046 glColor4ub(5, 171, 231, 128)
1047 glDrawArrays(GL_QUADS, 12, 8)
1049 sx = self._machineSize[0]
1050 sy = self._machineSize[1]
1051 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1052 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1057 x1 = max(min(x1, sx/2), -sx/2)
1058 y1 = max(min(y1, sy/2), -sy/2)
1059 x2 = max(min(x2, sx/2), -sx/2)
1060 y2 = max(min(y2, sy/2), -sy/2)
1061 if (x & 1) == (y & 1):
1062 glColor4ub(5, 171, 231, 127)
1064 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1066 glVertex3f(x1, y1, -0.02)
1067 glVertex3f(x2, y1, -0.02)
1068 glVertex3f(x2, y2, -0.02)
1069 glVertex3f(x1, y2, -0.02)
1072 glDisableClientState(GL_VERTEX_ARRAY)
1074 glDisable(GL_CULL_FACE)
1076 def _generateGCodeVBOs(self, layer):
1078 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1079 pointList = numpy.zeros((0,3), numpy.float32)
1081 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1083 a = numpy.concatenate((a[:-1], a[1:]), 1)
1084 a = a.reshape((len(a) * 2, 3))
1085 pointList = numpy.concatenate((pointList, a))
1086 ret.append(opengl.GLVBO(pointList))
1089 def _generateGCodeVBOs2(self, layer):
1090 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1091 filamentArea = math.pi * filamentRadius * filamentRadius
1094 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1095 pointList = numpy.zeros((0,3), numpy.float32)
1097 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1099 if extrudeType == 'FILL':
1102 normal = a[1:] - a[:-1]
1103 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1104 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1107 ePerDist = path['extrusion'][1:] / lens
1108 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1110 normal[:,0] *= lineWidth
1111 normal[:,1] *= lineWidth
1113 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1114 b = numpy.concatenate((b, a[1:] + normal), 1)
1115 b = numpy.concatenate((b, a[1:] - normal), 1)
1116 b = numpy.concatenate((b, a[:-1] - normal), 1)
1117 b = numpy.concatenate((b, a[:-1] + normal), 1)
1118 #b = numpy.concatenate((b, a[:-1]), 1)
1119 #b = numpy.concatenate((b, a[:-1]), 1)
1120 b = b.reshape((len(b) * 4, 3))
1122 pointList = numpy.concatenate((pointList, b))
1123 ret.append(opengl.GLVBO(pointList))
1125 pointList = numpy.zeros((0,3), numpy.float32)
1127 if path['type'] == 'move' or path['type'] == 'retract':
1129 a = numpy.concatenate((a[:-1], a[1:]), 1)
1130 a = a.reshape((len(a) * 2, 3))
1131 pointList = numpy.concatenate((pointList, a))
1132 ret.append(opengl.GLVBO(pointList))
1136 def getObjectCenterPos(self):
1137 if self._selectedObj is None:
1138 return [0.0, 0.0, 0.0]
1139 pos = self._selectedObj.getPosition()
1140 size = self._selectedObj.getSize()
1141 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1143 def getObjectBoundaryCircle(self):
1144 if self._selectedObj is None:
1146 return self._selectedObj.getBoundaryCircle()
1148 def getObjectSize(self):
1149 if self._selectedObj is None:
1150 return [0.0, 0.0, 0.0]
1151 return self._selectedObj.getSize()
1153 def getObjectMatrix(self):
1154 if self._selectedObj is None:
1155 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1156 return self._selectedObj.getMatrix()
1158 class shaderEditor(wx.Dialog):
1159 def __init__(self, parent, callback, v, f):
1160 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1161 self._callback = callback
1162 s = wx.BoxSizer(wx.VERTICAL)
1164 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1165 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1166 s.Add(self._vertex, 1, flag=wx.EXPAND)
1167 s.Add(self._fragment, 1, flag=wx.EXPAND)
1169 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1170 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1172 self.SetPosition(self.GetParent().GetPosition())
1173 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1176 def OnText(self, e):
1177 self._callback(self._vertex.GetValue(), self._fragment.GetValue())