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'
180 shutil.copy(self._gcode.filename, drive[1] + filename)
182 self.notification.message("Failed to save to SD card")
184 self.notification.message("Saved as %s" % (drive[1] + filename), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(drive[1]) else self.notification.message('Safe remove failed...'))
189 self.Bind(wx.EVT_MENU, lambda e: printWindow.printFile(self._gcode.filename), menu.Append(-1, 'Print with USB'))
190 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, 'Save GCode...'))
191 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, 'Slice engine log...'))
195 def showSaveGCode(self):
196 defPath = profile.getPreference('lastFile')
197 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
198 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
199 dlg.SetFilename(defPath)
200 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
201 if dlg.ShowModal() != wx.ID_OK:
204 filename = dlg.GetPath()
208 shutil.copy(self._gcode.filename, filename)
210 self.notification.message("Failed to save")
212 self.notification.message("Saved as %s" % (filename))
214 def _showSliceLog(self):
215 dlg = wx.TextEntryDialog(self, "The slicing engine reported the following", "Engine log...", '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
219 def OnToolSelect(self, button):
220 if self.rotateToolButton.getSelected():
221 self.tool = previewTools.toolRotate(self)
222 elif self.scaleToolButton.getSelected():
223 self.tool = previewTools.toolScale(self)
224 elif self.mirrorToolButton.getSelected():
225 self.tool = previewTools.toolNone(self)
227 self.tool = previewTools.toolNone(self)
228 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
229 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
230 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
231 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
232 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
233 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
234 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
235 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
237 def updateToolButtons(self):
238 if self._selectedObj is None:
242 self.rotateToolButton.setHidden(hidden)
243 self.scaleToolButton.setHidden(hidden)
244 self.mirrorToolButton.setHidden(hidden)
246 self.rotateToolButton.setSelected(False)
247 self.scaleToolButton.setSelected(False)
248 self.mirrorToolButton.setSelected(False)
251 def OnViewChange(self):
252 if self.viewSelection.getValue() == 3:
253 self.viewMode = 'gcode'
254 if self._gcode is not None:
255 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
256 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
257 self._selectObject(None)
258 elif self.viewSelection.getValue() == 1:
259 self.viewMode = 'transparent'
260 elif self.viewSelection.getValue() == 2:
261 self.viewMode = 'xray'
263 self.viewMode = 'normal'
264 self.layerSelect.setHidden(self.viewMode != 'gcode')
267 def OnRotateReset(self, button):
268 if self._selectedObj is None:
270 self._selectedObj.resetRotation()
271 self._scene.pushFree()
272 self._selectObject(self._selectedObj)
275 def OnLayFlat(self, button):
276 if self._selectedObj is None:
278 self._selectedObj.layFlat()
279 self._scene.pushFree()
280 self._selectObject(self._selectedObj)
283 def OnScaleReset(self, button):
284 if self._selectedObj is None:
286 self._selectedObj.resetScale()
287 self._selectObject(self._selectedObj)
288 self.updateProfileToControls()
291 def OnScaleMax(self, button):
292 if self._selectedObj is None:
294 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
295 self._scene.pushFree()
296 self._selectObject(self._selectedObj)
297 self.updateProfileToControls()
300 def OnMirror(self, axis):
301 if self._selectedObj is None:
303 self._selectedObj.mirror(axis)
306 def OnScaleEntry(self, value, axis):
307 if self._selectedObj is None:
313 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
314 self.updateProfileToControls()
315 self._scene.pushFree()
316 self._selectObject(self._selectedObj)
319 def OnScaleEntryMM(self, value, axis):
320 if self._selectedObj is None:
326 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
327 self.updateProfileToControls()
328 self._scene.pushFree()
329 self._selectObject(self._selectedObj)
332 def OnDeleteAll(self, e):
333 while len(self._scene.objects()) > 0:
334 self._deleteObject(self._scene.objects()[0])
335 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
337 def OnMultiply(self, e):
338 if self._focusObj is None:
341 dlg = wx.NumberEntryDialog(self, "How many items do you want?", "Copies", "Multiply", 2, 1, 100)
342 if dlg.ShowModal() != wx.ID_OK:
345 cnt = dlg.GetValue() - 1
351 self._scene.add(newObj)
352 self._scene.centerAll()
353 if not self._scene.checkPlatform(newObj):
358 self.notification.message("Could not create more then %d items" % (n))
359 self._scene.remove(newObj)
360 self._scene.centerAll()
363 def OnSplitObject(self, e):
364 if self._focusObj is None:
366 self._scene.remove(self._focusObj)
367 for obj in self._focusObj.split(self._splitCallback):
369 self._scene.centerAll()
370 self._selectObject(None)
373 def _splitCallback(self, progress):
376 def OnMergeObjects(self, e):
377 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
379 self._scene.merge(self._selectedObj, self._focusObj)
382 def sceneUpdated(self):
383 self._sceneUpdateTimer.Start(1, True)
384 self._slicer.abortSlicer()
385 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
388 def _onRunSlicer(self, e):
389 if self._isSimpleMode:
390 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
391 self._slicer.runSlicer(self._scene)
392 if self._isSimpleMode:
393 profile.resetTempOverride()
395 def _updateSliceProgress(self, progressValue, ready):
396 self.printButton.setDisabled(not ready)
397 if progressValue >= 0.0:
398 self.printButton.setProgressBar(progressValue)
400 self.printButton.setProgressBar(None)
401 if self._gcode is not None:
403 for layerVBOlist in self._gcodeVBOs:
404 for vbo in layerVBOlist:
405 self.glReleaseList.append(vbo)
408 self.printButton.setProgressBar(None)
409 cost = self._slicer.getFilamentCost()
411 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
413 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
414 self._gcode = gcodeInterpreter.gcode()
415 self._gcode.progressCallback = self._gcodeLoadCallback
416 self._thread = threading.Thread(target=self._loadGCode)
417 self._thread.daemon = True
420 self.printButton.setBottomText('')
423 def _loadGCode(self, filename = None):
425 filename = self._slicer.getGCodeFilename()
426 self._gcode.load(filename)
428 def _gcodeLoadCallback(self, progress):
429 if self._gcode is None:
431 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
432 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
433 self.layerSelect.setValue(self.layerSelect.getMaxValue())
435 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
438 def loadScene(self, fileList):
439 for filename in fileList:
441 objList = meshLoader.loadMeshes(filename)
443 traceback.print_exc()
446 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
448 self._scene.centerAll()
449 self._selectObject(obj)
452 def _deleteObject(self, obj):
453 if obj == self._selectedObj:
454 self._selectObject(None)
455 if obj == self._focusObj:
456 self._focusObj = None
457 self._scene.remove(obj)
458 for m in obj._meshList:
459 if m.vbo is not None and m.vbo.decRef():
460 self.glReleaseList.append(m.vbo)
465 def _selectObject(self, obj, zoom = True):
466 if obj != self._selectedObj:
467 self._selectedObj = obj
468 self.updateProfileToControls()
469 self.updateToolButtons()
470 if zoom and obj is not None:
471 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
472 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
473 newZoom = obj.getBoundaryCircle() * 6
474 if newZoom > numpy.max(self._machineSize) * 3:
475 newZoom = numpy.max(self._machineSize) * 3
476 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
478 def updateProfileToControls(self):
479 oldSimpleMode = self._isSimpleMode
480 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
481 if self._isSimpleMode and not oldSimpleMode:
482 self._scene.arrangeAll()
484 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
485 self._objColors[0] = profile.getPreferenceColour('model_colour')
486 self._objColors[1] = profile.getPreferenceColour('model_colour2')
487 self._objColors[2] = profile.getPreferenceColour('model_colour3')
488 self._objColors[3] = profile.getPreferenceColour('model_colour4')
489 self._scene.setMachineSize(self._machineSize)
490 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
491 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'))
493 if self._selectedObj is not None:
494 scale = self._selectedObj.getScale()
495 size = self._selectedObj.getSize()
496 self.scaleXctrl.setValue(round(scale[0], 2))
497 self.scaleYctrl.setValue(round(scale[1], 2))
498 self.scaleZctrl.setValue(round(scale[2], 2))
499 self.scaleXmmctrl.setValue(round(size[0], 2))
500 self.scaleYmmctrl.setValue(round(size[1], 2))
501 self.scaleZmmctrl.setValue(round(size[2], 2))
503 def OnKeyChar(self, keyCode):
504 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
505 if self._selectedObj is not None:
506 self._deleteObject(self._selectedObj)
508 if keyCode == wx.WXK_UP:
509 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
511 elif keyCode == wx.WXK_DOWN:
512 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
514 elif keyCode == wx.WXK_PAGEUP:
515 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
517 elif keyCode == wx.WXK_PAGEDOWN:
518 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
521 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
522 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
523 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
524 from collections import defaultdict
525 from gc import get_objects
526 self._beforeLeakTest = defaultdict(int)
527 for i in get_objects():
528 self._beforeLeakTest[type(i)] += 1
529 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
530 from collections import defaultdict
531 from gc import get_objects
532 self._afterLeakTest = defaultdict(int)
533 for i in get_objects():
534 self._afterLeakTest[type(i)] += 1
535 for k in self._afterLeakTest:
536 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
537 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
539 def ShaderUpdate(self, v, f):
540 s = opengl.GLShader(v, f)
542 self._objectLoadShader.release()
543 self._objectLoadShader = s
544 for obj in self._scene.objects():
545 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
548 def OnMouseDown(self,e):
549 self._mouseX = e.GetX()
550 self._mouseY = e.GetY()
551 self._mouseClick3DPos = self._mouse3Dpos
552 self._mouseClickFocus = self._focusObj
554 self._mouseState = 'doubleClick'
556 self._mouseState = 'dragOrClick'
557 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
558 p0 -= self.getObjectCenterPos() - self._viewTarget
559 p1 -= self.getObjectCenterPos() - self._viewTarget
560 if self.tool.OnDragStart(p0, p1):
561 self._mouseState = 'tool'
562 if self._mouseState == 'dragOrClick':
563 if e.GetButton() == 1:
564 if self._focusObj is not None:
565 self._selectObject(self._focusObj, False)
568 def OnMouseUp(self, e):
569 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
571 if self._mouseState == 'dragOrClick':
572 if e.GetButton() == 1:
573 self._selectObject(self._focusObj)
574 if e.GetButton() == 3:
576 if self._focusObj is not None:
577 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
578 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
579 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
580 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
581 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
582 if len(self._scene.objects()) > 0:
583 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
584 if menu.MenuItemCount > 0:
587 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
588 self._scene.pushFree()
590 elif self._mouseState == 'tool':
591 if self.tempMatrix is not None and self._selectedObj is not None:
592 self._selectedObj.applyMatrix(self.tempMatrix)
593 self._scene.pushFree()
594 self._selectObject(self._selectedObj)
595 self.tempMatrix = None
596 self.tool.OnDragEnd()
598 self._mouseState = None
600 def OnMouseMotion(self,e):
601 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
602 p0 -= self.getObjectCenterPos() - self._viewTarget
603 p1 -= self.getObjectCenterPos() - self._viewTarget
605 if e.Dragging() and self._mouseState is not None:
606 if self._mouseState == 'tool':
607 self.tool.OnDrag(p0, p1)
608 elif not e.LeftIsDown() and e.RightIsDown():
609 self._mouseState = 'drag'
610 if wx.GetKeyState(wx.WXK_SHIFT):
611 a = math.cos(math.radians(self._yaw)) / 3.0
612 b = math.sin(math.radians(self._yaw)) / 3.0
613 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
614 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
615 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
616 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
618 self._yaw += e.GetX() - self._mouseX
619 self._pitch -= e.GetY() - self._mouseY
620 if self._pitch > 170:
624 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
625 self._mouseState = 'drag'
626 self._zoom += e.GetY() - self._mouseY
629 if self._zoom > numpy.max(self._machineSize) * 3:
630 self._zoom = numpy.max(self._machineSize) * 3
631 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
632 self._mouseState = 'dragObject'
633 z = max(0, self._mouseClick3DPos[2])
634 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
635 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
640 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
641 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
642 diff = cursorZ1 - cursorZ0
643 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
644 if not e.Dragging() or self._mouseState != 'tool':
645 self.tool.OnMouseMove(p0, p1)
647 self._mouseX = e.GetX()
648 self._mouseY = e.GetY()
650 def OnMouseWheel(self, e):
651 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
652 delta = max(min(delta,4),-4)
653 self._zoom *= 1.0 - delta / 10.0
656 if self._zoom > numpy.max(self._machineSize) * 3:
657 self._zoom = numpy.max(self._machineSize) * 3
660 def getMouseRay(self, x, y):
661 if self._viewport is None:
662 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
663 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
664 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
665 p0 -= self._viewTarget
666 p1 -= self._viewTarget
669 def _init3DView(self):
670 # set viewing projection
671 size = self.GetSize()
672 glViewport(0, 0, size.GetWidth(), size.GetHeight())
675 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
677 glDisable(GL_RESCALE_NORMAL)
678 glDisable(GL_LIGHTING)
680 glEnable(GL_DEPTH_TEST)
681 glDisable(GL_CULL_FACE)
683 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
685 glClearColor(0.8, 0.8, 0.8, 1.0)
689 glMatrixMode(GL_PROJECTION)
691 aspect = float(size.GetWidth()) / float(size.GetHeight())
692 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
694 glMatrixMode(GL_MODELVIEW)
696 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
699 if machineCom.machineIsConnected():
700 self.printButton._imageID = 6
701 self.printButton._tooltip = 'Print'
702 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
703 self.printButton._imageID = 2
704 self.printButton._tooltip = 'Toolpath to SD'
706 self.printButton._imageID = 3
707 self.printButton._tooltip = 'Save toolpath'
709 if self._animView is not None:
710 self._viewTarget = self._animView.getPosition()
711 if self._animView.isDone():
712 self._animView = None
713 if self._animZoom is not None:
714 self._zoom = self._animZoom.getPosition()
715 if self._animZoom.isDone():
716 self._animZoom = None
717 if self.viewMode == 'gcode' and self._gcode is not None:
719 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1].points[0][2]
722 if self._objectShader is None:
723 self._objectShader = opengl.GLShader("""
724 varying float light_amount;
728 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
729 gl_FrontColor = gl_Color;
731 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
735 varying float light_amount;
739 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
742 self._objectLoadShader = opengl.GLShader("""
743 uniform float intensity;
745 varying float light_amount;
749 vec4 tmp = gl_Vertex;
750 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
751 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
752 gl_Position = gl_ModelViewProjectionMatrix * tmp;
753 gl_FrontColor = gl_Color;
755 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
759 uniform float intensity;
760 varying float light_amount;
764 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
768 glTranslate(0,0,-self._zoom)
769 glRotate(-self._pitch, 1,0,0)
770 glRotate(self._yaw, 0,0,1)
771 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
773 self._viewport = glGetIntegerv(GL_VIEWPORT)
774 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
775 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
777 glClearColor(1,1,1,1)
778 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
780 if self.viewMode != 'gcode':
781 for n in xrange(0, len(self._scene.objects())):
782 obj = self._scene.objects()[n]
783 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
784 self._renderObject(obj)
786 if self._mouseX > -1:
788 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
789 if n < len(self._scene.objects()):
790 self._focusObj = self._scene.objects()[n]
792 self._focusObj = None
793 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
794 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
795 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
796 self._mouse3Dpos -= self._viewTarget
799 glTranslate(0,0,-self._zoom)
800 glRotate(-self._pitch, 1,0,0)
801 glRotate(self._yaw, 0,0,1)
802 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
804 if self.viewMode == 'gcode':
805 if self._gcode is not None:
807 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
809 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
810 for n in xrange(0, drawUpTill):
811 c = 1.0 - float(drawUpTill - n) / 15
813 if len(self._gcodeVBOs) < n + 1:
814 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
815 if time.time() - t > 0.5:
818 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
819 if n == drawUpTill - 1:
820 if len(self._gcodeVBOs[n]) < 6:
821 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
823 self._gcodeVBOs[n][5].render(GL_QUADS)
825 self._gcodeVBOs[n][6].render(GL_QUADS)
826 glColor3f(c/2, c/2, 0.0)
827 self._gcodeVBOs[n][7].render(GL_QUADS)
829 self._gcodeVBOs[n][8].render(GL_QUADS)
830 self._gcodeVBOs[n][9].render(GL_QUADS)
832 self._gcodeVBOs[n][10].render(GL_LINES)
835 self._gcodeVBOs[n][0].render(GL_LINES)
837 self._gcodeVBOs[n][1].render(GL_LINES)
838 glColor3f(c/2, c/2, 0.0)
839 self._gcodeVBOs[n][2].render(GL_LINES)
841 self._gcodeVBOs[n][3].render(GL_LINES)
842 self._gcodeVBOs[n][4].render(GL_LINES)
845 glStencilFunc(GL_ALWAYS, 1, 1)
846 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
848 self._objectShader.bind()
849 for obj in self._scene.objects():
850 if obj._loadAnim is not None:
851 if obj._loadAnim.isDone():
856 if self._focusObj == obj:
858 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
861 if self._selectedObj == obj or self._selectedObj is None:
862 #If we want transparent, then first render a solid black model to remove the printer size lines.
863 if self.viewMode == 'transparent':
864 glColor4f(0, 0, 0, 0)
865 self._renderObject(obj)
867 glBlendFunc(GL_ONE, GL_ONE)
868 glDisable(GL_DEPTH_TEST)
870 if self.viewMode == 'xray':
871 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
872 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
873 glEnable(GL_STENCIL_TEST)
875 if not self._scene.checkPlatform(obj):
876 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
877 self._renderObject(obj)
879 self._renderObject(obj, brightness)
880 glDisable(GL_STENCIL_TEST)
882 glEnable(GL_DEPTH_TEST)
883 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
885 if self.viewMode == 'xray':
888 glEnable(GL_STENCIL_TEST)
889 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
890 glDisable(GL_DEPTH_TEST)
891 for i in xrange(2, 15, 2):
892 glStencilFunc(GL_EQUAL, i, 0xFF)
893 glColor(float(i)/10, float(i)/10, float(i)/5)
895 glVertex3f(-1000,-1000,-10)
896 glVertex3f( 1000,-1000,-10)
897 glVertex3f( 1000, 1000,-10)
898 glVertex3f(-1000, 1000,-10)
900 for i in xrange(1, 15, 2):
901 glStencilFunc(GL_EQUAL, i, 0xFF)
902 glColor(float(i)/10, 0, 0)
904 glVertex3f(-1000,-1000,-10)
905 glVertex3f( 1000,-1000,-10)
906 glVertex3f( 1000, 1000,-10)
907 glVertex3f(-1000, 1000,-10)
910 glDisable(GL_STENCIL_TEST)
911 glEnable(GL_DEPTH_TEST)
913 self._objectShader.unbind()
915 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
917 self._objectLoadShader.bind()
918 glColor4f(0.2, 0.6, 1.0, 1.0)
919 for obj in self._scene.objects():
920 if obj._loadAnim is None:
922 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
923 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
924 self._renderObject(obj)
925 self._objectLoadShader.unbind()
930 if self.viewMode == 'gcode':
933 #Draw the object box-shadow, so you can see where it will collide with other objects.
934 if self._selectedObj is not None and len(self._scene.objects()) > 1:
935 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
937 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
939 glEnable(GL_CULL_FACE)
940 glColor4f(0,0,0,0.12)
942 glVertex3f(-size[0], size[1], 0.1)
943 glVertex3f(-size[0], -size[1], 0.1)
944 glVertex3f( size[0], -size[1], 0.1)
945 glVertex3f( size[0], size[1], 0.1)
947 glDisable(GL_CULL_FACE)
950 #Draw the outline of the selected object, on top of everything else except the GUI.
951 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
952 glDisable(GL_DEPTH_TEST)
953 glEnable(GL_CULL_FACE)
954 glEnable(GL_STENCIL_TEST)
956 glStencilFunc(GL_EQUAL, 0, 255)
958 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
961 self._renderObject(self._selectedObj)
962 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
964 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
965 glDisable(GL_STENCIL_TEST)
966 glDisable(GL_CULL_FACE)
967 glEnable(GL_DEPTH_TEST)
969 if self._selectedObj is not None:
971 pos = self.getObjectCenterPos()
972 glTranslate(pos[0], pos[1], pos[2])
976 def _renderObject(self, obj, brightness = False):
978 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
980 if self.tempMatrix is not None and obj == self._selectedObj:
981 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
982 glMultMatrixf(tempMatrix)
984 offset = obj.getDrawOffset()
985 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
987 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
988 glMultMatrixf(tempMatrix)
991 for m in obj._meshList:
993 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
995 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1000 def _drawMachine(self):
1001 glEnable(GL_CULL_FACE)
1004 if profile.getPreference('machine_type') == 'ultimaker':
1005 glColor4f(1,1,1,0.5)
1006 self._objectShader.bind()
1007 self._renderObject(self._platformMesh)
1008 self._objectShader.unbind()
1010 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1011 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1012 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1013 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1014 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1015 v4 = [ size[0] / 2, size[1] / 2, 0]
1016 v5 = [ size[0] / 2,-size[1] / 2, 0]
1017 v6 = [-size[0] / 2, size[1] / 2, 0]
1018 v7 = [-size[0] / 2,-size[1] / 2, 0]
1020 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1021 glEnableClientState(GL_VERTEX_ARRAY)
1022 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1024 glColor4ub(5, 171, 231, 64)
1025 glDrawArrays(GL_QUADS, 0, 4)
1026 glColor4ub(5, 171, 231, 96)
1027 glDrawArrays(GL_QUADS, 4, 8)
1028 glColor4ub(5, 171, 231, 128)
1029 glDrawArrays(GL_QUADS, 12, 8)
1031 sx = self._machineSize[0]
1032 sy = self._machineSize[1]
1033 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1034 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1039 x1 = max(min(x1, sx/2), -sx/2)
1040 y1 = max(min(y1, sy/2), -sy/2)
1041 x2 = max(min(x2, sx/2), -sx/2)
1042 y2 = max(min(y2, sy/2), -sy/2)
1043 if (x & 1) == (y & 1):
1044 glColor4ub(5, 171, 231, 127)
1046 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1048 glVertex3f(x1, y1, -0.02)
1049 glVertex3f(x2, y1, -0.02)
1050 glVertex3f(x2, y2, -0.02)
1051 glVertex3f(x1, y2, -0.02)
1054 glDisableClientState(GL_VERTEX_ARRAY)
1056 glDisable(GL_CULL_FACE)
1058 def _generateGCodeVBOs(self, layer):
1060 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1061 pointList = numpy.zeros((0,3), numpy.float32)
1063 if path.type == 'extrude' and path.pathType == extrudeType:
1065 a = numpy.concatenate((a[:-1], a[1:]), 1)
1066 a = a.reshape((len(a) * 2, 3))
1067 pointList = numpy.concatenate((pointList, a))
1068 ret.append(opengl.GLVBO(pointList))
1071 def _generateGCodeVBOs2(self, layer):
1072 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1073 filamentArea = math.pi * filamentRadius * filamentRadius
1076 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1077 pointList = numpy.zeros((0,3), numpy.float32)
1079 if path.type == 'extrude' and path.pathType == extrudeType:
1081 if extrudeType == 'FILL':
1084 normal = a[1:] - a[:-1]
1085 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1086 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1089 ePerDist = path.extrusion[1:] / lens
1090 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1092 normal[:,0] *= lineWidth
1093 normal[:,1] *= lineWidth
1095 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1096 b = numpy.concatenate((b, a[1:] + normal), 1)
1097 b = numpy.concatenate((b, a[1:] - normal), 1)
1098 b = numpy.concatenate((b, a[:-1] - normal), 1)
1099 b = numpy.concatenate((b, a[:-1] + normal), 1)
1100 #b = numpy.concatenate((b, a[:-1]), 1)
1101 #b = numpy.concatenate((b, a[:-1]), 1)
1102 b = b.reshape((len(b) * 4, 3))
1104 pointList = numpy.concatenate((pointList, b))
1105 ret.append(opengl.GLVBO(pointList))
1107 pointList = numpy.zeros((0,3), numpy.float32)
1109 if path.type == 'move' or path.type == 'retract':
1111 a = numpy.concatenate((a[:-1], a[1:]), 1)
1112 a = a.reshape((len(a) * 2, 3))
1113 pointList = numpy.concatenate((pointList, a))
1114 ret.append(opengl.GLVBO(pointList))
1118 def getObjectCenterPos(self):
1119 if self._selectedObj is None:
1120 return [0.0, 0.0, 0.0]
1121 pos = self._selectedObj.getPosition()
1122 size = self._selectedObj.getSize()
1123 return [pos[0], pos[1], size[2]/2]
1125 def getObjectBoundaryCircle(self):
1126 if self._selectedObj is None:
1128 return self._selectedObj.getBoundaryCircle()
1130 def getObjectSize(self):
1131 if self._selectedObj is None:
1132 return [0.0, 0.0, 0.0]
1133 return self._selectedObj.getSize()
1135 def getObjectMatrix(self):
1136 if self._selectedObj is None:
1137 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1138 return self._selectedObj.getMatrix()
1140 class shaderEditor(wx.Dialog):
1141 def __init__(self, parent, callback, v, f):
1142 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1143 self._callback = callback
1144 s = wx.BoxSizer(wx.VERTICAL)
1146 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1147 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1148 s.Add(self._vertex, 1, flag=wx.EXPAND)
1149 s.Add(self._fragment, 1, flag=wx.EXPAND)
1151 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1152 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1154 self.SetPosition(self.GetParent().GetPosition())
1155 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1158 def OnText(self, e):
1159 self._callback(self._vertex.GetValue(), self._fragment.GetValue())