1 from __future__ import absolute_import
13 OpenGL.ERROR_CHECKING = False
14 from OpenGL.GLU import *
15 from OpenGL.GL import *
17 from Cura.gui import printWindow
18 from Cura.util import profile
19 from Cura.util import meshLoader
20 from Cura.util import objectScene
21 from Cura.util import resources
22 from Cura.util import sliceEngine
23 from Cura.util import machineCom
24 from Cura.util import removableStorage
25 from Cura.util import gcodeInterpreter
26 from Cura.gui.util import previewTools
27 from Cura.gui.util import opengl
28 from Cura.gui.util import openglGui
30 class SceneView(openglGui.glGuiPanel):
31 def __init__(self, parent):
32 super(SceneView, self).__init__(parent)
37 self._scene = objectScene.Scene()
40 self._objectShader = None
42 self._selectedObj = None
43 self._objColors = [None,None,None,None]
46 self._mouseState = None
47 self._viewTarget = numpy.array([0,0,0], numpy.float32)
50 self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
51 self._platformMesh._drawOffset = numpy.array([0,0,2.5], numpy.float32)
52 self._isSimpleMode = True
55 self._modelMatrix = None
56 self._projMatrix = None
57 self.tempMatrix = None
59 self.openFileButton = openglGui.glButton(self, 4, 'Load', (0,0), self.showLoadModel)
60 self.printButton = openglGui.glButton(self, 6, 'Print', (1,0), self.showPrintWindow)
61 self.printButton.setDisabled(True)
64 self.rotateToolButton = openglGui.glRadioButton(self, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
65 self.scaleToolButton = openglGui.glRadioButton(self, 9, 'Scale', (1,-1), group, self.OnToolSelect)
66 self.mirrorToolButton = openglGui.glRadioButton(self, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
68 self.resetRotationButton = openglGui.glButton(self, 12, 'Reset', (0,-2), self.OnRotateReset)
69 self.layFlatButton = openglGui.glButton(self, 16, 'Lay flat', (0,-3), self.OnLayFlat)
71 self.resetScaleButton = openglGui.glButton(self, 13, 'Reset', (1,-2), self.OnScaleReset)
72 self.scaleMaxButton = openglGui.glButton(self, 17, 'To max', (1,-3), self.OnScaleMax)
74 self.mirrorXButton = openglGui.glButton(self, 14, 'Mirror X', (2,-2), lambda button: self.OnMirror(0))
75 self.mirrorYButton = openglGui.glButton(self, 18, 'Mirror Y', (2,-3), lambda button: self.OnMirror(1))
76 self.mirrorZButton = openglGui.glButton(self, 22, 'Mirror Z', (2,-4), lambda button: self.OnMirror(2))
78 self.rotateToolButton.setExpandArrow(True)
79 self.scaleToolButton.setExpandArrow(True)
80 self.mirrorToolButton.setExpandArrow(True)
82 self.scaleForm = openglGui.glFrame(self, (2, -2))
83 openglGui.glGuiLayoutGrid(self.scaleForm)
84 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
85 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
86 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
87 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
88 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
89 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
90 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
91 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
92 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
93 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
94 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
95 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
96 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
97 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
99 self.viewSelection = openglGui.glComboButton(self, 'View mode', [7,11,15,23], ['Normal', 'Transparent', 'X-Ray', 'Layers'], (-1,0), self.OnViewChange)
100 self.layerSelect = openglGui.glSlider(self, 100, 0, 100, (-1,-2), lambda : self.QueueRefresh())
102 self.notification = openglGui.glNotification(self, (0, 0))
104 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
105 self._sceneUpdateTimer = wx.Timer(self)
106 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
107 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
111 self.updateToolButtons()
112 self.updateProfileToControls()
114 def showLoadModel(self, button = 1):
116 dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
117 dlg.SetWildcard(meshLoader.loadWildcardFilter() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
118 if dlg.ShowModal() != wx.ID_OK:
121 filename = dlg.GetPath()
123 if not(os.path.exists(filename)):
125 profile.putPreference('lastFile', filename)
126 self.GetParent().GetParent().GetParent().addToModelMRU(filename)
127 ext = filename[filename.rfind('.')+1:].upper()
128 if ext == 'G' or ext == 'GCODE':
129 if self._gcode is not None:
131 for layerVBOlist in self._gcodeVBOs:
132 for vbo in layerVBOlist:
133 self.glReleaseList.append(vbo)
135 self._gcode = gcodeInterpreter.gcode()
136 self._gcode.progressCallback = self._gcodeLoadCallback
137 self._thread = threading.Thread(target=self._loadGCode, args=(filename,))
138 self._thread.daemon = True
140 self.printButton.setBottomText('')
141 self.viewSelection.setValue(3)
142 self.printButton.setDisabled(False)
145 if self.viewSelection.getValue() == 3:
146 self.viewSelection.setValue(0)
148 self.loadScene([filename])
150 def showSaveModel(self):
151 if len(self._scene.objects()) < 1:
153 dlg=wx.FileDialog(self, 'Save 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
154 dlg.SetWildcard(meshLoader.saveWildcardFilter())
155 if dlg.ShowModal() != wx.ID_OK:
158 filename = dlg.GetPath()
160 meshLoader.saveMeshes(filename, self._scene.objects())
162 def showPrintWindow(self, button):
164 if machineCom.machineIsConnected():
165 printWindow.printFile(self._gcode.filename)
166 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
167 drives = removableStorage.getPossibleSDcardDrives()
169 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))
170 if dlg.ShowModal() != wx.ID_OK:
173 drive = drives[dlg.GetSelection()]
177 filename = os.path.basename(profile.getPreference('lastFile'))
178 filename = filename[0:filename.rfind('.')] + '.gcode'
179 threading.Thread(target=self._copyFile,args=(self._gcode.filename, drive[1] + filename, drive[1])).start()
184 self.Bind(wx.EVT_MENU, lambda e: printWindow.printFile(self._gcode.filename), menu.Append(-1, 'Print with USB'))
185 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, 'Save GCode...'))
186 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, 'Slice engine log...'))
190 def showSaveGCode(self):
191 defPath = profile.getPreference('lastFile')
192 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
193 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
194 dlg.SetFilename(os.path.basename(defPath))
195 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
196 if dlg.ShowModal() != wx.ID_OK:
199 filename = dlg.GetPath()
202 threading.Thread(target=self._copyFile,args=(self._gcode.filename, filename)).start()
204 def _copyFile(self, fileA, fileB, allowEject = False):
206 size = float(os.stat(fileA).st_size)
207 with open(fileA, 'rb') as fsrc:
208 with open(fileB, 'wb') as fdst:
210 buf = fsrc.read(16*1024)
214 self.printButton.setProgressBar(float(fsrc.tell()) / size)
217 self.notification.message("Failed to save")
220 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...'))
222 self.notification.message("Saved as %s" % (fileB))
223 self.printButton.setProgressBar(None)
226 def _showSliceLog(self):
227 dlg = wx.TextEntryDialog(self, "The slicing engine reported the following", "Engine log...", '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
231 def OnToolSelect(self, button):
232 if self.rotateToolButton.getSelected():
233 self.tool = previewTools.toolRotate(self)
234 elif self.scaleToolButton.getSelected():
235 self.tool = previewTools.toolScale(self)
236 elif self.mirrorToolButton.getSelected():
237 self.tool = previewTools.toolNone(self)
239 self.tool = previewTools.toolNone(self)
240 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
241 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
242 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
243 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
244 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
245 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
246 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
247 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
249 def updateToolButtons(self):
250 if self._selectedObj is None:
254 self.rotateToolButton.setHidden(hidden)
255 self.scaleToolButton.setHidden(hidden)
256 self.mirrorToolButton.setHidden(hidden)
258 self.rotateToolButton.setSelected(False)
259 self.scaleToolButton.setSelected(False)
260 self.mirrorToolButton.setSelected(False)
263 def OnViewChange(self):
264 if self.viewSelection.getValue() == 3:
265 self.viewMode = 'gcode'
266 if self._gcode is not None:
267 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
268 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
269 self._selectObject(None)
270 elif self.viewSelection.getValue() == 1:
271 self.viewMode = 'transparent'
272 elif self.viewSelection.getValue() == 2:
273 self.viewMode = 'xray'
275 self.viewMode = 'normal'
276 self.layerSelect.setHidden(self.viewMode != 'gcode')
279 def OnRotateReset(self, button):
280 if self._selectedObj is None:
282 self._selectedObj.resetRotation()
283 self._scene.pushFree()
284 self._selectObject(self._selectedObj)
287 def OnLayFlat(self, button):
288 if self._selectedObj is None:
290 self._selectedObj.layFlat()
291 self._scene.pushFree()
292 self._selectObject(self._selectedObj)
295 def OnScaleReset(self, button):
296 if self._selectedObj is None:
298 self._selectedObj.resetScale()
299 self._selectObject(self._selectedObj)
300 self.updateProfileToControls()
303 def OnScaleMax(self, button):
304 if self._selectedObj is None:
306 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
307 self._scene.pushFree()
308 self._selectObject(self._selectedObj)
309 self.updateProfileToControls()
312 def OnMirror(self, axis):
313 if self._selectedObj is None:
315 self._selectedObj.mirror(axis)
318 def OnScaleEntry(self, value, axis):
319 if self._selectedObj is None:
325 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
326 self.updateProfileToControls()
327 self._scene.pushFree()
328 self._selectObject(self._selectedObj)
331 def OnScaleEntryMM(self, value, axis):
332 if self._selectedObj is None:
338 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
339 self.updateProfileToControls()
340 self._scene.pushFree()
341 self._selectObject(self._selectedObj)
344 def OnDeleteAll(self, e):
345 while len(self._scene.objects()) > 0:
346 self._deleteObject(self._scene.objects()[0])
347 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
349 def OnMultiply(self, e):
350 if self._focusObj is None:
353 dlg = wx.NumberEntryDialog(self, "How many items do you want?", "Copies", "Multiply", 2, 1, 100)
354 if dlg.ShowModal() != wx.ID_OK:
357 cnt = dlg.GetValue() - 1
363 self._scene.add(newObj)
364 self._scene.centerAll()
365 if not self._scene.checkPlatform(newObj):
370 self.notification.message("Could not create more then %d items" % (n))
371 self._scene.remove(newObj)
372 self._scene.centerAll()
375 def OnSplitObject(self, e):
376 if self._focusObj is None:
378 self._scene.remove(self._focusObj)
379 for obj in self._focusObj.split(self._splitCallback):
381 self._scene.centerAll()
382 self._selectObject(None)
385 def _splitCallback(self, progress):
388 def OnMergeObjects(self, e):
389 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
391 self._scene.merge(self._selectedObj, self._focusObj)
394 def sceneUpdated(self):
395 self._sceneUpdateTimer.Start(1, True)
396 self._slicer.abortSlicer()
397 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
400 def _onRunSlicer(self, e):
401 if self._isSimpleMode:
402 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
403 self._slicer.runSlicer(self._scene)
404 if self._isSimpleMode:
405 profile.resetTempOverride()
407 def _updateSliceProgress(self, progressValue, ready):
408 self.printButton.setDisabled(not ready)
409 if progressValue >= 0.0:
410 self.printButton.setProgressBar(progressValue)
412 self.printButton.setProgressBar(None)
413 if self._gcode is not None:
415 for layerVBOlist in self._gcodeVBOs:
416 for vbo in layerVBOlist:
417 self.glReleaseList.append(vbo)
420 self.printButton.setProgressBar(None)
421 cost = self._slicer.getFilamentCost()
423 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
425 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
426 self._gcode = gcodeInterpreter.gcode()
427 self._gcode.progressCallback = self._gcodeLoadCallback
428 self._thread = threading.Thread(target=self._loadGCode)
429 self._thread.daemon = True
432 self.printButton.setBottomText('')
435 def _loadGCode(self, filename = None):
437 filename = self._slicer.getGCodeFilename()
438 self._gcode.load(filename)
440 def _gcodeLoadCallback(self, progress):
441 if self._gcode is None:
443 if len(self._gcode.layerList) % 5 == 0:
445 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
446 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
447 self.layerSelect.setValue(self.layerSelect.getMaxValue())
449 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
452 def loadScene(self, fileList):
453 for filename in fileList:
455 objList = meshLoader.loadMeshes(filename)
457 traceback.print_exc()
460 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
462 self._scene.centerAll()
463 self._selectObject(obj)
466 def _deleteObject(self, obj):
467 if obj == self._selectedObj:
468 self._selectObject(None)
469 if obj == self._focusObj:
470 self._focusObj = None
471 self._scene.remove(obj)
472 for m in obj._meshList:
473 if m.vbo is not None and m.vbo.decRef():
474 self.glReleaseList.append(m.vbo)
479 def _selectObject(self, obj, zoom = True):
480 if obj != self._selectedObj:
481 self._selectedObj = obj
482 self.updateProfileToControls()
483 self.updateToolButtons()
484 if zoom and obj is not None:
485 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
486 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
487 newZoom = obj.getBoundaryCircle() * 6
488 if newZoom > numpy.max(self._machineSize) * 3:
489 newZoom = numpy.max(self._machineSize) * 3
490 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
492 def updateProfileToControls(self):
493 oldSimpleMode = self._isSimpleMode
494 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
495 if self._isSimpleMode and not oldSimpleMode:
496 self._scene.arrangeAll()
498 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
499 self._objColors[0] = profile.getPreferenceColour('model_colour')
500 self._objColors[1] = profile.getPreferenceColour('model_colour2')
501 self._objColors[2] = profile.getPreferenceColour('model_colour3')
502 self._objColors[3] = profile.getPreferenceColour('model_colour4')
503 self._scene.setMachineSize(self._machineSize)
504 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
505 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'))
507 if self._selectedObj is not None:
508 scale = self._selectedObj.getScale()
509 size = self._selectedObj.getSize()
510 self.scaleXctrl.setValue(round(scale[0], 2))
511 self.scaleYctrl.setValue(round(scale[1], 2))
512 self.scaleZctrl.setValue(round(scale[2], 2))
513 self.scaleXmmctrl.setValue(round(size[0], 2))
514 self.scaleYmmctrl.setValue(round(size[1], 2))
515 self.scaleZmmctrl.setValue(round(size[2], 2))
517 def OnKeyChar(self, keyCode):
518 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
519 if self._selectedObj is not None:
520 self._deleteObject(self._selectedObj)
522 if keyCode == wx.WXK_UP:
523 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
525 elif keyCode == wx.WXK_DOWN:
526 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
528 elif keyCode == wx.WXK_PAGEUP:
529 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
531 elif keyCode == wx.WXK_PAGEDOWN:
532 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
535 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
536 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
537 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
538 from collections import defaultdict
539 from gc import get_objects
540 self._beforeLeakTest = defaultdict(int)
541 for i in get_objects():
542 self._beforeLeakTest[type(i)] += 1
543 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
544 from collections import defaultdict
545 from gc import get_objects
546 self._afterLeakTest = defaultdict(int)
547 for i in get_objects():
548 self._afterLeakTest[type(i)] += 1
549 for k in self._afterLeakTest:
550 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
551 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
553 def ShaderUpdate(self, v, f):
554 s = opengl.GLShader(v, f)
556 self._objectLoadShader.release()
557 self._objectLoadShader = s
558 for obj in self._scene.objects():
559 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
562 def OnMouseDown(self,e):
563 self._mouseX = e.GetX()
564 self._mouseY = e.GetY()
565 self._mouseClick3DPos = self._mouse3Dpos
566 self._mouseClickFocus = self._focusObj
568 self._mouseState = 'doubleClick'
570 self._mouseState = 'dragOrClick'
571 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
572 p0 -= self.getObjectCenterPos() - self._viewTarget
573 p1 -= self.getObjectCenterPos() - self._viewTarget
574 if self.tool.OnDragStart(p0, p1):
575 self._mouseState = 'tool'
576 if self._mouseState == 'dragOrClick':
577 if e.GetButton() == 1:
578 if self._focusObj is not None:
579 self._selectObject(self._focusObj, False)
582 def OnMouseUp(self, e):
583 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
585 if self._mouseState == 'dragOrClick':
586 if e.GetButton() == 1:
587 self._selectObject(self._focusObj)
588 if e.GetButton() == 3:
590 if self._focusObj is not None:
591 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
592 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
593 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
594 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
595 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
596 if len(self._scene.objects()) > 0:
597 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
598 if menu.MenuItemCount > 0:
601 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
602 self._scene.pushFree()
604 elif self._mouseState == 'tool':
605 if self.tempMatrix is not None and self._selectedObj is not None:
606 self._selectedObj.applyMatrix(self.tempMatrix)
607 self._scene.pushFree()
608 self._selectObject(self._selectedObj)
609 self.tempMatrix = None
610 self.tool.OnDragEnd()
612 self._mouseState = None
614 def OnMouseMotion(self,e):
615 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
616 p0 -= self.getObjectCenterPos() - self._viewTarget
617 p1 -= self.getObjectCenterPos() - self._viewTarget
619 if e.Dragging() and self._mouseState is not None:
620 if self._mouseState == 'tool':
621 self.tool.OnDrag(p0, p1)
622 elif not e.LeftIsDown() and e.RightIsDown():
623 self._mouseState = 'drag'
624 if wx.GetKeyState(wx.WXK_SHIFT):
625 a = math.cos(math.radians(self._yaw)) / 3.0
626 b = math.sin(math.radians(self._yaw)) / 3.0
627 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
628 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
629 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
630 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
632 self._yaw += e.GetX() - self._mouseX
633 self._pitch -= e.GetY() - self._mouseY
634 if self._pitch > 170:
638 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
639 self._mouseState = 'drag'
640 self._zoom += e.GetY() - self._mouseY
643 if self._zoom > numpy.max(self._machineSize) * 3:
644 self._zoom = numpy.max(self._machineSize) * 3
645 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
646 self._mouseState = 'dragObject'
647 z = max(0, self._mouseClick3DPos[2])
648 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
649 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
654 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
655 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
656 diff = cursorZ1 - cursorZ0
657 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
658 if not e.Dragging() or self._mouseState != 'tool':
659 self.tool.OnMouseMove(p0, p1)
661 self._mouseX = e.GetX()
662 self._mouseY = e.GetY()
664 def OnMouseWheel(self, e):
665 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
666 delta = max(min(delta,4),-4)
667 self._zoom *= 1.0 - delta / 10.0
670 if self._zoom > numpy.max(self._machineSize) * 3:
671 self._zoom = numpy.max(self._machineSize) * 3
674 def getMouseRay(self, x, y):
675 if self._viewport is None:
676 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
677 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
678 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
679 p0 -= self._viewTarget
680 p1 -= self._viewTarget
683 def _init3DView(self):
684 # set viewing projection
685 size = self.GetSize()
686 glViewport(0, 0, size.GetWidth(), size.GetHeight())
689 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
691 glDisable(GL_RESCALE_NORMAL)
692 glDisable(GL_LIGHTING)
694 glEnable(GL_DEPTH_TEST)
695 glDisable(GL_CULL_FACE)
697 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
699 glClearColor(0.8, 0.8, 0.8, 1.0)
703 glMatrixMode(GL_PROJECTION)
705 aspect = float(size.GetWidth()) / float(size.GetHeight())
706 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
708 glMatrixMode(GL_MODELVIEW)
710 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
713 if machineCom.machineIsConnected():
714 self.printButton._imageID = 6
715 self.printButton._tooltip = 'Print'
716 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
717 self.printButton._imageID = 2
718 self.printButton._tooltip = 'Toolpath to SD'
720 self.printButton._imageID = 3
721 self.printButton._tooltip = 'Save toolpath'
723 if self._animView is not None:
724 self._viewTarget = self._animView.getPosition()
725 if self._animView.isDone():
726 self._animView = None
727 if self._animZoom is not None:
728 self._zoom = self._animZoom.getPosition()
729 if self._animZoom.isDone():
730 self._animZoom = None
731 if self.viewMode == 'gcode' and self._gcode is not None:
733 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
736 if self._objectShader is None:
737 self._objectShader = opengl.GLShader("""
738 varying float light_amount;
742 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
743 gl_FrontColor = gl_Color;
745 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
749 varying float light_amount;
753 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
756 self._objectLoadShader = opengl.GLShader("""
757 uniform float intensity;
759 varying float light_amount;
763 vec4 tmp = gl_Vertex;
764 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
765 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
766 gl_Position = gl_ModelViewProjectionMatrix * tmp;
767 gl_FrontColor = gl_Color;
769 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
773 uniform float intensity;
774 varying float light_amount;
778 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
782 glTranslate(0,0,-self._zoom)
783 glRotate(-self._pitch, 1,0,0)
784 glRotate(self._yaw, 0,0,1)
785 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
787 self._viewport = glGetIntegerv(GL_VIEWPORT)
788 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
789 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
791 glClearColor(1,1,1,1)
792 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
794 if self.viewMode != 'gcode':
795 for n in xrange(0, len(self._scene.objects())):
796 obj = self._scene.objects()[n]
797 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
798 self._renderObject(obj)
800 if self._mouseX > -1:
802 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
803 if n < len(self._scene.objects()):
804 self._focusObj = self._scene.objects()[n]
806 self._focusObj = None
807 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
808 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
809 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
810 self._mouse3Dpos -= self._viewTarget
813 glTranslate(0,0,-self._zoom)
814 glRotate(-self._pitch, 1,0,0)
815 glRotate(self._yaw, 0,0,1)
816 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
818 if self.viewMode == 'gcode':
819 if self._gcode is not None:
821 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
823 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
824 for n in xrange(0, drawUpTill):
825 c = 1.0 - float(drawUpTill - n) / 15
827 if len(self._gcodeVBOs) < n + 1:
828 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
829 if time.time() - t > 0.5:
832 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
833 if n == drawUpTill - 1:
834 if len(self._gcodeVBOs[n]) < 6:
835 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
837 self._gcodeVBOs[n][5].render(GL_QUADS)
839 self._gcodeVBOs[n][6].render(GL_QUADS)
840 glColor3f(c/2, c/2, 0.0)
841 self._gcodeVBOs[n][7].render(GL_QUADS)
843 self._gcodeVBOs[n][8].render(GL_QUADS)
844 self._gcodeVBOs[n][9].render(GL_QUADS)
846 self._gcodeVBOs[n][10].render(GL_LINES)
849 self._gcodeVBOs[n][0].render(GL_LINES)
851 self._gcodeVBOs[n][1].render(GL_LINES)
852 glColor3f(c/2, c/2, 0.0)
853 self._gcodeVBOs[n][2].render(GL_LINES)
855 self._gcodeVBOs[n][3].render(GL_LINES)
856 self._gcodeVBOs[n][4].render(GL_LINES)
859 glStencilFunc(GL_ALWAYS, 1, 1)
860 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
862 self._objectShader.bind()
863 for obj in self._scene.objects():
864 if obj._loadAnim is not None:
865 if obj._loadAnim.isDone():
870 if self._focusObj == obj:
872 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
875 if self._selectedObj == obj or self._selectedObj is None:
876 #If we want transparent, then first render a solid black model to remove the printer size lines.
877 if self.viewMode == 'transparent':
878 glColor4f(0, 0, 0, 0)
879 self._renderObject(obj)
881 glBlendFunc(GL_ONE, GL_ONE)
882 glDisable(GL_DEPTH_TEST)
884 if self.viewMode == 'xray':
885 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
886 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
887 glEnable(GL_STENCIL_TEST)
889 if not self._scene.checkPlatform(obj):
890 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
891 self._renderObject(obj)
893 self._renderObject(obj, brightness)
894 glDisable(GL_STENCIL_TEST)
896 glEnable(GL_DEPTH_TEST)
897 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
899 if self.viewMode == 'xray':
902 glEnable(GL_STENCIL_TEST)
903 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
904 glDisable(GL_DEPTH_TEST)
905 for i in xrange(2, 15, 2):
906 glStencilFunc(GL_EQUAL, i, 0xFF)
907 glColor(float(i)/10, float(i)/10, float(i)/5)
909 glVertex3f(-1000,-1000,-10)
910 glVertex3f( 1000,-1000,-10)
911 glVertex3f( 1000, 1000,-10)
912 glVertex3f(-1000, 1000,-10)
914 for i in xrange(1, 15, 2):
915 glStencilFunc(GL_EQUAL, i, 0xFF)
916 glColor(float(i)/10, 0, 0)
918 glVertex3f(-1000,-1000,-10)
919 glVertex3f( 1000,-1000,-10)
920 glVertex3f( 1000, 1000,-10)
921 glVertex3f(-1000, 1000,-10)
924 glDisable(GL_STENCIL_TEST)
925 glEnable(GL_DEPTH_TEST)
927 self._objectShader.unbind()
929 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
931 self._objectLoadShader.bind()
932 glColor4f(0.2, 0.6, 1.0, 1.0)
933 for obj in self._scene.objects():
934 if obj._loadAnim is None:
936 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
937 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
938 self._renderObject(obj)
939 self._objectLoadShader.unbind()
944 if self.viewMode == 'gcode':
947 #Draw the object box-shadow, so you can see where it will collide with other objects.
948 if self._selectedObj is not None and len(self._scene.objects()) > 1:
949 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
951 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
953 glEnable(GL_CULL_FACE)
954 glColor4f(0,0,0,0.12)
956 glVertex3f(-size[0], size[1], 0.1)
957 glVertex3f(-size[0], -size[1], 0.1)
958 glVertex3f( size[0], -size[1], 0.1)
959 glVertex3f( size[0], size[1], 0.1)
961 glDisable(GL_CULL_FACE)
964 #Draw the outline of the selected object, on top of everything else except the GUI.
965 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
966 glDisable(GL_DEPTH_TEST)
967 glEnable(GL_CULL_FACE)
968 glEnable(GL_STENCIL_TEST)
970 glStencilFunc(GL_EQUAL, 0, 255)
972 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
975 self._renderObject(self._selectedObj)
976 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
978 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
979 glDisable(GL_STENCIL_TEST)
980 glDisable(GL_CULL_FACE)
981 glEnable(GL_DEPTH_TEST)
983 if self._selectedObj is not None:
985 pos = self.getObjectCenterPos()
986 glTranslate(pos[0], pos[1], pos[2])
990 def _renderObject(self, obj, brightness = False):
992 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
994 if self.tempMatrix is not None and obj == self._selectedObj:
995 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
996 glMultMatrixf(tempMatrix)
998 offset = obj.getDrawOffset()
999 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1001 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1002 glMultMatrixf(tempMatrix)
1005 for m in obj._meshList:
1007 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1009 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1014 def _drawMachine(self):
1015 glEnable(GL_CULL_FACE)
1018 if profile.getPreference('machine_type') == 'ultimaker':
1019 glColor4f(1,1,1,0.5)
1020 self._objectShader.bind()
1021 self._renderObject(self._platformMesh)
1022 self._objectShader.unbind()
1024 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1025 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1026 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1027 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1028 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1029 v4 = [ size[0] / 2, size[1] / 2, 0]
1030 v5 = [ size[0] / 2,-size[1] / 2, 0]
1031 v6 = [-size[0] / 2, size[1] / 2, 0]
1032 v7 = [-size[0] / 2,-size[1] / 2, 0]
1034 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1035 glEnableClientState(GL_VERTEX_ARRAY)
1036 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1038 glColor4ub(5, 171, 231, 64)
1039 glDrawArrays(GL_QUADS, 0, 4)
1040 glColor4ub(5, 171, 231, 96)
1041 glDrawArrays(GL_QUADS, 4, 8)
1042 glColor4ub(5, 171, 231, 128)
1043 glDrawArrays(GL_QUADS, 12, 8)
1045 sx = self._machineSize[0]
1046 sy = self._machineSize[1]
1047 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1048 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1053 x1 = max(min(x1, sx/2), -sx/2)
1054 y1 = max(min(y1, sy/2), -sy/2)
1055 x2 = max(min(x2, sx/2), -sx/2)
1056 y2 = max(min(y2, sy/2), -sy/2)
1057 if (x & 1) == (y & 1):
1058 glColor4ub(5, 171, 231, 127)
1060 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1062 glVertex3f(x1, y1, -0.02)
1063 glVertex3f(x2, y1, -0.02)
1064 glVertex3f(x2, y2, -0.02)
1065 glVertex3f(x1, y2, -0.02)
1068 glDisableClientState(GL_VERTEX_ARRAY)
1070 glDisable(GL_CULL_FACE)
1072 def _generateGCodeVBOs(self, layer):
1074 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1075 pointList = numpy.zeros((0,3), numpy.float32)
1077 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1079 a = numpy.concatenate((a[:-1], a[1:]), 1)
1080 a = a.reshape((len(a) * 2, 3))
1081 pointList = numpy.concatenate((pointList, a))
1082 ret.append(opengl.GLVBO(pointList))
1085 def _generateGCodeVBOs2(self, layer):
1086 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1087 filamentArea = math.pi * filamentRadius * filamentRadius
1090 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1091 pointList = numpy.zeros((0,3), numpy.float32)
1093 if path['type'] == 'extrude' and path['pathType'] == extrudeType:
1095 if extrudeType == 'FILL':
1098 normal = a[1:] - a[:-1]
1099 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1100 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1103 ePerDist = path['extrusion'][1:] / lens
1104 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1106 normal[:,0] *= lineWidth
1107 normal[:,1] *= lineWidth
1109 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1110 b = numpy.concatenate((b, a[1:] + normal), 1)
1111 b = numpy.concatenate((b, a[1:] - normal), 1)
1112 b = numpy.concatenate((b, a[:-1] - normal), 1)
1113 b = numpy.concatenate((b, a[:-1] + normal), 1)
1114 #b = numpy.concatenate((b, a[:-1]), 1)
1115 #b = numpy.concatenate((b, a[:-1]), 1)
1116 b = b.reshape((len(b) * 4, 3))
1118 pointList = numpy.concatenate((pointList, b))
1119 ret.append(opengl.GLVBO(pointList))
1121 pointList = numpy.zeros((0,3), numpy.float32)
1123 if path['type'] == 'move' or path['type'] == 'retract':
1125 a = numpy.concatenate((a[:-1], a[1:]), 1)
1126 a = a.reshape((len(a) * 2, 3))
1127 pointList = numpy.concatenate((pointList, a))
1128 ret.append(opengl.GLVBO(pointList))
1132 def getObjectCenterPos(self):
1133 if self._selectedObj is None:
1134 return [0.0, 0.0, 0.0]
1135 pos = self._selectedObj.getPosition()
1136 size = self._selectedObj.getSize()
1137 return [pos[0], pos[1], size[2]/2]
1139 def getObjectBoundaryCircle(self):
1140 if self._selectedObj is None:
1142 return self._selectedObj.getBoundaryCircle()
1144 def getObjectSize(self):
1145 if self._selectedObj is None:
1146 return [0.0, 0.0, 0.0]
1147 return self._selectedObj.getSize()
1149 def getObjectMatrix(self):
1150 if self._selectedObj is None:
1151 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1152 return self._selectedObj.getMatrix()
1154 class shaderEditor(wx.Dialog):
1155 def __init__(self, parent, callback, v, f):
1156 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1157 self._callback = callback
1158 s = wx.BoxSizer(wx.VERTICAL)
1160 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1161 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1162 s.Add(self._vertex, 1, flag=wx.EXPAND)
1163 s.Add(self._fragment, 1, flag=wx.EXPAND)
1165 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1166 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1168 self.SetPosition(self.GetParent().GetPosition())
1169 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1172 def OnText(self, e):
1173 self._callback(self._vertex.GetValue(), self._fragment.GetValue())