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)
439 if self._isSimpleMode:
440 self._scene.arrangeAll()
443 def _selectObject(self, obj, zoom = True):
444 if obj != self._selectedObj:
445 self._selectedObj = obj
446 self.updateProfileToControls()
447 self.updateToolButtons()
448 if zoom and obj is not None:
449 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
450 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
451 newZoom = obj.getBoundaryCircle() * 6
452 if newZoom > numpy.max(self._machineSize) * 3:
453 newZoom = numpy.max(self._machineSize) * 3
454 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
456 def updateProfileToControls(self):
457 oldSimpleMode = self._isSimpleMode
458 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
459 if self._isSimpleMode and not oldSimpleMode:
460 self._scene.arrangeAll()
462 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
463 self._objColors[0] = profile.getPreferenceColour('model_colour')
464 self._objColors[1] = profile.getPreferenceColour('model_colour2')
465 self._objColors[2] = profile.getPreferenceColour('model_colour3')
466 self._objColors[3] = profile.getPreferenceColour('model_colour4')
467 self._scene.setMachineSize(self._machineSize)
468 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
469 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'))
471 if self._selectedObj is not None:
472 scale = self._selectedObj.getScale()
473 size = self._selectedObj.getSize()
474 self.scaleXctrl.setValue(round(scale[0], 2))
475 self.scaleYctrl.setValue(round(scale[1], 2))
476 self.scaleZctrl.setValue(round(scale[2], 2))
477 self.scaleXmmctrl.setValue(round(size[0], 2))
478 self.scaleYmmctrl.setValue(round(size[1], 2))
479 self.scaleZmmctrl.setValue(round(size[2], 2))
481 def OnKeyChar(self, keyCode):
482 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
483 if self._selectedObj is not None:
484 self._deleteObject(self._selectedObj)
486 if keyCode == wx.WXK_UP:
487 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
489 elif keyCode == wx.WXK_DOWN:
490 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
492 elif keyCode == wx.WXK_PAGEUP:
493 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
495 elif keyCode == wx.WXK_PAGEDOWN:
496 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
499 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
500 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
502 def ShaderUpdate(self, v, f):
503 s = opengl.GLShader(v, f)
505 self._objectLoadShader.release()
506 self._objectLoadShader = s
507 for obj in self._scene.objects():
508 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
511 def OnMouseDown(self,e):
512 self._mouseX = e.GetX()
513 self._mouseY = e.GetY()
514 self._mouseClick3DPos = self._mouse3Dpos
515 self._mouseClickFocus = self._focusObj
517 self._mouseState = 'doubleClick'
519 self._mouseState = 'dragOrClick'
520 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
521 p0 -= self.getObjectCenterPos() - self._viewTarget
522 p1 -= self.getObjectCenterPos() - self._viewTarget
523 if self.tool.OnDragStart(p0, p1):
524 self._mouseState = 'tool'
525 if self._mouseState == 'dragOrClick':
526 if e.GetButton() == 1:
527 if self._focusObj is not None:
528 self._selectObject(self._focusObj, False)
531 def OnMouseUp(self, e):
532 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
534 if self._mouseState == 'dragOrClick':
535 if e.GetButton() == 1:
536 self._selectObject(self._focusObj)
537 if e.GetButton() == 3:
539 if self._focusObj is not None:
540 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
541 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
542 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
543 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
544 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
545 if len(self._scene.objects()) > 0:
546 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
547 if menu.MenuItemCount > 0:
550 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
551 self._scene.pushFree()
553 elif self._mouseState == 'tool':
554 if self.tempMatrix is not None and self._selectedObj is not None:
555 self._selectedObj.applyMatrix(self.tempMatrix)
556 self._scene.pushFree()
557 self._selectObject(self._selectedObj)
558 self.tempMatrix = None
559 self.tool.OnDragEnd()
561 self._mouseState = None
563 def OnMouseMotion(self,e):
564 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
565 p0 -= self.getObjectCenterPos() - self._viewTarget
566 p1 -= self.getObjectCenterPos() - self._viewTarget
568 if e.Dragging() and self._mouseState is not None:
569 if self._mouseState == 'tool':
570 self.tool.OnDrag(p0, p1)
571 elif not e.LeftIsDown() and e.RightIsDown():
572 self._mouseState = 'drag'
573 if wx.GetKeyState(wx.WXK_SHIFT):
574 a = math.cos(math.radians(self._yaw)) / 3.0
575 b = math.sin(math.radians(self._yaw)) / 3.0
576 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
577 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
578 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
579 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
581 self._yaw += e.GetX() - self._mouseX
582 self._pitch -= e.GetY() - self._mouseY
583 if self._pitch > 170:
587 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
588 self._mouseState = 'drag'
589 self._zoom += e.GetY() - self._mouseY
592 if self._zoom > numpy.max(self._machineSize) * 3:
593 self._zoom = numpy.max(self._machineSize) * 3
594 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
595 self._mouseState = 'dragObject'
596 z = max(0, self._mouseClick3DPos[2])
597 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
598 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
603 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
604 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
605 diff = cursorZ1 - cursorZ0
606 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
607 if not e.Dragging() or self._mouseState != 'tool':
608 self.tool.OnMouseMove(p0, p1)
610 self._mouseX = e.GetX()
611 self._mouseY = e.GetY()
613 def OnMouseWheel(self, e):
614 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
615 delta = max(min(delta,4),-4)
616 self._zoom *= 1.0 - delta / 10.0
619 if self._zoom > numpy.max(self._machineSize) * 3:
620 self._zoom = numpy.max(self._machineSize) * 3
623 def getMouseRay(self, x, y):
624 if self._viewport is None:
625 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
626 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
627 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
628 p0 -= self._viewTarget
629 p1 -= self._viewTarget
632 def _init3DView(self):
633 # set viewing projection
634 size = self.GetSize()
635 glViewport(0, 0, size.GetWidth(), size.GetHeight())
638 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
640 glDisable(GL_RESCALE_NORMAL)
641 glDisable(GL_LIGHTING)
643 glEnable(GL_DEPTH_TEST)
644 glDisable(GL_CULL_FACE)
646 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
648 glClearColor(0.8, 0.8, 0.8, 1.0)
652 glMatrixMode(GL_PROJECTION)
654 aspect = float(size.GetWidth()) / float(size.GetHeight())
655 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
657 glMatrixMode(GL_MODELVIEW)
659 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
662 if machineCom.machineIsConnected():
663 self.printButton._imageID = 6
664 self.printButton._tooltip = 'Print'
665 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
666 self.printButton._imageID = 2
667 self.printButton._tooltip = 'Toolpath to SD'
669 self.printButton._imageID = 3
670 self.printButton._tooltip = 'Save toolpath'
672 if self._animView is not None:
673 self._viewTarget = self._animView.getPosition()
674 if self._animView.isDone():
675 self._animView = None
676 if self._animZoom is not None:
677 self._zoom = self._animZoom.getPosition()
678 if self._animZoom.isDone():
679 self._animZoom = None
680 if self._objectShader is None:
681 self._objectShader = opengl.GLShader("""
682 varying float light_amount;
686 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
687 gl_FrontColor = gl_Color;
689 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
693 varying float light_amount;
697 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
700 self._objectLoadShader = opengl.GLShader("""
701 uniform float intensity;
703 varying float light_amount;
707 vec4 tmp = gl_Vertex;
708 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
709 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
710 gl_Position = gl_ModelViewProjectionMatrix * tmp;
711 gl_FrontColor = gl_Color;
713 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
717 uniform float intensity;
718 varying float light_amount;
722 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
726 glTranslate(0,0,-self._zoom)
727 glRotate(-self._pitch, 1,0,0)
728 glRotate(self._yaw, 0,0,1)
729 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
731 self._viewport = glGetIntegerv(GL_VIEWPORT)
732 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
733 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
735 glClearColor(1,1,1,1)
736 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
738 if self.viewMode != 'gcode':
739 for n in xrange(0, len(self._scene.objects())):
740 obj = self._scene.objects()[n]
741 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
742 self._renderObject(obj)
744 if self._mouseX > -1:
745 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
746 if n < len(self._scene.objects()):
747 self._focusObj = self._scene.objects()[n]
749 self._focusObj = None
750 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
751 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
752 self._mouse3Dpos -= self._viewTarget
755 glTranslate(0,0,-self._zoom)
756 glRotate(-self._pitch, 1,0,0)
757 glRotate(self._yaw, 0,0,1)
758 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
760 if self.viewMode == 'gcode':
761 if self._gcode is not None:
763 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
765 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
766 for n in xrange(0, drawUpTill):
767 c = 1.0 - float(drawUpTill - n) / 15
769 if len(self._gcodeVBOs) < n + 1:
770 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
771 if time.time() - t > 0.5:
774 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
775 if n == drawUpTill - 1:
776 if len(self._gcodeVBOs[n]) < 6:
777 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
779 self._gcodeVBOs[n][5].render(GL_QUADS)
781 self._gcodeVBOs[n][6].render(GL_QUADS)
782 glColor3f(c/2, c/2, 0.0)
783 self._gcodeVBOs[n][7].render(GL_QUADS)
785 self._gcodeVBOs[n][8].render(GL_QUADS)
786 self._gcodeVBOs[n][9].render(GL_QUADS)
789 self._gcodeVBOs[n][0].render(GL_LINES)
791 self._gcodeVBOs[n][1].render(GL_LINES)
792 glColor3f(c/2, c/2, 0.0)
793 self._gcodeVBOs[n][2].render(GL_LINES)
795 self._gcodeVBOs[n][3].render(GL_LINES)
796 self._gcodeVBOs[n][4].render(GL_LINES)
799 glStencilFunc(GL_ALWAYS, 1, 1)
800 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
802 self._objectShader.bind()
803 for obj in self._scene.objects():
804 if obj._loadAnim is not None:
805 if obj._loadAnim.isDone():
810 if self._focusObj == obj:
812 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
815 if self._selectedObj == obj or self._selectedObj is None:
816 #If we want transparent, then first render a solid black model to remove the printer size lines.
817 if self.viewMode == 'transparent':
818 glColor4f(0, 0, 0, 0)
819 self._renderObject(obj)
821 glBlendFunc(GL_ONE, GL_ONE)
822 glDisable(GL_DEPTH_TEST)
824 if self.viewMode == 'xray':
825 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
826 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
827 glEnable(GL_STENCIL_TEST)
829 if not self._scene.checkPlatform(obj):
830 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
831 self._renderObject(obj)
833 self._renderObject(obj, brightness)
834 glDisable(GL_STENCIL_TEST)
836 glEnable(GL_DEPTH_TEST)
837 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
839 if self.viewMode == 'xray':
842 glEnable(GL_STENCIL_TEST)
843 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
844 glDisable(GL_DEPTH_TEST)
845 for i in xrange(2, 15, 2):
846 glStencilFunc(GL_EQUAL, i, 0xFF)
847 glColor(float(i)/10, float(i)/10, float(i)/5)
849 glVertex3f(-1000,-1000,-10)
850 glVertex3f( 1000,-1000,-10)
851 glVertex3f( 1000, 1000,-10)
852 glVertex3f(-1000, 1000,-10)
854 for i in xrange(1, 15, 2):
855 glStencilFunc(GL_EQUAL, i, 0xFF)
856 glColor(float(i)/10, 0, 0)
858 glVertex3f(-1000,-1000,-10)
859 glVertex3f( 1000,-1000,-10)
860 glVertex3f( 1000, 1000,-10)
861 glVertex3f(-1000, 1000,-10)
864 glDisable(GL_STENCIL_TEST)
865 glEnable(GL_DEPTH_TEST)
867 self._objectShader.unbind()
869 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
871 self._objectLoadShader.bind()
872 glColor4f(0.2, 0.6, 1.0, 1.0)
873 for obj in self._scene.objects():
874 if obj._loadAnim is None:
876 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
877 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
878 self._renderObject(obj)
879 self._objectLoadShader.unbind()
884 if self.viewMode == 'gcode':
887 #Draw the object box-shadow, so you can see where it will collide with other objects.
888 if self._selectedObj is not None and len(self._scene.objects()) > 1:
889 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
891 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
893 glEnable(GL_CULL_FACE)
894 glColor4f(0,0,0,0.12)
896 glVertex3f(-size[0], size[1], 0.1)
897 glVertex3f(-size[0], -size[1], 0.1)
898 glVertex3f( size[0], -size[1], 0.1)
899 glVertex3f( size[0], size[1], 0.1)
901 glDisable(GL_CULL_FACE)
904 #Draw the outline of the selected object, on top of everything else except the GUI.
905 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
906 glDisable(GL_DEPTH_TEST)
907 glEnable(GL_CULL_FACE)
908 glEnable(GL_STENCIL_TEST)
910 glStencilFunc(GL_EQUAL, 0, 255)
912 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
915 self._renderObject(self._selectedObj)
916 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
918 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
919 glDisable(GL_STENCIL_TEST)
920 glDisable(GL_CULL_FACE)
921 glEnable(GL_DEPTH_TEST)
923 if self._selectedObj is not None:
925 pos = self.getObjectCenterPos()
926 glTranslate(pos[0], pos[1], pos[2])
930 def _renderObject(self, obj, brightness = False):
932 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
934 if self.tempMatrix is not None and obj == self._selectedObj:
935 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
936 glMultMatrixf(tempMatrix)
938 offset = obj.getDrawOffset()
939 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
941 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
942 glMultMatrixf(tempMatrix)
945 for m in obj._meshList:
947 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
949 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
954 def _drawMachine(self):
955 glEnable(GL_CULL_FACE)
958 if profile.getPreference('machine_type') == 'ultimaker':
960 self._objectShader.bind()
961 self._renderObject(self._platformMesh)
962 self._objectShader.unbind()
964 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
965 v0 = [ size[0] / 2, size[1] / 2, size[2]]
966 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
967 v2 = [-size[0] / 2, size[1] / 2, size[2]]
968 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
969 v4 = [ size[0] / 2, size[1] / 2, 0]
970 v5 = [ size[0] / 2,-size[1] / 2, 0]
971 v6 = [-size[0] / 2, size[1] / 2, 0]
972 v7 = [-size[0] / 2,-size[1] / 2, 0]
974 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
975 glEnableClientState(GL_VERTEX_ARRAY)
976 glVertexPointer(3, GL_FLOAT, 3*4, vList)
978 glColor4ub(5, 171, 231, 64)
979 glDrawArrays(GL_QUADS, 0, 4)
980 glColor4ub(5, 171, 231, 96)
981 glDrawArrays(GL_QUADS, 4, 8)
982 glColor4ub(5, 171, 231, 128)
983 glDrawArrays(GL_QUADS, 12, 8)
985 sx = self._machineSize[0]
986 sy = self._machineSize[1]
987 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
988 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
993 x1 = max(min(x1, sx/2), -sx/2)
994 y1 = max(min(y1, sy/2), -sy/2)
995 x2 = max(min(x2, sx/2), -sx/2)
996 y2 = max(min(y2, sy/2), -sy/2)
997 if (x & 1) == (y & 1):
998 glColor4ub(5, 171, 231, 127)
1000 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1002 glVertex3f(x1, y1, -0.02)
1003 glVertex3f(x2, y1, -0.02)
1004 glVertex3f(x2, y2, -0.02)
1005 glVertex3f(x1, y2, -0.02)
1008 glDisableClientState(GL_VERTEX_ARRAY)
1010 glDisable(GL_CULL_FACE)
1012 def _generateGCodeVBOs(self, layer):
1014 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1015 pointList = numpy.zeros((0,3), numpy.float32)
1017 if path.type == 'extrude' and path.pathType == extrudeType:
1018 a = numpy.array(path.points, numpy.float32)
1019 a = numpy.concatenate((a[:-1], a[1:]), 1)
1020 a = a.reshape((len(a) * 2, 3))
1021 pointList = numpy.concatenate((pointList, a))
1022 ret.append(opengl.GLVBO(pointList))
1025 def _generateGCodeVBOs2(self, layer):
1026 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1027 filamentArea = math.pi * filamentRadius * filamentRadius
1030 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1031 pointList = numpy.zeros((0,3), numpy.float32)
1033 if path.type == 'extrude' and path.pathType == extrudeType:
1034 a = numpy.array(path.points, numpy.float32)
1035 if extrudeType == 'FILL':
1038 normal = a[1:] - a[:-1]
1039 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1040 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1043 ePerDist = path.extrusion[1:] / lens
1044 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1046 normal[:,0] *= lineWidth
1047 normal[:,1] *= lineWidth
1049 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1050 b = numpy.concatenate((b, a[:-1] + normal), 1)
1051 b = numpy.concatenate((b, a[1:] + normal), 1)
1052 b = numpy.concatenate((b, a[1:] - normal), 1)
1053 b = numpy.concatenate((b, a[:-1] - normal), 1)
1054 b = b.reshape((len(b) * 4, 3))
1056 pointList = numpy.concatenate((pointList, b))
1057 ret.append(opengl.GLVBO(pointList))
1060 def getObjectCenterPos(self):
1061 if self._selectedObj is None:
1062 return [0.0, 0.0, 0.0]
1063 pos = self._selectedObj.getPosition()
1064 size = self._selectedObj.getSize()
1065 return [pos[0], pos[1], size[2]/2]
1067 def getObjectBoundaryCircle(self):
1068 if self._selectedObj is None:
1070 return self._selectedObj.getBoundaryCircle()
1072 def getObjectSize(self):
1073 if self._selectedObj is None:
1074 return [0.0, 0.0, 0.0]
1075 return self._selectedObj.getSize()
1077 def getObjectMatrix(self):
1078 if self._selectedObj is None:
1079 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1080 return self._selectedObj.getMatrix()
1082 class shaderEditor(wx.Dialog):
1083 def __init__(self, parent, callback, v, f):
1084 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1085 self._callback = callback
1086 s = wx.BoxSizer(wx.VERTICAL)
1088 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1089 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1090 s.Add(self._vertex, 1, flag=wx.EXPAND)
1091 s.Add(self._fragment, 1, flag=wx.EXPAND)
1093 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1094 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1096 self.SetPosition(self.GetParent().GetPosition())
1097 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1100 def OnText(self, e):
1101 self._callback(self._vertex.GetValue(), self._fragment.GetValue())