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
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._gcode is None:
448 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
449 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
450 self.layerSelect.setValue(self.layerSelect.getMaxValue())
452 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
453 if self.viewMode == 'gcode':
457 def loadScene(self, fileList):
458 for filename in fileList:
460 objList = meshLoader.loadMeshes(filename)
462 traceback.print_exc()
465 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
467 self._scene.centerAll()
468 self._selectObject(obj)
471 def _deleteObject(self, obj):
472 if obj == self._selectedObj:
473 self._selectObject(None)
474 if obj == self._focusObj:
475 self._focusObj = None
476 self._scene.remove(obj)
477 for m in obj._meshList:
478 if m.vbo is not None and m.vbo.decRef():
479 self.glReleaseList.append(m.vbo)
484 def _selectObject(self, obj, zoom = True):
485 if obj != self._selectedObj:
486 self._selectedObj = obj
487 self.updateProfileToControls()
488 self.updateToolButtons()
489 if zoom and obj is not None:
490 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
491 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
492 newZoom = obj.getBoundaryCircle() * 6
493 if newZoom > numpy.max(self._machineSize) * 3:
494 newZoom = numpy.max(self._machineSize) * 3
495 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
497 def updateProfileToControls(self):
498 oldSimpleMode = self._isSimpleMode
499 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
500 if self._isSimpleMode and not oldSimpleMode:
501 self._scene.arrangeAll()
503 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
504 self._objColors[0] = profile.getPreferenceColour('model_colour')
505 self._objColors[1] = profile.getPreferenceColour('model_colour2')
506 self._objColors[2] = profile.getPreferenceColour('model_colour3')
507 self._objColors[3] = profile.getPreferenceColour('model_colour4')
508 self._scene.setMachineSize(self._machineSize)
509 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
510 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'))
512 if self._selectedObj is not None:
513 scale = self._selectedObj.getScale()
514 size = self._selectedObj.getSize()
515 self.scaleXctrl.setValue(round(scale[0], 2))
516 self.scaleYctrl.setValue(round(scale[1], 2))
517 self.scaleZctrl.setValue(round(scale[2], 2))
518 self.scaleXmmctrl.setValue(round(size[0], 2))
519 self.scaleYmmctrl.setValue(round(size[1], 2))
520 self.scaleZmmctrl.setValue(round(size[2], 2))
522 def OnKeyChar(self, keyCode):
523 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
524 if self._selectedObj is not None:
525 self._deleteObject(self._selectedObj)
527 if keyCode == wx.WXK_UP:
528 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
530 elif keyCode == wx.WXK_DOWN:
531 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
533 elif keyCode == wx.WXK_PAGEUP:
534 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
536 elif keyCode == wx.WXK_PAGEDOWN:
537 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
540 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
541 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
542 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
543 from collections import defaultdict
544 from gc import get_objects
545 self._beforeLeakTest = defaultdict(int)
546 for i in get_objects():
547 self._beforeLeakTest[type(i)] += 1
548 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
549 from collections import defaultdict
550 from gc import get_objects
551 self._afterLeakTest = defaultdict(int)
552 for i in get_objects():
553 self._afterLeakTest[type(i)] += 1
554 for k in self._afterLeakTest:
555 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
556 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
558 def ShaderUpdate(self, v, f):
559 s = opengl.GLShader(v, f)
561 self._objectLoadShader.release()
562 self._objectLoadShader = s
563 for obj in self._scene.objects():
564 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
567 def OnMouseDown(self,e):
568 self._mouseX = e.GetX()
569 self._mouseY = e.GetY()
570 self._mouseClick3DPos = self._mouse3Dpos
571 self._mouseClickFocus = self._focusObj
573 self._mouseState = 'doubleClick'
575 self._mouseState = 'dragOrClick'
576 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
577 p0 -= self.getObjectCenterPos() - self._viewTarget
578 p1 -= self.getObjectCenterPos() - self._viewTarget
579 if self.tool.OnDragStart(p0, p1):
580 self._mouseState = 'tool'
581 if self._mouseState == 'dragOrClick':
582 if e.GetButton() == 1:
583 if self._focusObj is not None:
584 self._selectObject(self._focusObj, False)
587 def OnMouseUp(self, e):
588 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
590 if self._mouseState == 'dragOrClick':
591 if e.GetButton() == 1:
592 self._selectObject(self._focusObj)
593 if e.GetButton() == 3:
595 if self._focusObj is not None:
596 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
597 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
598 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
599 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
600 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
601 if len(self._scene.objects()) > 0:
602 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
603 if menu.MenuItemCount > 0:
606 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
607 self._scene.pushFree()
609 elif self._mouseState == 'tool':
610 if self.tempMatrix is not None and self._selectedObj is not None:
611 self._selectedObj.applyMatrix(self.tempMatrix)
612 self._scene.pushFree()
613 self._selectObject(self._selectedObj)
614 self.tempMatrix = None
615 self.tool.OnDragEnd()
617 self._mouseState = None
619 def OnMouseMotion(self,e):
620 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
621 p0 -= self.getObjectCenterPos() - self._viewTarget
622 p1 -= self.getObjectCenterPos() - self._viewTarget
624 if e.Dragging() and self._mouseState is not None:
625 if self._mouseState == 'tool':
626 self.tool.OnDrag(p0, p1)
627 elif not e.LeftIsDown() and e.RightIsDown():
628 self._mouseState = 'drag'
629 if wx.GetKeyState(wx.WXK_SHIFT):
630 a = math.cos(math.radians(self._yaw)) / 3.0
631 b = math.sin(math.radians(self._yaw)) / 3.0
632 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
633 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
634 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
635 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
637 self._yaw += e.GetX() - self._mouseX
638 self._pitch -= e.GetY() - self._mouseY
639 if self._pitch > 170:
643 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
644 self._mouseState = 'drag'
645 self._zoom += e.GetY() - self._mouseY
648 if self._zoom > numpy.max(self._machineSize) * 3:
649 self._zoom = numpy.max(self._machineSize) * 3
650 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
651 self._mouseState = 'dragObject'
652 z = max(0, self._mouseClick3DPos[2])
653 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
654 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
659 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
660 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
661 diff = cursorZ1 - cursorZ0
662 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
663 if not e.Dragging() or self._mouseState != 'tool':
664 self.tool.OnMouseMove(p0, p1)
666 self._mouseX = e.GetX()
667 self._mouseY = e.GetY()
669 def OnMouseWheel(self, e):
670 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
671 delta = max(min(delta,4),-4)
672 self._zoom *= 1.0 - delta / 10.0
675 if self._zoom > numpy.max(self._machineSize) * 3:
676 self._zoom = numpy.max(self._machineSize) * 3
679 def getMouseRay(self, x, y):
680 if self._viewport is None:
681 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
682 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
683 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
684 p0 -= self._viewTarget
685 p1 -= self._viewTarget
688 def _init3DView(self):
689 # set viewing projection
690 size = self.GetSize()
691 glViewport(0, 0, size.GetWidth(), size.GetHeight())
694 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
696 glDisable(GL_RESCALE_NORMAL)
697 glDisable(GL_LIGHTING)
699 glEnable(GL_DEPTH_TEST)
700 glDisable(GL_CULL_FACE)
702 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
704 glClearColor(0.8, 0.8, 0.8, 1.0)
708 glMatrixMode(GL_PROJECTION)
710 aspect = float(size.GetWidth()) / float(size.GetHeight())
711 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
713 glMatrixMode(GL_MODELVIEW)
715 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
718 if machineCom.machineIsConnected():
719 self.printButton._imageID = 6
720 self.printButton._tooltip = 'Print'
721 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
722 self.printButton._imageID = 2
723 self.printButton._tooltip = 'Toolpath to SD'
725 self.printButton._imageID = 3
726 self.printButton._tooltip = 'Save toolpath'
728 if self._animView is not None:
729 self._viewTarget = self._animView.getPosition()
730 if self._animView.isDone():
731 self._animView = None
732 if self._animZoom is not None:
733 self._zoom = self._animZoom.getPosition()
734 if self._animZoom.isDone():
735 self._animZoom = None
736 if self.viewMode == 'gcode' and self._gcode is not None:
738 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
741 if self._objectShader is None:
742 self._objectShader = opengl.GLShader("""
743 varying float light_amount;
747 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
748 gl_FrontColor = gl_Color;
750 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
754 varying float light_amount;
758 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
761 self._objectLoadShader = opengl.GLShader("""
762 uniform float intensity;
764 varying float light_amount;
768 vec4 tmp = gl_Vertex;
769 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
770 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
771 gl_Position = gl_ModelViewProjectionMatrix * tmp;
772 gl_FrontColor = gl_Color;
774 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
778 uniform float intensity;
779 varying float light_amount;
783 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
787 glTranslate(0,0,-self._zoom)
788 glRotate(-self._pitch, 1,0,0)
789 glRotate(self._yaw, 0,0,1)
790 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
792 self._viewport = glGetIntegerv(GL_VIEWPORT)
793 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
794 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
796 glClearColor(1,1,1,1)
797 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
799 if self.viewMode != 'gcode':
800 for n in xrange(0, len(self._scene.objects())):
801 obj = self._scene.objects()[n]
802 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
803 self._renderObject(obj)
805 if self._mouseX > -1:
807 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
808 if n < len(self._scene.objects()):
809 self._focusObj = self._scene.objects()[n]
811 self._focusObj = None
812 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
813 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
814 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
815 self._mouse3Dpos -= self._viewTarget
818 glTranslate(0,0,-self._zoom)
819 glRotate(-self._pitch, 1,0,0)
820 glRotate(self._yaw, 0,0,1)
821 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
823 if self.viewMode == 'gcode':
824 if self._gcode is not None:
826 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
828 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
829 for n in xrange(0, drawUpTill):
830 c = 1.0 - float(drawUpTill - n) / 15
832 if len(self._gcodeVBOs) < n + 1:
833 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
834 if time.time() - t > 0.5:
837 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
838 if n == drawUpTill - 1:
839 if len(self._gcodeVBOs[n]) < 6:
840 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
842 self._gcodeVBOs[n][5].render(GL_QUADS)
844 self._gcodeVBOs[n][6].render(GL_QUADS)
845 glColor3f(c/2, c/2, 0.0)
846 self._gcodeVBOs[n][7].render(GL_QUADS)
848 self._gcodeVBOs[n][8].render(GL_QUADS)
849 self._gcodeVBOs[n][9].render(GL_QUADS)
851 self._gcodeVBOs[n][10].render(GL_LINES)
854 self._gcodeVBOs[n][0].render(GL_LINES)
856 self._gcodeVBOs[n][1].render(GL_LINES)
857 glColor3f(c/2, c/2, 0.0)
858 self._gcodeVBOs[n][2].render(GL_LINES)
860 self._gcodeVBOs[n][3].render(GL_LINES)
861 self._gcodeVBOs[n][4].render(GL_LINES)
864 glStencilFunc(GL_ALWAYS, 1, 1)
865 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
867 self._objectShader.bind()
868 for obj in self._scene.objects():
869 if obj._loadAnim is not None:
870 if obj._loadAnim.isDone():
875 if self._focusObj == obj:
877 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
880 if self._selectedObj == obj or self._selectedObj is None:
881 #If we want transparent, then first render a solid black model to remove the printer size lines.
882 if self.viewMode == 'transparent':
883 glColor4f(0, 0, 0, 0)
884 self._renderObject(obj)
886 glBlendFunc(GL_ONE, GL_ONE)
887 glDisable(GL_DEPTH_TEST)
889 if self.viewMode == 'xray':
890 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
891 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
892 glEnable(GL_STENCIL_TEST)
894 if not self._scene.checkPlatform(obj):
895 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
896 self._renderObject(obj)
898 self._renderObject(obj, brightness)
899 glDisable(GL_STENCIL_TEST)
901 glEnable(GL_DEPTH_TEST)
902 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
904 if self.viewMode == 'xray':
907 glEnable(GL_STENCIL_TEST)
908 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
909 glDisable(GL_DEPTH_TEST)
910 for i in xrange(2, 15, 2):
911 glStencilFunc(GL_EQUAL, i, 0xFF)
912 glColor(float(i)/10, float(i)/10, float(i)/5)
914 glVertex3f(-1000,-1000,-10)
915 glVertex3f( 1000,-1000,-10)
916 glVertex3f( 1000, 1000,-10)
917 glVertex3f(-1000, 1000,-10)
919 for i in xrange(1, 15, 2):
920 glStencilFunc(GL_EQUAL, i, 0xFF)
921 glColor(float(i)/10, 0, 0)
923 glVertex3f(-1000,-1000,-10)
924 glVertex3f( 1000,-1000,-10)
925 glVertex3f( 1000, 1000,-10)
926 glVertex3f(-1000, 1000,-10)
929 glDisable(GL_STENCIL_TEST)
930 glEnable(GL_DEPTH_TEST)
932 self._objectShader.unbind()
934 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
936 self._objectLoadShader.bind()
937 glColor4f(0.2, 0.6, 1.0, 1.0)
938 for obj in self._scene.objects():
939 if obj._loadAnim is None:
941 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
942 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
943 self._renderObject(obj)
944 self._objectLoadShader.unbind()
949 if self.viewMode == 'gcode':
952 #Draw the object box-shadow, so you can see where it will collide with other objects.
953 if self._selectedObj is not None and len(self._scene.objects()) > 1:
954 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
956 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
958 glEnable(GL_CULL_FACE)
959 glColor4f(0,0,0,0.12)
961 glVertex3f(-size[0], size[1], 0.1)
962 glVertex3f(-size[0], -size[1], 0.1)
963 glVertex3f( size[0], -size[1], 0.1)
964 glVertex3f( size[0], size[1], 0.1)
966 glDisable(GL_CULL_FACE)
969 #Draw the outline of the selected object, on top of everything else except the GUI.
970 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
971 glDisable(GL_DEPTH_TEST)
972 glEnable(GL_CULL_FACE)
973 glEnable(GL_STENCIL_TEST)
975 glStencilFunc(GL_EQUAL, 0, 255)
977 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
980 self._renderObject(self._selectedObj)
981 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
983 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
984 glDisable(GL_STENCIL_TEST)
985 glDisable(GL_CULL_FACE)
986 glEnable(GL_DEPTH_TEST)
988 if self._selectedObj is not None:
990 pos = self.getObjectCenterPos()
991 glTranslate(pos[0], pos[1], pos[2])
995 def _renderObject(self, obj, brightness = False, addSink = True):
998 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1000 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1002 if self.tempMatrix is not None and obj == self._selectedObj:
1003 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1004 glMultMatrixf(tempMatrix)
1006 offset = obj.getDrawOffset()
1007 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1009 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1010 glMultMatrixf(tempMatrix)
1013 for m in obj._meshList:
1015 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1017 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1022 def _drawMachine(self):
1023 glEnable(GL_CULL_FACE)
1026 if profile.getPreference('machine_type') == 'ultimaker':
1027 glColor4f(1,1,1,0.5)
1028 self._objectShader.bind()
1029 self._renderObject(self._platformMesh, False, False)
1030 self._objectShader.unbind()
1032 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1033 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1034 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1035 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1036 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1037 v4 = [ size[0] / 2, size[1] / 2, 0]
1038 v5 = [ size[0] / 2,-size[1] / 2, 0]
1039 v6 = [-size[0] / 2, size[1] / 2, 0]
1040 v7 = [-size[0] / 2,-size[1] / 2, 0]
1042 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1043 glEnableClientState(GL_VERTEX_ARRAY)
1044 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1046 glColor4ub(5, 171, 231, 64)
1047 glDrawArrays(GL_QUADS, 0, 4)
1048 glColor4ub(5, 171, 231, 96)
1049 glDrawArrays(GL_QUADS, 4, 8)
1050 glColor4ub(5, 171, 231, 128)
1051 glDrawArrays(GL_QUADS, 12, 8)
1053 sx = self._machineSize[0]
1054 sy = self._machineSize[1]
1055 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1056 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1061 x1 = max(min(x1, sx/2), -sx/2)
1062 y1 = max(min(y1, sy/2), -sy/2)
1063 x2 = max(min(x2, sx/2), -sx/2)
1064 y2 = max(min(y2, sy/2), -sy/2)
1065 if (x & 1) == (y & 1):
1066 glColor4ub(5, 171, 231, 127)
1068 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1070 glVertex3f(x1, y1, -0.02)
1071 glVertex3f(x2, y1, -0.02)
1072 glVertex3f(x2, y2, -0.02)
1073 glVertex3f(x1, y2, -0.02)
1076 glDisableClientState(GL_VERTEX_ARRAY)
1078 glDisable(GL_CULL_FACE)
1080 def _generateGCodeVBOs(self, layer):
1082 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1083 pointList = numpy.zeros((0,3), numpy.float32)
1085 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1087 a = numpy.concatenate((a[:-1], a[1:]), 1)
1088 a = a.reshape((len(a) * 2, 3))
1089 pointList = numpy.concatenate((pointList, a))
1090 ret.append(opengl.GLVBO(pointList))
1093 def _generateGCodeVBOs2(self, layer):
1094 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1095 filamentArea = math.pi * filamentRadius * filamentRadius
1098 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1099 pointList = numpy.zeros((0,3), numpy.float32)
1101 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1103 if extrudeType == 'FILL':
1106 normal = a[1:] - a[:-1]
1107 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1108 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1111 ePerDist = path['extrusion'][1:] / lens
1112 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1114 normal[:,0] *= lineWidth
1115 normal[:,1] *= lineWidth
1117 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1118 b = numpy.concatenate((b, a[1:] + normal), 1)
1119 b = numpy.concatenate((b, a[1:] - normal), 1)
1120 b = numpy.concatenate((b, a[:-1] - normal), 1)
1121 b = numpy.concatenate((b, a[:-1] + normal), 1)
1122 #b = numpy.concatenate((b, a[:-1]), 1)
1123 #b = numpy.concatenate((b, a[:-1]), 1)
1124 b = b.reshape((len(b) * 4, 3))
1126 pointList = numpy.concatenate((pointList, b))
1127 ret.append(opengl.GLVBO(pointList))
1129 pointList = numpy.zeros((0,3), numpy.float32)
1131 if path['type'] == 'move' or path['type'] == 'retract':
1133 a = numpy.concatenate((a[:-1], a[1:]), 1)
1134 a = a.reshape((len(a) * 2, 3))
1135 pointList = numpy.concatenate((pointList, a))
1136 ret.append(opengl.GLVBO(pointList))
1140 def getObjectCenterPos(self):
1141 if self._selectedObj is None:
1142 return [0.0, 0.0, 0.0]
1143 pos = self._selectedObj.getPosition()
1144 size = self._selectedObj.getSize()
1145 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1147 def getObjectBoundaryCircle(self):
1148 if self._selectedObj is None:
1150 return self._selectedObj.getBoundaryCircle()
1152 def getObjectSize(self):
1153 if self._selectedObj is None:
1154 return [0.0, 0.0, 0.0]
1155 return self._selectedObj.getSize()
1157 def getObjectMatrix(self):
1158 if self._selectedObj is None:
1159 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1160 return self._selectedObj.getMatrix()
1162 class shaderEditor(wx.Dialog):
1163 def __init__(self, parent, callback, v, f):
1164 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1165 self._callback = callback
1166 s = wx.BoxSizer(wx.VERTICAL)
1168 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1169 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1170 s.Add(self._vertex, 1, flag=wx.EXPAND)
1171 s.Add(self._fragment, 1, flag=wx.EXPAND)
1173 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1174 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1176 self.SetPosition(self.GetParent().GetPosition())
1177 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1180 def OnText(self, e):
1181 self._callback(self._vertex.GetValue(), self._fragment.GetValue())