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())
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), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(drive[1]) else self.notification.message('Safe remove failed...'))
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)
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())
501 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
502 from collections import defaultdict
503 from gc import get_objects
504 self._beforeLeakTest = defaultdict(int)
505 for i in get_objects():
506 self._beforeLeakTest[type(i)] += 1
507 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
508 from collections import defaultdict
509 from gc import get_objects
510 self._afterLeakTest = defaultdict(int)
511 for i in get_objects():
512 self._afterLeakTest[type(i)] += 1
513 for k in self._afterLeakTest:
514 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
515 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
517 def ShaderUpdate(self, v, f):
518 s = opengl.GLShader(v, f)
520 self._objectLoadShader.release()
521 self._objectLoadShader = s
522 for obj in self._scene.objects():
523 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
526 def OnMouseDown(self,e):
527 self._mouseX = e.GetX()
528 self._mouseY = e.GetY()
529 self._mouseClick3DPos = self._mouse3Dpos
530 self._mouseClickFocus = self._focusObj
532 self._mouseState = 'doubleClick'
534 self._mouseState = 'dragOrClick'
535 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
536 p0 -= self.getObjectCenterPos() - self._viewTarget
537 p1 -= self.getObjectCenterPos() - self._viewTarget
538 if self.tool.OnDragStart(p0, p1):
539 self._mouseState = 'tool'
540 if self._mouseState == 'dragOrClick':
541 if e.GetButton() == 1:
542 if self._focusObj is not None:
543 self._selectObject(self._focusObj, False)
546 def OnMouseUp(self, e):
547 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
549 if self._mouseState == 'dragOrClick':
550 if e.GetButton() == 1:
551 self._selectObject(self._focusObj)
552 if e.GetButton() == 3:
554 if self._focusObj is not None:
555 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
556 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
557 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
558 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
559 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
560 if len(self._scene.objects()) > 0:
561 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
562 if menu.MenuItemCount > 0:
565 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
566 self._scene.pushFree()
568 elif self._mouseState == 'tool':
569 if self.tempMatrix is not None and self._selectedObj is not None:
570 self._selectedObj.applyMatrix(self.tempMatrix)
571 self._scene.pushFree()
572 self._selectObject(self._selectedObj)
573 self.tempMatrix = None
574 self.tool.OnDragEnd()
576 self._mouseState = None
578 def OnMouseMotion(self,e):
579 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
580 p0 -= self.getObjectCenterPos() - self._viewTarget
581 p1 -= self.getObjectCenterPos() - self._viewTarget
583 if e.Dragging() and self._mouseState is not None:
584 if self._mouseState == 'tool':
585 self.tool.OnDrag(p0, p1)
586 elif not e.LeftIsDown() and e.RightIsDown():
587 self._mouseState = 'drag'
588 if wx.GetKeyState(wx.WXK_SHIFT):
589 a = math.cos(math.radians(self._yaw)) / 3.0
590 b = math.sin(math.radians(self._yaw)) / 3.0
591 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
592 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
593 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
594 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
596 self._yaw += e.GetX() - self._mouseX
597 self._pitch -= e.GetY() - self._mouseY
598 if self._pitch > 170:
602 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
603 self._mouseState = 'drag'
604 self._zoom += e.GetY() - self._mouseY
607 if self._zoom > numpy.max(self._machineSize) * 3:
608 self._zoom = numpy.max(self._machineSize) * 3
609 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
610 self._mouseState = 'dragObject'
611 z = max(0, self._mouseClick3DPos[2])
612 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
613 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
618 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
619 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
620 diff = cursorZ1 - cursorZ0
621 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
622 if not e.Dragging() or self._mouseState != 'tool':
623 self.tool.OnMouseMove(p0, p1)
625 self._mouseX = e.GetX()
626 self._mouseY = e.GetY()
628 def OnMouseWheel(self, e):
629 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
630 delta = max(min(delta,4),-4)
631 self._zoom *= 1.0 - delta / 10.0
634 if self._zoom > numpy.max(self._machineSize) * 3:
635 self._zoom = numpy.max(self._machineSize) * 3
638 def getMouseRay(self, x, y):
639 if self._viewport is None:
640 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
641 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
642 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
643 p0 -= self._viewTarget
644 p1 -= self._viewTarget
647 def _init3DView(self):
648 # set viewing projection
649 size = self.GetSize()
650 glViewport(0, 0, size.GetWidth(), size.GetHeight())
653 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
655 glDisable(GL_RESCALE_NORMAL)
656 glDisable(GL_LIGHTING)
658 glEnable(GL_DEPTH_TEST)
659 glDisable(GL_CULL_FACE)
661 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
663 glClearColor(0.8, 0.8, 0.8, 1.0)
667 glMatrixMode(GL_PROJECTION)
669 aspect = float(size.GetWidth()) / float(size.GetHeight())
670 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
672 glMatrixMode(GL_MODELVIEW)
674 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
677 if machineCom.machineIsConnected():
678 self.printButton._imageID = 6
679 self.printButton._tooltip = 'Print'
680 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
681 self.printButton._imageID = 2
682 self.printButton._tooltip = 'Toolpath to SD'
684 self.printButton._imageID = 3
685 self.printButton._tooltip = 'Save toolpath'
687 if self._animView is not None:
688 self._viewTarget = self._animView.getPosition()
689 if self._animView.isDone():
690 self._animView = None
691 if self._animZoom is not None:
692 self._zoom = self._animZoom.getPosition()
693 if self._animZoom.isDone():
694 self._animZoom = None
695 if self.viewMode == 'gcode' and self._gcode is not None:
697 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1].points[0][2]
700 if self._objectShader is None:
701 self._objectShader = opengl.GLShader("""
702 varying float light_amount;
706 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
707 gl_FrontColor = gl_Color;
709 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
713 varying float light_amount;
717 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
720 self._objectLoadShader = opengl.GLShader("""
721 uniform float intensity;
723 varying float light_amount;
727 vec4 tmp = gl_Vertex;
728 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
729 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
730 gl_Position = gl_ModelViewProjectionMatrix * tmp;
731 gl_FrontColor = gl_Color;
733 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
737 uniform float intensity;
738 varying float light_amount;
742 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
746 glTranslate(0,0,-self._zoom)
747 glRotate(-self._pitch, 1,0,0)
748 glRotate(self._yaw, 0,0,1)
749 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
751 self._viewport = glGetIntegerv(GL_VIEWPORT)
752 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
753 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
755 glClearColor(1,1,1,1)
756 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
758 if self.viewMode != 'gcode':
759 for n in xrange(0, len(self._scene.objects())):
760 obj = self._scene.objects()[n]
761 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
762 self._renderObject(obj)
764 if self._mouseX > -1:
766 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
767 if n < len(self._scene.objects()):
768 self._focusObj = self._scene.objects()[n]
770 self._focusObj = None
771 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
772 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
773 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
774 self._mouse3Dpos -= self._viewTarget
777 glTranslate(0,0,-self._zoom)
778 glRotate(-self._pitch, 1,0,0)
779 glRotate(self._yaw, 0,0,1)
780 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
782 if self.viewMode == 'gcode':
783 if self._gcode is not None:
785 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
787 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
788 for n in xrange(0, drawUpTill):
789 c = 1.0 - float(drawUpTill - n) / 15
791 if len(self._gcodeVBOs) < n + 1:
792 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
793 if time.time() - t > 0.5:
796 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
797 if n == drawUpTill - 1:
798 if len(self._gcodeVBOs[n]) < 6:
799 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
801 self._gcodeVBOs[n][5].render(GL_QUADS)
803 self._gcodeVBOs[n][6].render(GL_QUADS)
804 glColor3f(c/2, c/2, 0.0)
805 self._gcodeVBOs[n][7].render(GL_QUADS)
807 self._gcodeVBOs[n][8].render(GL_QUADS)
808 self._gcodeVBOs[n][9].render(GL_QUADS)
811 self._gcodeVBOs[n][0].render(GL_LINES)
813 self._gcodeVBOs[n][1].render(GL_LINES)
814 glColor3f(c/2, c/2, 0.0)
815 self._gcodeVBOs[n][2].render(GL_LINES)
817 self._gcodeVBOs[n][3].render(GL_LINES)
818 self._gcodeVBOs[n][4].render(GL_LINES)
821 glStencilFunc(GL_ALWAYS, 1, 1)
822 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
824 self._objectShader.bind()
825 for obj in self._scene.objects():
826 if obj._loadAnim is not None:
827 if obj._loadAnim.isDone():
832 if self._focusObj == obj:
834 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
837 if self._selectedObj == obj or self._selectedObj is None:
838 #If we want transparent, then first render a solid black model to remove the printer size lines.
839 if self.viewMode == 'transparent':
840 glColor4f(0, 0, 0, 0)
841 self._renderObject(obj)
843 glBlendFunc(GL_ONE, GL_ONE)
844 glDisable(GL_DEPTH_TEST)
846 if self.viewMode == 'xray':
847 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
848 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
849 glEnable(GL_STENCIL_TEST)
851 if not self._scene.checkPlatform(obj):
852 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
853 self._renderObject(obj)
855 self._renderObject(obj, brightness)
856 glDisable(GL_STENCIL_TEST)
858 glEnable(GL_DEPTH_TEST)
859 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
861 if self.viewMode == 'xray':
864 glEnable(GL_STENCIL_TEST)
865 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
866 glDisable(GL_DEPTH_TEST)
867 for i in xrange(2, 15, 2):
868 glStencilFunc(GL_EQUAL, i, 0xFF)
869 glColor(float(i)/10, float(i)/10, float(i)/5)
871 glVertex3f(-1000,-1000,-10)
872 glVertex3f( 1000,-1000,-10)
873 glVertex3f( 1000, 1000,-10)
874 glVertex3f(-1000, 1000,-10)
876 for i in xrange(1, 15, 2):
877 glStencilFunc(GL_EQUAL, i, 0xFF)
878 glColor(float(i)/10, 0, 0)
880 glVertex3f(-1000,-1000,-10)
881 glVertex3f( 1000,-1000,-10)
882 glVertex3f( 1000, 1000,-10)
883 glVertex3f(-1000, 1000,-10)
886 glDisable(GL_STENCIL_TEST)
887 glEnable(GL_DEPTH_TEST)
889 self._objectShader.unbind()
891 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
893 self._objectLoadShader.bind()
894 glColor4f(0.2, 0.6, 1.0, 1.0)
895 for obj in self._scene.objects():
896 if obj._loadAnim is None:
898 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
899 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
900 self._renderObject(obj)
901 self._objectLoadShader.unbind()
906 if self.viewMode == 'gcode':
909 #Draw the object box-shadow, so you can see where it will collide with other objects.
910 if self._selectedObj is not None and len(self._scene.objects()) > 1:
911 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
913 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
915 glEnable(GL_CULL_FACE)
916 glColor4f(0,0,0,0.12)
918 glVertex3f(-size[0], size[1], 0.1)
919 glVertex3f(-size[0], -size[1], 0.1)
920 glVertex3f( size[0], -size[1], 0.1)
921 glVertex3f( size[0], size[1], 0.1)
923 glDisable(GL_CULL_FACE)
926 #Draw the outline of the selected object, on top of everything else except the GUI.
927 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
928 glDisable(GL_DEPTH_TEST)
929 glEnable(GL_CULL_FACE)
930 glEnable(GL_STENCIL_TEST)
932 glStencilFunc(GL_EQUAL, 0, 255)
934 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
937 self._renderObject(self._selectedObj)
938 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
940 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
941 glDisable(GL_STENCIL_TEST)
942 glDisable(GL_CULL_FACE)
943 glEnable(GL_DEPTH_TEST)
945 if self._selectedObj is not None:
947 pos = self.getObjectCenterPos()
948 glTranslate(pos[0], pos[1], pos[2])
952 def _renderObject(self, obj, brightness = False):
954 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
956 if self.tempMatrix is not None and obj == self._selectedObj:
957 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
958 glMultMatrixf(tempMatrix)
960 offset = obj.getDrawOffset()
961 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
963 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
964 glMultMatrixf(tempMatrix)
967 for m in obj._meshList:
969 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
971 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
976 def _drawMachine(self):
977 glEnable(GL_CULL_FACE)
980 if profile.getPreference('machine_type') == 'ultimaker':
982 self._objectShader.bind()
983 self._renderObject(self._platformMesh)
984 self._objectShader.unbind()
986 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
987 v0 = [ size[0] / 2, size[1] / 2, size[2]]
988 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
989 v2 = [-size[0] / 2, size[1] / 2, size[2]]
990 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
991 v4 = [ size[0] / 2, size[1] / 2, 0]
992 v5 = [ size[0] / 2,-size[1] / 2, 0]
993 v6 = [-size[0] / 2, size[1] / 2, 0]
994 v7 = [-size[0] / 2,-size[1] / 2, 0]
996 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
997 glEnableClientState(GL_VERTEX_ARRAY)
998 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1000 glColor4ub(5, 171, 231, 64)
1001 glDrawArrays(GL_QUADS, 0, 4)
1002 glColor4ub(5, 171, 231, 96)
1003 glDrawArrays(GL_QUADS, 4, 8)
1004 glColor4ub(5, 171, 231, 128)
1005 glDrawArrays(GL_QUADS, 12, 8)
1007 sx = self._machineSize[0]
1008 sy = self._machineSize[1]
1009 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1010 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1015 x1 = max(min(x1, sx/2), -sx/2)
1016 y1 = max(min(y1, sy/2), -sy/2)
1017 x2 = max(min(x2, sx/2), -sx/2)
1018 y2 = max(min(y2, sy/2), -sy/2)
1019 if (x & 1) == (y & 1):
1020 glColor4ub(5, 171, 231, 127)
1022 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1024 glVertex3f(x1, y1, -0.02)
1025 glVertex3f(x2, y1, -0.02)
1026 glVertex3f(x2, y2, -0.02)
1027 glVertex3f(x1, y2, -0.02)
1030 glDisableClientState(GL_VERTEX_ARRAY)
1032 glDisable(GL_CULL_FACE)
1034 def _generateGCodeVBOs(self, layer):
1036 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1037 pointList = numpy.zeros((0,3), numpy.float32)
1039 if path.type == 'extrude' and path.pathType == extrudeType:
1041 a = numpy.concatenate((a[:-1], a[1:]), 1)
1042 a = a.reshape((len(a) * 2, 3))
1043 pointList = numpy.concatenate((pointList, a))
1044 ret.append(opengl.GLVBO(pointList))
1047 def _generateGCodeVBOs2(self, layer):
1048 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1049 filamentArea = math.pi * filamentRadius * filamentRadius
1052 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1053 pointList = numpy.zeros((0,3), numpy.float32)
1055 if path.type == 'extrude' and path.pathType == extrudeType:
1057 if extrudeType == 'FILL':
1060 normal = a[1:] - a[:-1]
1061 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1062 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1065 ePerDist = path.extrusion[1:] / lens
1066 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1068 normal[:,0] *= lineWidth
1069 normal[:,1] *= lineWidth
1071 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1072 b = numpy.concatenate((b, a[:-1] + normal), 1)
1073 b = numpy.concatenate((b, a[1:] + normal), 1)
1074 b = numpy.concatenate((b, a[1:] - normal), 1)
1075 b = numpy.concatenate((b, a[:-1] - normal), 1)
1076 b = b.reshape((len(b) * 4, 3))
1078 pointList = numpy.concatenate((pointList, b))
1079 ret.append(opengl.GLVBO(pointList))
1082 def getObjectCenterPos(self):
1083 if self._selectedObj is None:
1084 return [0.0, 0.0, 0.0]
1085 pos = self._selectedObj.getPosition()
1086 size = self._selectedObj.getSize()
1087 return [pos[0], pos[1], size[2]/2]
1089 def getObjectBoundaryCircle(self):
1090 if self._selectedObj is None:
1092 return self._selectedObj.getBoundaryCircle()
1094 def getObjectSize(self):
1095 if self._selectedObj is None:
1096 return [0.0, 0.0, 0.0]
1097 return self._selectedObj.getSize()
1099 def getObjectMatrix(self):
1100 if self._selectedObj is None:
1101 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1102 return self._selectedObj.getMatrix()
1104 class shaderEditor(wx.Dialog):
1105 def __init__(self, parent, callback, v, f):
1106 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1107 self._callback = callback
1108 s = wx.BoxSizer(wx.VERTICAL)
1110 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1111 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1112 s.Add(self._vertex, 1, flag=wx.EXPAND)
1113 s.Add(self._fragment, 1, flag=wx.EXPAND)
1115 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1116 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1118 self.SetPosition(self.GetParent().GetPosition())
1119 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1122 def OnText(self, e):
1123 self._callback(self._vertex.GetValue(), self._fragment.GetValue())