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), 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._objectShader is None:
696 self._objectShader = opengl.GLShader("""
697 varying float light_amount;
701 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
702 gl_FrontColor = gl_Color;
704 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
708 varying float light_amount;
712 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
715 self._objectLoadShader = opengl.GLShader("""
716 uniform float intensity;
718 varying float light_amount;
722 vec4 tmp = gl_Vertex;
723 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
724 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
725 gl_Position = gl_ModelViewProjectionMatrix * tmp;
726 gl_FrontColor = gl_Color;
728 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
732 uniform float intensity;
733 varying float light_amount;
737 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
741 glTranslate(0,0,-self._zoom)
742 glRotate(-self._pitch, 1,0,0)
743 glRotate(self._yaw, 0,0,1)
744 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
746 self._viewport = glGetIntegerv(GL_VIEWPORT)
747 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
748 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
750 glClearColor(1,1,1,1)
751 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
753 if self.viewMode != 'gcode':
754 for n in xrange(0, len(self._scene.objects())):
755 obj = self._scene.objects()[n]
756 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
757 self._renderObject(obj)
759 if self._mouseX > -1:
760 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
761 if n < len(self._scene.objects()):
762 self._focusObj = self._scene.objects()[n]
764 self._focusObj = None
765 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
766 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
767 self._mouse3Dpos -= self._viewTarget
770 glTranslate(0,0,-self._zoom)
771 glRotate(-self._pitch, 1,0,0)
772 glRotate(self._yaw, 0,0,1)
773 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
775 if self.viewMode == 'gcode':
776 if self._gcode is not None:
778 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
780 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
781 for n in xrange(0, drawUpTill):
782 c = 1.0 - float(drawUpTill - n) / 15
784 if len(self._gcodeVBOs) < n + 1:
785 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
786 if time.time() - t > 0.5:
789 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
790 if n == drawUpTill - 1:
791 if len(self._gcodeVBOs[n]) < 6:
792 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
794 self._gcodeVBOs[n][5].render(GL_QUADS)
796 self._gcodeVBOs[n][6].render(GL_QUADS)
797 glColor3f(c/2, c/2, 0.0)
798 self._gcodeVBOs[n][7].render(GL_QUADS)
800 self._gcodeVBOs[n][8].render(GL_QUADS)
801 self._gcodeVBOs[n][9].render(GL_QUADS)
804 self._gcodeVBOs[n][0].render(GL_LINES)
806 self._gcodeVBOs[n][1].render(GL_LINES)
807 glColor3f(c/2, c/2, 0.0)
808 self._gcodeVBOs[n][2].render(GL_LINES)
810 self._gcodeVBOs[n][3].render(GL_LINES)
811 self._gcodeVBOs[n][4].render(GL_LINES)
814 glStencilFunc(GL_ALWAYS, 1, 1)
815 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
817 self._objectShader.bind()
818 for obj in self._scene.objects():
819 if obj._loadAnim is not None:
820 if obj._loadAnim.isDone():
825 if self._focusObj == obj:
827 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
830 if self._selectedObj == obj or self._selectedObj is None:
831 #If we want transparent, then first render a solid black model to remove the printer size lines.
832 if self.viewMode == 'transparent':
833 glColor4f(0, 0, 0, 0)
834 self._renderObject(obj)
836 glBlendFunc(GL_ONE, GL_ONE)
837 glDisable(GL_DEPTH_TEST)
839 if self.viewMode == 'xray':
840 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
841 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
842 glEnable(GL_STENCIL_TEST)
844 if not self._scene.checkPlatform(obj):
845 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
846 self._renderObject(obj)
848 self._renderObject(obj, brightness)
849 glDisable(GL_STENCIL_TEST)
851 glEnable(GL_DEPTH_TEST)
852 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
854 if self.viewMode == 'xray':
857 glEnable(GL_STENCIL_TEST)
858 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
859 glDisable(GL_DEPTH_TEST)
860 for i in xrange(2, 15, 2):
861 glStencilFunc(GL_EQUAL, i, 0xFF)
862 glColor(float(i)/10, float(i)/10, float(i)/5)
864 glVertex3f(-1000,-1000,-10)
865 glVertex3f( 1000,-1000,-10)
866 glVertex3f( 1000, 1000,-10)
867 glVertex3f(-1000, 1000,-10)
869 for i in xrange(1, 15, 2):
870 glStencilFunc(GL_EQUAL, i, 0xFF)
871 glColor(float(i)/10, 0, 0)
873 glVertex3f(-1000,-1000,-10)
874 glVertex3f( 1000,-1000,-10)
875 glVertex3f( 1000, 1000,-10)
876 glVertex3f(-1000, 1000,-10)
879 glDisable(GL_STENCIL_TEST)
880 glEnable(GL_DEPTH_TEST)
882 self._objectShader.unbind()
884 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
886 self._objectLoadShader.bind()
887 glColor4f(0.2, 0.6, 1.0, 1.0)
888 for obj in self._scene.objects():
889 if obj._loadAnim is None:
891 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
892 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
893 self._renderObject(obj)
894 self._objectLoadShader.unbind()
899 if self.viewMode == 'gcode':
902 #Draw the object box-shadow, so you can see where it will collide with other objects.
903 if self._selectedObj is not None and len(self._scene.objects()) > 1:
904 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
906 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
908 glEnable(GL_CULL_FACE)
909 glColor4f(0,0,0,0.12)
911 glVertex3f(-size[0], size[1], 0.1)
912 glVertex3f(-size[0], -size[1], 0.1)
913 glVertex3f( size[0], -size[1], 0.1)
914 glVertex3f( size[0], size[1], 0.1)
916 glDisable(GL_CULL_FACE)
919 #Draw the outline of the selected object, on top of everything else except the GUI.
920 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
921 glDisable(GL_DEPTH_TEST)
922 glEnable(GL_CULL_FACE)
923 glEnable(GL_STENCIL_TEST)
925 glStencilFunc(GL_EQUAL, 0, 255)
927 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
930 self._renderObject(self._selectedObj)
931 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
933 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
934 glDisable(GL_STENCIL_TEST)
935 glDisable(GL_CULL_FACE)
936 glEnable(GL_DEPTH_TEST)
938 if self._selectedObj is not None:
940 pos = self.getObjectCenterPos()
941 glTranslate(pos[0], pos[1], pos[2])
945 def _renderObject(self, obj, brightness = False):
947 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
949 if self.tempMatrix is not None and obj == self._selectedObj:
950 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
951 glMultMatrixf(tempMatrix)
953 offset = obj.getDrawOffset()
954 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
956 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
957 glMultMatrixf(tempMatrix)
960 for m in obj._meshList:
962 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
964 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
969 def _drawMachine(self):
970 glEnable(GL_CULL_FACE)
973 if profile.getPreference('machine_type') == 'ultimaker':
975 self._objectShader.bind()
976 self._renderObject(self._platformMesh)
977 self._objectShader.unbind()
979 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
980 v0 = [ size[0] / 2, size[1] / 2, size[2]]
981 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
982 v2 = [-size[0] / 2, size[1] / 2, size[2]]
983 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
984 v4 = [ size[0] / 2, size[1] / 2, 0]
985 v5 = [ size[0] / 2,-size[1] / 2, 0]
986 v6 = [-size[0] / 2, size[1] / 2, 0]
987 v7 = [-size[0] / 2,-size[1] / 2, 0]
989 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
990 glEnableClientState(GL_VERTEX_ARRAY)
991 glVertexPointer(3, GL_FLOAT, 3*4, vList)
993 glColor4ub(5, 171, 231, 64)
994 glDrawArrays(GL_QUADS, 0, 4)
995 glColor4ub(5, 171, 231, 96)
996 glDrawArrays(GL_QUADS, 4, 8)
997 glColor4ub(5, 171, 231, 128)
998 glDrawArrays(GL_QUADS, 12, 8)
1000 sx = self._machineSize[0]
1001 sy = self._machineSize[1]
1002 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1003 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1008 x1 = max(min(x1, sx/2), -sx/2)
1009 y1 = max(min(y1, sy/2), -sy/2)
1010 x2 = max(min(x2, sx/2), -sx/2)
1011 y2 = max(min(y2, sy/2), -sy/2)
1012 if (x & 1) == (y & 1):
1013 glColor4ub(5, 171, 231, 127)
1015 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1017 glVertex3f(x1, y1, -0.02)
1018 glVertex3f(x2, y1, -0.02)
1019 glVertex3f(x2, y2, -0.02)
1020 glVertex3f(x1, y2, -0.02)
1023 glDisableClientState(GL_VERTEX_ARRAY)
1025 glDisable(GL_CULL_FACE)
1027 def _generateGCodeVBOs(self, layer):
1029 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1030 pointList = numpy.zeros((0,3), numpy.float32)
1032 if path.type == 'extrude' and path.pathType == extrudeType:
1034 a = numpy.concatenate((a[:-1], a[1:]), 1)
1035 a = a.reshape((len(a) * 2, 3))
1036 pointList = numpy.concatenate((pointList, a))
1037 ret.append(opengl.GLVBO(pointList))
1040 def _generateGCodeVBOs2(self, layer):
1041 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1042 filamentArea = math.pi * filamentRadius * filamentRadius
1045 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1046 pointList = numpy.zeros((0,3), numpy.float32)
1048 if path.type == 'extrude' and path.pathType == extrudeType:
1050 if extrudeType == 'FILL':
1053 normal = a[1:] - a[:-1]
1054 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1055 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1058 ePerDist = path.extrusion[1:] / lens
1059 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1061 normal[:,0] *= lineWidth
1062 normal[:,1] *= lineWidth
1064 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1065 b = numpy.concatenate((b, a[:-1] + normal), 1)
1066 b = numpy.concatenate((b, a[1:] + normal), 1)
1067 b = numpy.concatenate((b, a[1:] - normal), 1)
1068 b = numpy.concatenate((b, a[:-1] - normal), 1)
1069 b = b.reshape((len(b) * 4, 3))
1071 pointList = numpy.concatenate((pointList, b))
1072 ret.append(opengl.GLVBO(pointList))
1075 def getObjectCenterPos(self):
1076 if self._selectedObj is None:
1077 return [0.0, 0.0, 0.0]
1078 pos = self._selectedObj.getPosition()
1079 size = self._selectedObj.getSize()
1080 return [pos[0], pos[1], size[2]/2]
1082 def getObjectBoundaryCircle(self):
1083 if self._selectedObj is None:
1085 return self._selectedObj.getBoundaryCircle()
1087 def getObjectSize(self):
1088 if self._selectedObj is None:
1089 return [0.0, 0.0, 0.0]
1090 return self._selectedObj.getSize()
1092 def getObjectMatrix(self):
1093 if self._selectedObj is None:
1094 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1095 return self._selectedObj.getMatrix()
1097 class shaderEditor(wx.Dialog):
1098 def __init__(self, parent, callback, v, f):
1099 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1100 self._callback = callback
1101 s = wx.BoxSizer(wx.VERTICAL)
1103 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1104 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1105 s.Add(self._vertex, 1, flag=wx.EXPAND)
1106 s.Add(self._fragment, 1, flag=wx.EXPAND)
1108 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1109 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1111 self.SetPosition(self.GetParent().GetPosition())
1112 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1115 def OnText(self, e):
1116 self._callback(self._vertex.GetValue(), self._fragment.GetValue())