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,1.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())
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 self.loadScene([filename])
129 def showSaveModel(self):
130 if len(self._scene.objects()) < 1:
132 dlg=wx.FileDialog(self, 'Save 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
133 dlg.SetWildcard(meshLoader.saveWildcardFilter())
134 if dlg.ShowModal() != wx.ID_OK:
137 filename = dlg.GetPath()
139 meshLoader.saveMeshes(filename, self._scene.objects())
141 def showPrintWindow(self, button):
143 if machineCom.machineIsConnected():
144 printWindow.printFile(self._slicer.getGCodeFilename())
145 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
146 drives = removableStorage.getPossibleSDcardDrives()
148 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))
149 if dlg.ShowModal() != wx.ID_OK:
152 drive = drives[dlg.GetSelection()]
156 filename = os.path.basename(profile.getPreference('lastFile'))
157 filename = filename[0:filename.rfind('.')] + '.gcode'
159 shutil.copy(self._slicer.getGCodeFilename(), drive[1] + filename)
161 self.notification.message("Failed to save to SD card")
163 self.notification.message("Saved as %s" % (drive[1] + filename))
168 self.Bind(wx.EVT_MENU, lambda e: printWindow.printFile(self._slicer.getGCodeFilename()), menu.Append(-1, 'Print with USB'))
169 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, 'Save GCode...'))
170 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, 'Slice engine log...'))
174 def showSaveGCode(self):
175 defPath = profile.getPreference('lastFile')
176 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
177 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
178 dlg.SetFilename(defPath)
179 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
180 if dlg.ShowModal() != wx.ID_OK:
183 filename = dlg.GetPath()
187 shutil.copy(self._slicer.getGCodeFilename(), filename)
189 self.notification.message("Failed to save")
191 self.notification.message("Saved as %s" % (filename))
193 def _showSliceLog(self):
194 dlg = wx.TextEntryDialog(self, "The slicing engine reported the following", "Engine log...", '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
198 def OnToolSelect(self, button):
199 if self.rotateToolButton.getSelected():
200 self.tool = previewTools.toolRotate(self)
201 elif self.scaleToolButton.getSelected():
202 self.tool = previewTools.toolScale(self)
203 elif self.mirrorToolButton.getSelected():
204 self.tool = previewTools.toolNone(self)
206 self.tool = previewTools.toolNone(self)
207 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
208 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
209 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
210 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
211 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
212 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
213 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
214 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
216 def updateToolButtons(self):
217 if self._selectedObj is None:
221 self.rotateToolButton.setHidden(hidden)
222 self.scaleToolButton.setHidden(hidden)
223 self.mirrorToolButton.setHidden(hidden)
225 self.rotateToolButton.setSelected(False)
226 self.scaleToolButton.setSelected(False)
227 self.mirrorToolButton.setSelected(False)
230 def OnViewChange(self):
231 if self.viewSelection.getValue() == 3:
232 self.viewMode = 'gcode'
233 if self._gcode is not None:
234 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
235 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
236 self._selectObject(None)
237 elif self.viewSelection.getValue() == 1:
238 self.viewMode = 'transparent'
239 elif self.viewSelection.getValue() == 2:
240 self.viewMode = 'xray'
242 self.viewMode = 'normal'
243 self.layerSelect.setHidden(self.viewMode != 'gcode')
244 self.openFileButton.setHidden(self.viewMode == 'gcode')
247 def OnRotateReset(self, button):
248 if self._selectedObj is None:
250 self._selectedObj.resetRotation()
251 self._scene.pushFree()
252 self._selectObject(self._selectedObj)
255 def OnLayFlat(self, button):
256 if self._selectedObj is None:
258 self._selectedObj.layFlat()
259 self._scene.pushFree()
260 self._selectObject(self._selectedObj)
263 def OnScaleReset(self, button):
264 if self._selectedObj is None:
266 self._selectedObj.resetScale()
267 self._selectObject(self._selectedObj)
268 self.updateProfileToControls()
271 def OnScaleMax(self, button):
272 if self._selectedObj is None:
274 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
275 self._scene.pushFree()
276 self._selectObject(self._selectedObj)
277 self.updateProfileToControls()
280 def OnMirror(self, axis):
281 if self._selectedObj is None:
283 self._selectedObj.mirror(axis)
286 def OnScaleEntry(self, value, axis):
287 if self._selectedObj is None:
293 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
294 self.updateProfileToControls()
295 self._scene.pushFree()
296 self._selectObject(self._selectedObj)
299 def OnScaleEntryMM(self, value, axis):
300 if self._selectedObj is None:
306 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
307 self.updateProfileToControls()
308 self._scene.pushFree()
309 self._selectObject(self._selectedObj)
312 def OnDeleteAll(self, e):
313 while len(self._scene.objects()) > 0:
314 self._deleteObject(self._scene.objects()[0])
315 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
317 def OnMultiply(self, e):
318 if self._focusObj is None:
321 dlg = wx.NumberEntryDialog(self, "How many items do you want?", "Copies", "Multiply", 2, 1, 100)
322 if dlg.ShowModal() != wx.ID_OK:
325 cnt = dlg.GetValue() - 1
331 self._scene.add(newObj)
332 self._scene.centerAll()
333 if not self._scene.checkPlatform(newObj):
338 self.notification.message("Could not create more then %d items" % (n))
339 self._scene.remove(newObj)
340 self._scene.centerAll()
343 def OnSplitObject(self, e):
344 if self._focusObj is None:
346 self._scene.remove(self._focusObj)
347 for obj in self._focusObj.split(self._splitCallback):
349 self._scene.centerAll()
350 self._selectObject(None)
353 def _splitCallback(self, progress):
356 def OnMergeObjects(self, e):
357 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
359 self._scene.merge(self._selectedObj, self._focusObj)
362 def sceneUpdated(self):
363 self._sceneUpdateTimer.Start(1, True)
364 self._slicer.abortSlicer()
365 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
368 def _onRunSlicer(self, e):
369 if self._isSimpleMode:
370 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
371 self._slicer.runSlicer(self._scene)
372 if self._isSimpleMode:
373 profile.resetTempOverride()
375 def _updateSliceProgress(self, progressValue, ready):
376 self.printButton.setDisabled(not ready)
377 if progressValue >= 0.0:
378 self.printButton.setProgressBar(progressValue)
380 self.printButton.setProgressBar(None)
381 if self._gcode is not None:
383 for layerVBOlist in self._gcodeVBOs:
384 for vbo in layerVBOlist:
385 self.glReleaseList.append(vbo)
388 self.printButton.setProgressBar(None)
389 cost = self._slicer.getFilamentCost()
391 self.printButton.setBottomText('%s\n%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount(), cost))
393 self.printButton.setBottomText('%s\n%s' % (self._slicer.getPrintTime(), self._slicer.getFilamentAmount()))
394 self._gcode = gcodeInterpreter.gcode()
395 self._gcode.progressCallback = self._gcodeLoadCallback
396 self._thread = threading.Thread(target=self._loadGCode)
397 self._thread.daemon = True
400 self.printButton.setBottomText('')
403 def _loadGCode(self):
404 self._gcode.load(self._slicer.getGCodeFilename())
406 def _gcodeLoadCallback(self, progress):
407 if self._gcode is None:
409 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
410 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
411 self.layerSelect.setValue(self.layerSelect.getMaxValue())
413 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
416 def loadScene(self, fileList):
417 for filename in fileList:
419 objList = meshLoader.loadMeshes(filename)
421 traceback.print_exc()
424 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
426 self._scene.centerAll()
427 self._selectObject(obj)
430 def _deleteObject(self, obj):
431 if obj == self._selectedObj:
432 self._selectObject(None)
433 if obj == self._focusObj:
434 self._focusObj = None
435 self._scene.remove(obj)
436 for m in obj._meshList:
437 if m.vbo is not None and m.vbo.decRef():
438 self.glReleaseList.append(m.vbo)
441 if self._isSimpleMode:
442 self._scene.arrangeAll()
445 def _selectObject(self, obj, zoom = True):
446 if obj != self._selectedObj:
447 self._selectedObj = obj
448 self.updateProfileToControls()
449 self.updateToolButtons()
450 if zoom and obj is not None:
451 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
452 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
453 newZoom = obj.getBoundaryCircle() * 6
454 if newZoom > numpy.max(self._machineSize) * 3:
455 newZoom = numpy.max(self._machineSize) * 3
456 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
458 def updateProfileToControls(self):
459 oldSimpleMode = self._isSimpleMode
460 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
461 if self._isSimpleMode and not oldSimpleMode:
462 self._scene.arrangeAll()
464 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
465 self._objColors[0] = profile.getPreferenceColour('model_colour')
466 self._objColors[1] = profile.getPreferenceColour('model_colour2')
467 self._objColors[2] = profile.getPreferenceColour('model_colour3')
468 self._objColors[3] = profile.getPreferenceColour('model_colour4')
469 self._scene.setMachineSize(self._machineSize)
470 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
471 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'))
473 if self._selectedObj is not None:
474 scale = self._selectedObj.getScale()
475 size = self._selectedObj.getSize()
476 self.scaleXctrl.setValue(round(scale[0], 2))
477 self.scaleYctrl.setValue(round(scale[1], 2))
478 self.scaleZctrl.setValue(round(scale[2], 2))
479 self.scaleXmmctrl.setValue(round(size[0], 2))
480 self.scaleYmmctrl.setValue(round(size[1], 2))
481 self.scaleZmmctrl.setValue(round(size[2], 2))
483 def OnKeyChar(self, keyCode):
484 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
485 if self._selectedObj is not None:
486 self._deleteObject(self._selectedObj)
488 if keyCode == wx.WXK_UP:
489 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
491 elif keyCode == wx.WXK_DOWN:
492 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
494 elif keyCode == wx.WXK_PAGEUP:
495 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
497 elif keyCode == wx.WXK_PAGEDOWN:
498 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
501 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
502 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
503 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
504 from collections import defaultdict
505 from gc import get_objects
506 self._beforeLeakTest = defaultdict(int)
507 for i in get_objects():
508 self._beforeLeakTest[type(i)] += 1
509 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
510 from collections import defaultdict
511 from gc import get_objects
512 self._afterLeakTest = defaultdict(int)
513 for i in get_objects():
514 self._afterLeakTest[type(i)] += 1
515 for k in self._afterLeakTest:
516 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
517 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
519 def ShaderUpdate(self, v, f):
520 s = opengl.GLShader(v, f)
522 self._objectLoadShader.release()
523 self._objectLoadShader = s
524 for obj in self._scene.objects():
525 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
528 def OnMouseDown(self,e):
529 self._mouseX = e.GetX()
530 self._mouseY = e.GetY()
531 self._mouseClick3DPos = self._mouse3Dpos
532 self._mouseClickFocus = self._focusObj
534 self._mouseState = 'doubleClick'
536 self._mouseState = 'dragOrClick'
537 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
538 p0 -= self.getObjectCenterPos() - self._viewTarget
539 p1 -= self.getObjectCenterPos() - self._viewTarget
540 if self.tool.OnDragStart(p0, p1):
541 self._mouseState = 'tool'
542 if self._mouseState == 'dragOrClick':
543 if e.GetButton() == 1:
544 if self._focusObj is not None:
545 self._selectObject(self._focusObj, False)
548 def OnMouseUp(self, e):
549 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
551 if self._mouseState == 'dragOrClick':
552 if e.GetButton() == 1:
553 self._selectObject(self._focusObj)
554 if e.GetButton() == 3:
556 if self._focusObj is not None:
557 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
558 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
559 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
560 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
561 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
562 if len(self._scene.objects()) > 0:
563 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
564 if menu.MenuItemCount > 0:
567 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
568 self._scene.pushFree()
570 elif self._mouseState == 'tool':
571 if self.tempMatrix is not None and self._selectedObj is not None:
572 self._selectedObj.applyMatrix(self.tempMatrix)
573 self._scene.pushFree()
574 self._selectObject(self._selectedObj)
575 self.tempMatrix = None
576 self.tool.OnDragEnd()
578 self._mouseState = None
580 def OnMouseMotion(self,e):
581 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
582 p0 -= self.getObjectCenterPos() - self._viewTarget
583 p1 -= self.getObjectCenterPos() - self._viewTarget
585 if e.Dragging() and self._mouseState is not None:
586 if self._mouseState == 'tool':
587 self.tool.OnDrag(p0, p1)
588 elif not e.LeftIsDown() and e.RightIsDown():
589 self._mouseState = 'drag'
590 if wx.GetKeyState(wx.WXK_SHIFT):
591 a = math.cos(math.radians(self._yaw)) / 3.0
592 b = math.sin(math.radians(self._yaw)) / 3.0
593 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
594 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
595 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
596 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
598 self._yaw += e.GetX() - self._mouseX
599 self._pitch -= e.GetY() - self._mouseY
600 if self._pitch > 170:
604 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
605 self._mouseState = 'drag'
606 self._zoom += e.GetY() - self._mouseY
609 if self._zoom > numpy.max(self._machineSize) * 3:
610 self._zoom = numpy.max(self._machineSize) * 3
611 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
612 self._mouseState = 'dragObject'
613 z = max(0, self._mouseClick3DPos[2])
614 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
615 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
620 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
621 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
622 diff = cursorZ1 - cursorZ0
623 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
624 if not e.Dragging() or self._mouseState != 'tool':
625 self.tool.OnMouseMove(p0, p1)
627 self._mouseX = e.GetX()
628 self._mouseY = e.GetY()
630 def OnMouseWheel(self, e):
631 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
632 delta = max(min(delta,4),-4)
633 self._zoom *= 1.0 - delta / 10.0
636 if self._zoom > numpy.max(self._machineSize) * 3:
637 self._zoom = numpy.max(self._machineSize) * 3
640 def getMouseRay(self, x, y):
641 if self._viewport is None:
642 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
643 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
644 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
645 p0 -= self._viewTarget
646 p1 -= self._viewTarget
649 def _init3DView(self):
650 # set viewing projection
651 size = self.GetSize()
652 glViewport(0, 0, size.GetWidth(), size.GetHeight())
655 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
657 glDisable(GL_RESCALE_NORMAL)
658 glDisable(GL_LIGHTING)
660 glEnable(GL_DEPTH_TEST)
661 glDisable(GL_CULL_FACE)
663 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
665 glClearColor(0.8, 0.8, 0.8, 1.0)
669 glMatrixMode(GL_PROJECTION)
671 aspect = float(size.GetWidth()) / float(size.GetHeight())
672 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
674 glMatrixMode(GL_MODELVIEW)
676 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
679 if machineCom.machineIsConnected():
680 self.printButton._imageID = 6
681 self.printButton._tooltip = 'Print'
682 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
683 self.printButton._imageID = 2
684 self.printButton._tooltip = 'Toolpath to SD'
686 self.printButton._imageID = 3
687 self.printButton._tooltip = 'Save toolpath'
689 if self._animView is not None:
690 self._viewTarget = self._animView.getPosition()
691 if self._animView.isDone():
692 self._animView = None
693 if self._animZoom is not None:
694 self._zoom = self._animZoom.getPosition()
695 if self._animZoom.isDone():
696 self._animZoom = None
697 if self._objectShader is None:
698 self._objectShader = opengl.GLShader("""
699 varying float light_amount;
703 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
704 gl_FrontColor = gl_Color;
706 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
710 varying float light_amount;
714 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
717 self._objectLoadShader = opengl.GLShader("""
718 uniform float intensity;
720 varying float light_amount;
724 vec4 tmp = gl_Vertex;
725 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
726 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
727 gl_Position = gl_ModelViewProjectionMatrix * tmp;
728 gl_FrontColor = gl_Color;
730 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
734 uniform float intensity;
735 varying float light_amount;
739 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
743 glTranslate(0,0,-self._zoom)
744 glRotate(-self._pitch, 1,0,0)
745 glRotate(self._yaw, 0,0,1)
746 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
748 self._viewport = glGetIntegerv(GL_VIEWPORT)
749 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
750 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
752 glClearColor(1,1,1,1)
753 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
755 if self.viewMode != 'gcode':
756 for n in xrange(0, len(self._scene.objects())):
757 obj = self._scene.objects()[n]
758 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
759 self._renderObject(obj)
761 if self._mouseX > -1:
762 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
763 if n < len(self._scene.objects()):
764 self._focusObj = self._scene.objects()[n]
766 self._focusObj = None
767 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
768 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
769 self._mouse3Dpos -= self._viewTarget
772 glTranslate(0,0,-self._zoom)
773 glRotate(-self._pitch, 1,0,0)
774 glRotate(self._yaw, 0,0,1)
775 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
777 if self.viewMode == 'gcode':
778 if self._gcode is not None:
780 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
782 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
783 for n in xrange(0, drawUpTill):
784 c = 1.0 - float(drawUpTill - n) / 15
786 if len(self._gcodeVBOs) < n + 1:
787 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
788 if time.time() - t > 0.5:
791 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
792 if n == drawUpTill - 1:
793 if len(self._gcodeVBOs[n]) < 6:
794 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
796 self._gcodeVBOs[n][5].render(GL_QUADS)
798 self._gcodeVBOs[n][6].render(GL_QUADS)
799 glColor3f(c/2, c/2, 0.0)
800 self._gcodeVBOs[n][7].render(GL_QUADS)
802 self._gcodeVBOs[n][8].render(GL_QUADS)
803 self._gcodeVBOs[n][9].render(GL_QUADS)
806 self._gcodeVBOs[n][0].render(GL_LINES)
808 self._gcodeVBOs[n][1].render(GL_LINES)
809 glColor3f(c/2, c/2, 0.0)
810 self._gcodeVBOs[n][2].render(GL_LINES)
812 self._gcodeVBOs[n][3].render(GL_LINES)
813 self._gcodeVBOs[n][4].render(GL_LINES)
816 glStencilFunc(GL_ALWAYS, 1, 1)
817 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
819 self._objectShader.bind()
820 for obj in self._scene.objects():
821 if obj._loadAnim is not None:
822 if obj._loadAnim.isDone():
827 if self._focusObj == obj:
829 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
832 if self._selectedObj == obj or self._selectedObj is None:
833 #If we want transparent, then first render a solid black model to remove the printer size lines.
834 if self.viewMode == 'transparent':
835 glColor4f(0, 0, 0, 0)
836 self._renderObject(obj)
838 glBlendFunc(GL_ONE, GL_ONE)
839 glDisable(GL_DEPTH_TEST)
841 if self.viewMode == 'xray':
842 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
843 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
844 glEnable(GL_STENCIL_TEST)
846 if not self._scene.checkPlatform(obj):
847 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
848 self._renderObject(obj)
850 self._renderObject(obj, brightness)
851 glDisable(GL_STENCIL_TEST)
853 glEnable(GL_DEPTH_TEST)
854 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
856 if self.viewMode == 'xray':
859 glEnable(GL_STENCIL_TEST)
860 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
861 glDisable(GL_DEPTH_TEST)
862 for i in xrange(2, 15, 2):
863 glStencilFunc(GL_EQUAL, i, 0xFF)
864 glColor(float(i)/10, float(i)/10, float(i)/5)
866 glVertex3f(-1000,-1000,-10)
867 glVertex3f( 1000,-1000,-10)
868 glVertex3f( 1000, 1000,-10)
869 glVertex3f(-1000, 1000,-10)
871 for i in xrange(1, 15, 2):
872 glStencilFunc(GL_EQUAL, i, 0xFF)
873 glColor(float(i)/10, 0, 0)
875 glVertex3f(-1000,-1000,-10)
876 glVertex3f( 1000,-1000,-10)
877 glVertex3f( 1000, 1000,-10)
878 glVertex3f(-1000, 1000,-10)
881 glDisable(GL_STENCIL_TEST)
882 glEnable(GL_DEPTH_TEST)
884 self._objectShader.unbind()
886 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
888 self._objectLoadShader.bind()
889 glColor4f(0.2, 0.6, 1.0, 1.0)
890 for obj in self._scene.objects():
891 if obj._loadAnim is None:
893 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
894 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
895 self._renderObject(obj)
896 self._objectLoadShader.unbind()
901 if self.viewMode == 'gcode':
904 #Draw the object box-shadow, so you can see where it will collide with other objects.
905 if self._selectedObj is not None and len(self._scene.objects()) > 1:
906 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
908 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
910 glEnable(GL_CULL_FACE)
911 glColor4f(0,0,0,0.12)
913 glVertex3f(-size[0], size[1], 0.1)
914 glVertex3f(-size[0], -size[1], 0.1)
915 glVertex3f( size[0], -size[1], 0.1)
916 glVertex3f( size[0], size[1], 0.1)
918 glDisable(GL_CULL_FACE)
921 #Draw the outline of the selected object, on top of everything else except the GUI.
922 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
923 glDisable(GL_DEPTH_TEST)
924 glEnable(GL_CULL_FACE)
925 glEnable(GL_STENCIL_TEST)
927 glStencilFunc(GL_EQUAL, 0, 255)
929 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
932 self._renderObject(self._selectedObj)
933 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
935 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
936 glDisable(GL_STENCIL_TEST)
937 glDisable(GL_CULL_FACE)
938 glEnable(GL_DEPTH_TEST)
940 if self._selectedObj is not None:
942 pos = self.getObjectCenterPos()
943 glTranslate(pos[0], pos[1], pos[2])
947 def _renderObject(self, obj, brightness = False):
949 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
951 if self.tempMatrix is not None and obj == self._selectedObj:
952 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
953 glMultMatrixf(tempMatrix)
955 offset = obj.getDrawOffset()
956 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
958 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
959 glMultMatrixf(tempMatrix)
962 for m in obj._meshList:
964 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
966 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
971 def _drawMachine(self):
972 glEnable(GL_CULL_FACE)
975 if profile.getPreference('machine_type') == 'ultimaker':
977 self._objectShader.bind()
978 self._renderObject(self._platformMesh)
979 self._objectShader.unbind()
981 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
982 v0 = [ size[0] / 2, size[1] / 2, size[2]]
983 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
984 v2 = [-size[0] / 2, size[1] / 2, size[2]]
985 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
986 v4 = [ size[0] / 2, size[1] / 2, 0]
987 v5 = [ size[0] / 2,-size[1] / 2, 0]
988 v6 = [-size[0] / 2, size[1] / 2, 0]
989 v7 = [-size[0] / 2,-size[1] / 2, 0]
991 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
992 glEnableClientState(GL_VERTEX_ARRAY)
993 glVertexPointer(3, GL_FLOAT, 3*4, vList)
995 glColor4ub(5, 171, 231, 64)
996 glDrawArrays(GL_QUADS, 0, 4)
997 glColor4ub(5, 171, 231, 96)
998 glDrawArrays(GL_QUADS, 4, 8)
999 glColor4ub(5, 171, 231, 128)
1000 glDrawArrays(GL_QUADS, 12, 8)
1002 sx = self._machineSize[0]
1003 sy = self._machineSize[1]
1004 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1005 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1010 x1 = max(min(x1, sx/2), -sx/2)
1011 y1 = max(min(y1, sy/2), -sy/2)
1012 x2 = max(min(x2, sx/2), -sx/2)
1013 y2 = max(min(y2, sy/2), -sy/2)
1014 if (x & 1) == (y & 1):
1015 glColor4ub(5, 171, 231, 127)
1017 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1019 glVertex3f(x1, y1, -0.02)
1020 glVertex3f(x2, y1, -0.02)
1021 glVertex3f(x2, y2, -0.02)
1022 glVertex3f(x1, y2, -0.02)
1025 glDisableClientState(GL_VERTEX_ARRAY)
1027 glDisable(GL_CULL_FACE)
1029 def _generateGCodeVBOs(self, layer):
1031 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1032 pointList = numpy.zeros((0,3), numpy.float32)
1034 if path.type == 'extrude' and path.pathType == extrudeType:
1035 a = numpy.array(path.points, numpy.float32)
1036 a = numpy.concatenate((a[:-1], a[1:]), 1)
1037 a = a.reshape((len(a) * 2, 3))
1038 pointList = numpy.concatenate((pointList, a))
1039 ret.append(opengl.GLVBO(pointList))
1042 def _generateGCodeVBOs2(self, layer):
1043 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1044 filamentArea = math.pi * filamentRadius * filamentRadius
1047 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1048 pointList = numpy.zeros((0,3), numpy.float32)
1050 if path.type == 'extrude' and path.pathType == extrudeType:
1051 a = numpy.array(path.points, numpy.float32)
1052 if extrudeType == 'FILL':
1055 normal = a[1:] - a[:-1]
1056 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1057 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1060 ePerDist = path.extrusion[1:] / lens
1061 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1063 normal[:,0] *= lineWidth
1064 normal[:,1] *= lineWidth
1066 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1067 b = numpy.concatenate((b, a[:-1] + normal), 1)
1068 b = numpy.concatenate((b, a[1:] + normal), 1)
1069 b = numpy.concatenate((b, a[1:] - normal), 1)
1070 b = numpy.concatenate((b, a[:-1] - normal), 1)
1071 b = b.reshape((len(b) * 4, 3))
1073 pointList = numpy.concatenate((pointList, b))
1074 ret.append(opengl.GLVBO(pointList))
1077 def getObjectCenterPos(self):
1078 if self._selectedObj is None:
1079 return [0.0, 0.0, 0.0]
1080 pos = self._selectedObj.getPosition()
1081 size = self._selectedObj.getSize()
1082 return [pos[0], pos[1], size[2]/2]
1084 def getObjectBoundaryCircle(self):
1085 if self._selectedObj is None:
1087 return self._selectedObj.getBoundaryCircle()
1089 def getObjectSize(self):
1090 if self._selectedObj is None:
1091 return [0.0, 0.0, 0.0]
1092 return self._selectedObj.getSize()
1094 def getObjectMatrix(self):
1095 if self._selectedObj is None:
1096 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1097 return self._selectedObj.getMatrix()
1099 class shaderEditor(wx.Dialog):
1100 def __init__(self, parent, callback, v, f):
1101 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1102 self._callback = callback
1103 s = wx.BoxSizer(wx.VERTICAL)
1105 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1106 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1107 s.Add(self._vertex, 1, flag=wx.EXPAND)
1108 s.Add(self._fragment, 1, flag=wx.EXPAND)
1110 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1111 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1113 self.SetPosition(self.GetParent().GetPosition())
1114 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1117 def OnText(self, e):
1118 self._callback(self._vertex.GetValue(), self._fragment.GetValue())