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)
833 self._gcodeVBOs[n][0].render(GL_LINES)
835 self._gcodeVBOs[n][1].render(GL_LINES)
836 glColor3f(c/2, c/2, 0.0)
837 self._gcodeVBOs[n][2].render(GL_LINES)
839 self._gcodeVBOs[n][3].render(GL_LINES)
840 self._gcodeVBOs[n][4].render(GL_LINES)
843 glStencilFunc(GL_ALWAYS, 1, 1)
844 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
846 self._objectShader.bind()
847 for obj in self._scene.objects():
848 if obj._loadAnim is not None:
849 if obj._loadAnim.isDone():
854 if self._focusObj == obj:
856 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
859 if self._selectedObj == obj or self._selectedObj is None:
860 #If we want transparent, then first render a solid black model to remove the printer size lines.
861 if self.viewMode == 'transparent':
862 glColor4f(0, 0, 0, 0)
863 self._renderObject(obj)
865 glBlendFunc(GL_ONE, GL_ONE)
866 glDisable(GL_DEPTH_TEST)
868 if self.viewMode == 'xray':
869 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
870 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
871 glEnable(GL_STENCIL_TEST)
873 if not self._scene.checkPlatform(obj):
874 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
875 self._renderObject(obj)
877 self._renderObject(obj, brightness)
878 glDisable(GL_STENCIL_TEST)
880 glEnable(GL_DEPTH_TEST)
881 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
883 if self.viewMode == 'xray':
886 glEnable(GL_STENCIL_TEST)
887 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
888 glDisable(GL_DEPTH_TEST)
889 for i in xrange(2, 15, 2):
890 glStencilFunc(GL_EQUAL, i, 0xFF)
891 glColor(float(i)/10, float(i)/10, float(i)/5)
893 glVertex3f(-1000,-1000,-10)
894 glVertex3f( 1000,-1000,-10)
895 glVertex3f( 1000, 1000,-10)
896 glVertex3f(-1000, 1000,-10)
898 for i in xrange(1, 15, 2):
899 glStencilFunc(GL_EQUAL, i, 0xFF)
900 glColor(float(i)/10, 0, 0)
902 glVertex3f(-1000,-1000,-10)
903 glVertex3f( 1000,-1000,-10)
904 glVertex3f( 1000, 1000,-10)
905 glVertex3f(-1000, 1000,-10)
908 glDisable(GL_STENCIL_TEST)
909 glEnable(GL_DEPTH_TEST)
911 self._objectShader.unbind()
913 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
915 self._objectLoadShader.bind()
916 glColor4f(0.2, 0.6, 1.0, 1.0)
917 for obj in self._scene.objects():
918 if obj._loadAnim is None:
920 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
921 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
922 self._renderObject(obj)
923 self._objectLoadShader.unbind()
928 if self.viewMode == 'gcode':
931 #Draw the object box-shadow, so you can see where it will collide with other objects.
932 if self._selectedObj is not None and len(self._scene.objects()) > 1:
933 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
935 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
937 glEnable(GL_CULL_FACE)
938 glColor4f(0,0,0,0.12)
940 glVertex3f(-size[0], size[1], 0.1)
941 glVertex3f(-size[0], -size[1], 0.1)
942 glVertex3f( size[0], -size[1], 0.1)
943 glVertex3f( size[0], size[1], 0.1)
945 glDisable(GL_CULL_FACE)
948 #Draw the outline of the selected object, on top of everything else except the GUI.
949 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
950 glDisable(GL_DEPTH_TEST)
951 glEnable(GL_CULL_FACE)
952 glEnable(GL_STENCIL_TEST)
954 glStencilFunc(GL_EQUAL, 0, 255)
956 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
959 self._renderObject(self._selectedObj)
960 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
962 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
963 glDisable(GL_STENCIL_TEST)
964 glDisable(GL_CULL_FACE)
965 glEnable(GL_DEPTH_TEST)
967 if self._selectedObj is not None:
969 pos = self.getObjectCenterPos()
970 glTranslate(pos[0], pos[1], pos[2])
974 def _renderObject(self, obj, brightness = False):
976 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
978 if self.tempMatrix is not None and obj == self._selectedObj:
979 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
980 glMultMatrixf(tempMatrix)
982 offset = obj.getDrawOffset()
983 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
985 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
986 glMultMatrixf(tempMatrix)
989 for m in obj._meshList:
991 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
993 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
998 def _drawMachine(self):
999 glEnable(GL_CULL_FACE)
1002 if profile.getPreference('machine_type') == 'ultimaker':
1003 glColor4f(1,1,1,0.5)
1004 self._objectShader.bind()
1005 self._renderObject(self._platformMesh)
1006 self._objectShader.unbind()
1008 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
1009 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1010 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1011 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1012 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1013 v4 = [ size[0] / 2, size[1] / 2, 0]
1014 v5 = [ size[0] / 2,-size[1] / 2, 0]
1015 v6 = [-size[0] / 2, size[1] / 2, 0]
1016 v7 = [-size[0] / 2,-size[1] / 2, 0]
1018 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1019 glEnableClientState(GL_VERTEX_ARRAY)
1020 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1022 glColor4ub(5, 171, 231, 64)
1023 glDrawArrays(GL_QUADS, 0, 4)
1024 glColor4ub(5, 171, 231, 96)
1025 glDrawArrays(GL_QUADS, 4, 8)
1026 glColor4ub(5, 171, 231, 128)
1027 glDrawArrays(GL_QUADS, 12, 8)
1029 sx = self._machineSize[0]
1030 sy = self._machineSize[1]
1031 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1032 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1037 x1 = max(min(x1, sx/2), -sx/2)
1038 y1 = max(min(y1, sy/2), -sy/2)
1039 x2 = max(min(x2, sx/2), -sx/2)
1040 y2 = max(min(y2, sy/2), -sy/2)
1041 if (x & 1) == (y & 1):
1042 glColor4ub(5, 171, 231, 127)
1044 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1046 glVertex3f(x1, y1, -0.02)
1047 glVertex3f(x2, y1, -0.02)
1048 glVertex3f(x2, y2, -0.02)
1049 glVertex3f(x1, y2, -0.02)
1052 glDisableClientState(GL_VERTEX_ARRAY)
1054 glDisable(GL_CULL_FACE)
1056 def _generateGCodeVBOs(self, layer):
1058 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1059 pointList = numpy.zeros((0,3), numpy.float32)
1061 if path.type == 'extrude' and path.pathType == extrudeType:
1063 a = numpy.concatenate((a[:-1], a[1:]), 1)
1064 a = a.reshape((len(a) * 2, 3))
1065 pointList = numpy.concatenate((pointList, a))
1066 ret.append(opengl.GLVBO(pointList))
1069 def _generateGCodeVBOs2(self, layer):
1070 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1071 filamentArea = math.pi * filamentRadius * filamentRadius
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 if extrudeType == 'FILL':
1082 normal = a[1:] - a[:-1]
1083 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1084 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1087 ePerDist = path.extrusion[1:] / lens
1088 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1090 normal[:,0] *= lineWidth
1091 normal[:,1] *= lineWidth
1093 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1094 b = numpy.concatenate((b, a[:-1] + normal), 1)
1095 b = numpy.concatenate((b, a[1:] + normal), 1)
1096 b = numpy.concatenate((b, a[1:] - normal), 1)
1097 b = numpy.concatenate((b, a[:-1] - normal), 1)
1098 b = b.reshape((len(b) * 4, 3))
1100 pointList = numpy.concatenate((pointList, b))
1101 ret.append(opengl.GLVBO(pointList))
1104 def getObjectCenterPos(self):
1105 if self._selectedObj is None:
1106 return [0.0, 0.0, 0.0]
1107 pos = self._selectedObj.getPosition()
1108 size = self._selectedObj.getSize()
1109 return [pos[0], pos[1], size[2]/2]
1111 def getObjectBoundaryCircle(self):
1112 if self._selectedObj is None:
1114 return self._selectedObj.getBoundaryCircle()
1116 def getObjectSize(self):
1117 if self._selectedObj is None:
1118 return [0.0, 0.0, 0.0]
1119 return self._selectedObj.getSize()
1121 def getObjectMatrix(self):
1122 if self._selectedObj is None:
1123 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1124 return self._selectedObj.getMatrix()
1126 class shaderEditor(wx.Dialog):
1127 def __init__(self, parent, callback, v, f):
1128 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1129 self._callback = callback
1130 s = wx.BoxSizer(wx.VERTICAL)
1132 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1133 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1134 s.Add(self._vertex, 1, flag=wx.EXPAND)
1135 s.Add(self._fragment, 1, flag=wx.EXPAND)
1137 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1138 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1140 self.SetPosition(self.GetParent().GetPosition())
1141 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1144 def OnText(self, e):
1145 self._callback(self._vertex.GetValue(), self._fragment.GetValue())