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):
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.wildcardFilter())
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 ShowPrintWindow(self, button):
131 if machineCom.machineIsConnected():
132 printWindow.printFile(self._slicer.getGCodeFilename())
133 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
134 drives = removableStorage.getPossibleSDcardDrives()
136 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))
137 if dlg.ShowModal() != wx.ID_OK:
140 drive = drives[dlg.GetSelection()]
144 filename = os.path.basename(profile.getPreference('lastFile'))
145 filename = filename[0:filename.rfind('.')] + '.gcode'
147 shutil.copy(self._slicer.getGCodeFilename(), drive[1] + filename)
149 self.notification.message("Failed to save to SD card")
151 self.notification.message("Saved as %s" % (drive[1] + filename))
153 self._showSaveGCode()
156 self.Bind(wx.EVT_MENU, lambda e: printWindow.printFile(self._slicer.getGCodeFilename()), menu.Append(-1, 'Print with USB'))
157 self.Bind(wx.EVT_MENU, lambda e: self._showSaveGCode(), menu.Append(-1, 'Save GCode...'))
161 def _showSaveGCode(self):
162 defPath = profile.getPreference('lastFile')
163 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
164 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
165 dlg.SetFilename(defPath)
166 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
167 if dlg.ShowModal() != wx.ID_OK:
170 filename = dlg.GetPath()
174 shutil.copy(self._slicer.getGCodeFilename(), filename)
176 self.notification.message("Failed to save")
178 self.notification.message("Saved as %s" % (filename))
180 def OnToolSelect(self, button):
181 if self.rotateToolButton.getSelected():
182 self.tool = previewTools.toolRotate(self)
183 elif self.scaleToolButton.getSelected():
184 self.tool = previewTools.toolScale(self)
185 elif self.mirrorToolButton.getSelected():
186 self.tool = previewTools.toolNone(self)
188 self.tool = previewTools.toolNone(self)
189 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
190 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
191 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
192 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
193 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
194 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
195 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
196 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
198 def updateToolButtons(self):
199 if self._selectedObj is None:
203 self.rotateToolButton.setHidden(hidden)
204 self.scaleToolButton.setHidden(hidden)
205 self.mirrorToolButton.setHidden(hidden)
207 self.rotateToolButton.setSelected(False)
208 self.scaleToolButton.setSelected(False)
209 self.mirrorToolButton.setSelected(False)
212 def OnViewChange(self):
213 if self.viewSelection.getValue() == 3:
214 self.viewMode = 'gcode'
215 if self._gcode is not None:
216 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
217 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
218 self._selectObject(None)
219 elif self.viewSelection.getValue() == 1:
220 self.viewMode = 'transparent'
221 elif self.viewSelection.getValue() == 2:
222 self.viewMode = 'xray'
224 self.viewMode = 'normal'
225 self.layerSelect.setHidden(self.viewMode != 'gcode')
226 self.openFileButton.setHidden(self.viewMode == 'gcode')
229 def OnRotateReset(self, button):
230 if self._selectedObj is None:
232 self._selectedObj.resetRotation()
233 self._scene.pushFree()
234 self._selectObject(self._selectedObj)
236 def OnLayFlat(self, button):
237 if self._selectedObj is None:
239 self._selectedObj.layFlat()
240 self._scene.pushFree()
241 self._selectObject(self._selectedObj)
243 def OnScaleReset(self, button):
244 if self._selectedObj is None:
246 self._selectedObj.resetScale()
248 def OnScaleMax(self, button):
249 if self._selectedObj is None:
251 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
252 self._scene.pushFree()
253 self._selectObject(self._selectedObj)
254 self.updateProfileToControls()
257 def OnMirror(self, axis):
258 if self._selectedObj is None:
260 self._selectedObj.mirror(axis)
263 def OnScaleEntry(self, value, axis):
264 if self._selectedObj is None:
270 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
271 self.updateProfileToControls()
272 self._scene.pushFree()
273 self._selectObject(self._selectedObj)
276 def OnScaleEntryMM(self, value, axis):
277 if self._selectedObj is None:
283 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
284 self.updateProfileToControls()
285 self._scene.pushFree()
286 self._selectObject(self._selectedObj)
289 def OnDeleteAll(self, e):
290 while len(self._scene.objects()) > 0:
291 self._deleteObject(self._scene.objects()[0])
292 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
294 def OnMultiply(self, e):
295 if self._focusObj is None:
298 dlg = wx.NumberEntryDialog(self, "How many copies need to be made?", "Copies", "Multiply", 1, 1, 100)
299 if dlg.ShowModal() != wx.ID_OK:
308 self._scene.add(newObj)
309 self._scene.centerAll()
310 if not self._scene.checkPlatform(newObj):
314 self._scene.remove(newObj)
315 self._scene.centerAll()
318 def OnSplitObject(self, e):
319 if self._focusObj is None:
321 self._scene.remove(self._focusObj)
322 for obj in self._focusObj.split():
324 self._scene.centerAll()
325 self._selectObject(None)
328 def OnMergeObjects(self, e):
329 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
331 self._scene.merge(self._selectedObj, self._focusObj)
334 def sceneUpdated(self):
335 self._sceneUpdateTimer.Start(1, True)
336 self._slicer.abortSlicer()
337 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
340 def _onRunSlicer(self, e):
341 if self._isSimpleMode:
342 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
343 self._slicer.runSlicer(self._scene)
344 if self._isSimpleMode:
345 profile.resetTempOverride()
347 def _updateSliceProgress(self, progressValue, ready):
348 self.printButton.setDisabled(not ready)
349 self.printButton.setProgressBar(progressValue)
350 if self._gcode is not None:
352 for layerVBOlist in self._gcodeVBOs:
353 for vbo in layerVBOlist:
354 self.glReleaseList.append(vbo)
357 self._gcode = gcodeInterpreter.gcode()
358 self._gcode.progressCallback = self._gcodeLoadCallback
359 self._thread = threading.Thread(target=self._loadGCode)
360 self._thread.daemon = True
364 def _loadGCode(self):
365 self._gcode.load(self._slicer.getGCodeFilename())
367 def _gcodeLoadCallback(self, progress):
368 if self._gcode is None:
370 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
371 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
372 self.layerSelect.setValue(self.layerSelect.getMaxValue())
374 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
377 def loadScene(self, fileList):
378 for filename in fileList:
380 objList = meshLoader.loadMeshes(filename)
382 traceback.print_exc()
385 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
387 self._scene.centerAll()
388 self._selectObject(obj)
391 def _deleteObject(self, obj):
392 if obj == self._selectedObj:
393 self._selectObject(None)
394 if obj == self._focusObj:
395 self._focusObj = None
396 self._scene.remove(obj)
397 for m in obj._meshList:
398 if m.vbo is not None and m.vbo.decRef():
399 self.glReleaseList.append(m.vbo)
400 if self._isSimpleMode:
401 self._scene.arrangeAll()
404 def _selectObject(self, obj, zoom = True):
405 if obj != self._selectedObj:
406 self._selectedObj = obj
407 self.updateProfileToControls()
408 self.updateToolButtons()
409 if zoom and obj is not None:
410 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
411 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
412 newZoom = obj.getBoundaryCircle() * 6
413 if newZoom > numpy.max(self._machineSize) * 3:
414 newZoom = numpy.max(self._machineSize) * 3
415 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
417 def updateProfileToControls(self):
418 oldSimpleMode = self._isSimpleMode
419 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
420 if self._isSimpleMode and not oldSimpleMode:
421 self._scene.arrangeAll()
423 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
424 self._objColors[0] = profile.getPreferenceColour('model_colour')
425 self._objColors[1] = profile.getPreferenceColour('model_colour2')
426 self._objColors[2] = profile.getPreferenceColour('model_colour3')
427 self._objColors[3] = profile.getPreferenceColour('model_colour4')
428 self._scene.setMachineSize(self._machineSize)
429 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
430 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'))
432 if self._selectedObj is not None:
433 scale = self._selectedObj.getScale()
434 size = self._selectedObj.getSize()
435 self.scaleXctrl.setValue(round(scale[0], 2))
436 self.scaleYctrl.setValue(round(scale[1], 2))
437 self.scaleZctrl.setValue(round(scale[2], 2))
438 self.scaleXmmctrl.setValue(round(size[0], 2))
439 self.scaleYmmctrl.setValue(round(size[1], 2))
440 self.scaleZmmctrl.setValue(round(size[2], 2))
442 def OnKeyChar(self, keyCode):
443 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
444 if self._selectedObj is not None:
445 self._deleteObject(self._selectedObj)
447 if keyCode == wx.WXK_UP:
448 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
450 elif keyCode == wx.WXK_DOWN:
451 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
453 elif keyCode == wx.WXK_PAGEUP:
454 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
456 elif keyCode == wx.WXK_PAGEDOWN:
457 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
460 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
461 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
463 def ShaderUpdate(self, v, f):
464 s = opengl.GLShader(v, f)
466 self._objectLoadShader.release()
467 self._objectLoadShader = s
468 for obj in self._scene.objects():
469 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
472 def OnMouseDown(self,e):
473 self._mouseX = e.GetX()
474 self._mouseY = e.GetY()
475 self._mouseClick3DPos = self._mouse3Dpos
476 self._mouseClickFocus = self._focusObj
478 self._mouseState = 'doubleClick'
480 self._mouseState = 'dragOrClick'
481 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
482 p0 -= self.getObjectCenterPos() - self._viewTarget
483 p1 -= self.getObjectCenterPos() - self._viewTarget
484 if self.tool.OnDragStart(p0, p1):
485 self._mouseState = 'tool'
486 if self._mouseState == 'dragOrClick':
487 if e.GetButton() == 1:
488 if self._focusObj is not None:
489 self._selectObject(self._focusObj, False)
492 def OnMouseUp(self, e):
493 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
495 if self._mouseState == 'dragOrClick':
496 if e.GetButton() == 1:
497 self._selectObject(self._focusObj)
498 if e.GetButton() == 3:
500 if self._focusObj is not None:
501 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
502 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
503 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
504 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
505 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
506 if len(self._scene.objects()) > 0:
507 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
508 if menu.MenuItemCount > 0:
511 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
512 self._scene.pushFree()
514 elif self._mouseState == 'tool':
515 if self.tempMatrix is not None and self._selectedObj is not None:
516 self._selectedObj.applyMatrix(self.tempMatrix)
517 self._scene.pushFree()
518 self._selectObject(self._selectedObj)
519 self.tempMatrix = None
520 self.tool.OnDragEnd()
522 self._mouseState = None
524 def OnMouseMotion(self,e):
525 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
526 p0 -= self.getObjectCenterPos() - self._viewTarget
527 p1 -= self.getObjectCenterPos() - self._viewTarget
529 if e.Dragging() and self._mouseState is not None:
530 if self._mouseState == 'tool':
531 self.tool.OnDrag(p0, p1)
532 elif not e.LeftIsDown() and e.RightIsDown():
533 self._mouseState = 'drag'
534 self._yaw += e.GetX() - self._mouseX
535 self._pitch -= e.GetY() - self._mouseY
536 if self._pitch > 170:
540 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
541 self._mouseState = 'drag'
542 self._zoom += e.GetY() - self._mouseY
545 if self._zoom > numpy.max(self._machineSize) * 3:
546 self._zoom = numpy.max(self._machineSize) * 3
547 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
548 self._mouseState = 'dragObject'
549 z = max(0, self._mouseClick3DPos[2])
550 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
551 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
556 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
557 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
558 diff = cursorZ1 - cursorZ0
559 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
560 if not e.Dragging() or self._mouseState != 'tool':
561 self.tool.OnMouseMove(p0, p1)
563 self._mouseX = e.GetX()
564 self._mouseY = e.GetY()
566 def OnMouseWheel(self, e):
567 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
568 delta = max(min(delta,4),-4)
569 self._zoom *= 1.0 - delta / 10.0
572 if self._zoom > numpy.max(self._machineSize) * 3:
573 self._zoom = numpy.max(self._machineSize) * 3
576 def getMouseRay(self, x, y):
577 if self._viewport is None:
578 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
579 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
580 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
581 p0 -= self._viewTarget
582 p1 -= self._viewTarget
585 def _init3DView(self):
586 # set viewing projection
587 size = self.GetSize()
588 glViewport(0, 0, size.GetWidth(), size.GetHeight())
591 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
593 glDisable(GL_RESCALE_NORMAL)
594 glDisable(GL_LIGHTING)
596 glEnable(GL_DEPTH_TEST)
597 glDisable(GL_CULL_FACE)
599 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
601 glClearColor(0.8, 0.8, 0.8, 1.0)
605 glMatrixMode(GL_PROJECTION)
607 aspect = float(size.GetWidth()) / float(size.GetHeight())
608 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
610 glMatrixMode(GL_MODELVIEW)
612 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
615 if machineCom.machineIsConnected():
616 self.printButton._imageID = 6
617 self.printButton._tooltip = 'Print'
618 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
619 self.printButton._imageID = 2
620 self.printButton._tooltip = 'Toolpath to SD'
622 self.printButton._imageID = 3
623 self.printButton._tooltip = 'Save toolpath'
625 if self._animView is not None:
626 self._viewTarget = self._animView.getPosition()
627 if self._animView.isDone():
628 self._animView = None
629 if self._animZoom is not None:
630 self._zoom = self._animZoom.getPosition()
631 if self._animZoom.isDone():
632 self._animZoom = None
633 if self._objectShader is None:
634 self._objectShader = opengl.GLShader("""
635 varying float light_amount;
639 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
640 gl_FrontColor = gl_Color;
642 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
646 varying float light_amount;
650 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
653 self._objectLoadShader = opengl.GLShader("""
654 uniform float intensity;
656 varying float light_amount;
660 vec4 tmp = gl_Vertex;
661 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
662 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
663 gl_Position = gl_ModelViewProjectionMatrix * tmp;
664 gl_FrontColor = gl_Color;
666 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
670 uniform float intensity;
671 varying float light_amount;
675 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
679 glTranslate(0,0,-self._zoom)
680 glRotate(-self._pitch, 1,0,0)
681 glRotate(self._yaw, 0,0,1)
682 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
684 self._viewport = glGetIntegerv(GL_VIEWPORT)
685 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
686 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
688 glClearColor(1,1,1,1)
689 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
691 if self.viewMode != 'gcode':
692 for n in xrange(0, len(self._scene.objects())):
693 obj = self._scene.objects()[n]
694 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
695 self._renderObject(obj)
697 if self._mouseX > -1:
698 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
699 if n < len(self._scene.objects()):
700 self._focusObj = self._scene.objects()[n]
702 self._focusObj = None
703 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
704 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
705 self._mouse3Dpos -= self._viewTarget
708 glTranslate(0,0,-self._zoom)
709 glRotate(-self._pitch, 1,0,0)
710 glRotate(self._yaw, 0,0,1)
711 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
713 if self.viewMode == 'gcode':
714 if self._gcode is not None:
716 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
718 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
719 for n in xrange(0, drawUpTill):
720 c = 1.0 - float(drawUpTill - n) / 15
722 if len(self._gcodeVBOs) < n + 1:
723 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
724 if time.time() - t > 0.5:
727 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
728 if n == drawUpTill - 1:
729 if len(self._gcodeVBOs[n]) < 6:
730 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
732 self._gcodeVBOs[n][5].render(GL_QUADS)
734 self._gcodeVBOs[n][6].render(GL_QUADS)
735 glColor3f(c/2, c/2, 0.0)
736 self._gcodeVBOs[n][7].render(GL_QUADS)
738 self._gcodeVBOs[n][8].render(GL_QUADS)
739 self._gcodeVBOs[n][9].render(GL_QUADS)
742 self._gcodeVBOs[n][0].render(GL_LINES)
744 self._gcodeVBOs[n][1].render(GL_LINES)
745 glColor3f(c/2, c/2, 0.0)
746 self._gcodeVBOs[n][2].render(GL_LINES)
748 self._gcodeVBOs[n][3].render(GL_LINES)
749 self._gcodeVBOs[n][4].render(GL_LINES)
752 glStencilFunc(GL_ALWAYS, 1, 1)
753 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
755 self._objectShader.bind()
756 for obj in self._scene.objects():
757 if obj._loadAnim is not None:
758 if obj._loadAnim.isDone():
763 if self._focusObj == obj:
765 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
768 if self._selectedObj == obj or self._selectedObj is None:
769 #If we want transparent, then first render a solid black model to remove the printer size lines.
770 if self.viewMode == 'transparent':
771 glColor4f(0, 0, 0, 0)
772 self._renderObject(obj)
774 glBlendFunc(GL_ONE, GL_ONE)
775 glDisable(GL_DEPTH_TEST)
777 if self.viewMode == 'xray':
778 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
779 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
780 glEnable(GL_STENCIL_TEST)
782 if not self._scene.checkPlatform(obj):
783 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
784 self._renderObject(obj)
786 self._renderObject(obj, brightness)
787 glDisable(GL_STENCIL_TEST)
789 glEnable(GL_DEPTH_TEST)
790 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
792 if self.viewMode == 'xray':
795 glEnable(GL_STENCIL_TEST)
796 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
797 glDisable(GL_DEPTH_TEST)
798 for i in xrange(2, 15, 2):
799 glStencilFunc(GL_EQUAL, i, 0xFF)
800 glColor(float(i)/10, float(i)/10, float(i)/5)
802 glVertex3f(-1000,-1000,-10)
803 glVertex3f( 1000,-1000,-10)
804 glVertex3f( 1000, 1000,-10)
805 glVertex3f(-1000, 1000,-10)
807 for i in xrange(1, 15, 2):
808 glStencilFunc(GL_EQUAL, i, 0xFF)
809 glColor(float(i)/10, 0, 0)
811 glVertex3f(-1000,-1000,-10)
812 glVertex3f( 1000,-1000,-10)
813 glVertex3f( 1000, 1000,-10)
814 glVertex3f(-1000, 1000,-10)
817 glDisable(GL_STENCIL_TEST)
818 glEnable(GL_DEPTH_TEST)
820 self._objectShader.unbind()
822 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
824 self._objectLoadShader.bind()
825 glColor4f(0.2, 0.6, 1.0, 1.0)
826 for obj in self._scene.objects():
827 if obj._loadAnim is None:
829 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
830 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
831 self._renderObject(obj)
832 self._objectLoadShader.unbind()
837 if self.viewMode == 'gcode':
840 #Draw the object box-shadow, so you can see where it will collide with other objects.
841 if self._selectedObj is not None and len(self._scene.objects()) > 1:
842 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
844 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
846 glEnable(GL_CULL_FACE)
847 glColor4f(0,0,0,0.12)
849 glVertex3f(-size[0], size[1], 0.1)
850 glVertex3f(-size[0], -size[1], 0.1)
851 glVertex3f( size[0], -size[1], 0.1)
852 glVertex3f( size[0], size[1], 0.1)
854 glDisable(GL_CULL_FACE)
857 #Draw the outline of the selected object, on top of everything else except the GUI.
858 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
859 glDisable(GL_DEPTH_TEST)
860 glEnable(GL_CULL_FACE)
861 glEnable(GL_STENCIL_TEST)
863 glStencilFunc(GL_EQUAL, 0, 255)
865 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
868 self._renderObject(self._selectedObj)
869 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
871 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
872 glDisable(GL_STENCIL_TEST)
873 glDisable(GL_CULL_FACE)
874 glEnable(GL_DEPTH_TEST)
876 if self._selectedObj is not None:
878 pos = self.getObjectCenterPos()
879 glTranslate(pos[0], pos[1], pos[2])
883 def _renderObject(self, obj, brightness = False):
885 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
887 if self.tempMatrix is not None and obj == self._selectedObj:
888 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
889 glMultMatrixf(tempMatrix)
891 offset = obj.getDrawOffset()
892 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
894 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
895 glMultMatrixf(tempMatrix)
898 for m in obj._meshList:
900 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
902 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
907 def _drawMachine(self):
908 glEnable(GL_CULL_FACE)
911 if profile.getPreference('machine_type') == 'ultimaker':
913 self._objectShader.bind()
914 self._renderObject(self._platformMesh)
915 self._objectShader.unbind()
917 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
918 v0 = [ size[0] / 2, size[1] / 2, size[2]]
919 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
920 v2 = [-size[0] / 2, size[1] / 2, size[2]]
921 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
922 v4 = [ size[0] / 2, size[1] / 2, 0]
923 v5 = [ size[0] / 2,-size[1] / 2, 0]
924 v6 = [-size[0] / 2, size[1] / 2, 0]
925 v7 = [-size[0] / 2,-size[1] / 2, 0]
927 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
928 glEnableClientState(GL_VERTEX_ARRAY)
929 glVertexPointer(3, GL_FLOAT, 3*4, vList)
931 glColor4ub(5, 171, 231, 64)
932 glDrawArrays(GL_QUADS, 0, 4)
933 glColor4ub(5, 171, 231, 96)
934 glDrawArrays(GL_QUADS, 4, 8)
935 glColor4ub(5, 171, 231, 128)
936 glDrawArrays(GL_QUADS, 12, 8)
938 sx = self._machineSize[0]
939 sy = self._machineSize[1]
940 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
941 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
946 x1 = max(min(x1, sx/2), -sx/2)
947 y1 = max(min(y1, sy/2), -sy/2)
948 x2 = max(min(x2, sx/2), -sx/2)
949 y2 = max(min(y2, sy/2), -sy/2)
950 if (x & 1) == (y & 1):
951 glColor4ub(5, 171, 231, 127)
953 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
955 glVertex3f(x1, y1, -0.02)
956 glVertex3f(x2, y1, -0.02)
957 glVertex3f(x2, y2, -0.02)
958 glVertex3f(x1, y2, -0.02)
961 glDisableClientState(GL_VERTEX_ARRAY)
963 glDisable(GL_CULL_FACE)
965 def _generateGCodeVBOs(self, layer):
967 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
968 pointList = numpy.zeros((0,3), numpy.float32)
970 if path.type == 'extrude' and path.pathType == extrudeType:
971 a = numpy.array(path.points, numpy.float32)
972 a = numpy.concatenate((a[:-1], a[1:]), 1)
973 a = a.reshape((len(a) * 2, 3))
974 pointList = numpy.concatenate((pointList, a))
975 ret.append(opengl.GLVBO(pointList))
978 def _generateGCodeVBOs2(self, layer):
979 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
980 filamentArea = math.pi * filamentRadius * filamentRadius
983 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
984 pointList = numpy.zeros((0,3), numpy.float32)
986 if path.type == 'extrude' and path.pathType == extrudeType:
987 a = numpy.array(path.points, numpy.float32)
988 if extrudeType == 'FILL':
991 normal = a[1:] - a[:-1]
992 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
993 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
996 ePerDist = path.extrusion[1:] / lens
997 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
999 normal[:,0] *= lineWidth
1000 normal[:,1] *= lineWidth
1002 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1003 b = numpy.concatenate((b, a[:-1] + normal), 1)
1004 b = numpy.concatenate((b, a[1:] + normal), 1)
1005 b = numpy.concatenate((b, a[1:] - normal), 1)
1006 b = numpy.concatenate((b, a[:-1] - normal), 1)
1007 b = b.reshape((len(b) * 4, 3))
1009 pointList = numpy.concatenate((pointList, b))
1010 ret.append(opengl.GLVBO(pointList))
1013 def getObjectCenterPos(self):
1014 if self._selectedObj is None:
1015 return [0.0, 0.0, 0.0]
1016 pos = self._selectedObj.getPosition()
1017 size = self._selectedObj.getSize()
1018 return [pos[0], pos[1], size[2]/2]
1020 def getObjectBoundaryCircle(self):
1021 if self._selectedObj is None:
1023 return self._selectedObj.getBoundaryCircle()
1025 def getObjectSize(self):
1026 if self._selectedObj is None:
1027 return [0.0, 0.0, 0.0]
1028 return self._selectedObj.getSize()
1030 def getObjectMatrix(self):
1031 if self._selectedObj is None:
1032 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1033 return self._selectedObj.getMatrix()
1035 class shaderEditor(wx.Dialog):
1036 def __init__(self, parent, callback, v, f):
1037 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1038 self._callback = callback
1039 s = wx.BoxSizer(wx.VERTICAL)
1041 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1042 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1043 s.Add(self._vertex, 1, flag=wx.EXPAND)
1044 s.Add(self._fragment, 1, flag=wx.EXPAND)
1046 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1047 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1049 self.SetPosition(self.GetParent().GetPosition())
1050 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1053 def OnText(self, e):
1054 self._callback(self._vertex.GetValue(), self._fragment.GetValue())