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._selectedObj == obj:
764 #If we want transparent, then first render a solid black model to remove the printer size lines.
765 if self.viewMode == 'transparent':
766 glColor4f(0, 0, 0, 0)
767 self._renderObject(obj)
769 glBlendFunc(GL_ONE, GL_ONE)
770 glDisable(GL_DEPTH_TEST)
772 if self.viewMode == 'xray':
773 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
774 glEnable(GL_STENCIL_TEST)
775 if self._focusObj == obj:
777 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
779 if not self._scene.checkPlatform(obj):
780 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
781 self._renderObject(obj)
783 self._renderObject(obj, brightness)
784 glDisable(GL_STENCIL_TEST)
786 glEnable(GL_DEPTH_TEST)
787 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
789 if obj == self._selectedObj and self.viewMode == 'xray':
792 glEnable(GL_STENCIL_TEST)
793 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP)
794 glDisable(GL_DEPTH_TEST)
795 for i in xrange(2, 15, 2):
796 glStencilFunc(GL_EQUAL, i, 0xFF);
797 glColor(float(i)/10, float(i)/10, float(i)/5)
799 glVertex3f(-1000,-1000,-1)
800 glVertex3f( 1000,-1000,-1)
801 glVertex3f( 1000, 1000,-1)
802 glVertex3f(-1000, 1000,-1)
804 for i in xrange(1, 15, 2):
805 glStencilFunc(GL_EQUAL, i, 0xFF);
806 glColor(float(i)/10, 0, 0)
808 glVertex3f(-1000,-1000,-1)
809 glVertex3f( 1000,-1000,-1)
810 glVertex3f( 1000, 1000,-1)
811 glVertex3f(-1000, 1000,-1)
814 glDisable(GL_STENCIL_TEST)
815 glEnable(GL_DEPTH_TEST)
817 self._objectShader.unbind()
819 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
821 self._objectLoadShader.bind()
822 glColor4f(0.2, 0.6, 1.0, 1.0)
823 for obj in self._scene.objects():
824 if obj._loadAnim is None:
826 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
827 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
828 self._renderObject(obj)
829 self._objectLoadShader.unbind()
834 if self.viewMode == 'gcode':
837 #Draw the object box-shadow, so you can see where it will collide with other objects.
838 if self._selectedObj is not None and len(self._scene.objects()) > 1:
839 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
841 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
843 glEnable(GL_CULL_FACE)
844 glColor4f(0,0,0,0.12)
846 glVertex3f(-size[0], size[1], 0.1)
847 glVertex3f(-size[0], -size[1], 0.1)
848 glVertex3f( size[0], -size[1], 0.1)
849 glVertex3f( size[0], size[1], 0.1)
851 glDisable(GL_CULL_FACE)
854 #Draw the outline of the selected object, on top of everything else except the GUI.
855 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
856 glDisable(GL_DEPTH_TEST)
857 glEnable(GL_CULL_FACE)
858 glEnable(GL_STENCIL_TEST)
860 glStencilFunc(GL_EQUAL, 0, 255)
862 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
865 self._renderObject(self._selectedObj)
866 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
868 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
869 glDisable(GL_STENCIL_TEST)
870 glDisable(GL_CULL_FACE)
871 glEnable(GL_DEPTH_TEST)
873 if self._selectedObj is not None:
875 pos = self.getObjectCenterPos()
876 glTranslate(pos[0], pos[1], pos[2])
880 def _renderObject(self, obj, brightness = False):
882 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
884 if self.tempMatrix is not None and obj == self._selectedObj:
885 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
886 glMultMatrixf(tempMatrix)
888 offset = obj.getDrawOffset()
889 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
891 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
892 glMultMatrixf(tempMatrix)
895 for m in obj._meshList:
897 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
899 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
904 def _drawMachine(self):
905 glEnable(GL_CULL_FACE)
908 if profile.getPreference('machine_type') == 'ultimaker':
910 self._objectShader.bind()
911 self._renderObject(self._platformMesh)
912 self._objectShader.unbind()
914 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
915 v0 = [ size[0] / 2, size[1] / 2, size[2]]
916 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
917 v2 = [-size[0] / 2, size[1] / 2, size[2]]
918 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
919 v4 = [ size[0] / 2, size[1] / 2, 0]
920 v5 = [ size[0] / 2,-size[1] / 2, 0]
921 v6 = [-size[0] / 2, size[1] / 2, 0]
922 v7 = [-size[0] / 2,-size[1] / 2, 0]
924 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
925 glEnableClientState(GL_VERTEX_ARRAY)
926 glVertexPointer(3, GL_FLOAT, 3*4, vList)
928 glColor4ub(5, 171, 231, 64)
929 glDrawArrays(GL_QUADS, 0, 4)
930 glColor4ub(5, 171, 231, 96)
931 glDrawArrays(GL_QUADS, 4, 8)
932 glColor4ub(5, 171, 231, 128)
933 glDrawArrays(GL_QUADS, 12, 8)
935 sx = self._machineSize[0]
936 sy = self._machineSize[1]
937 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
938 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
943 x1 = max(min(x1, sx/2), -sx/2)
944 y1 = max(min(y1, sy/2), -sy/2)
945 x2 = max(min(x2, sx/2), -sx/2)
946 y2 = max(min(y2, sy/2), -sy/2)
947 if (x & 1) == (y & 1):
948 glColor4ub(5, 171, 231, 127)
950 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
952 glVertex3f(x1, y1, -0.02)
953 glVertex3f(x2, y1, -0.02)
954 glVertex3f(x2, y2, -0.02)
955 glVertex3f(x1, y2, -0.02)
958 glDisableClientState(GL_VERTEX_ARRAY)
960 glDisable(GL_CULL_FACE)
962 def _generateGCodeVBOs(self, layer):
964 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
965 pointList = numpy.zeros((0,3), numpy.float32)
967 if path.type == 'extrude' and path.pathType == extrudeType:
968 a = numpy.array(path.points, numpy.float32)
969 a = numpy.concatenate((a[:-1], a[1:]), 1)
970 a = a.reshape((len(a) * 2, 3))
971 pointList = numpy.concatenate((pointList, a))
972 ret.append(opengl.GLVBO(pointList))
975 def _generateGCodeVBOs2(self, layer):
976 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
977 filamentArea = math.pi * filamentRadius * filamentRadius
980 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
981 pointList = numpy.zeros((0,3), numpy.float32)
983 if path.type == 'extrude' and path.pathType == extrudeType:
984 a = numpy.array(path.points, numpy.float32)
985 if extrudeType == 'FILL':
988 normal = a[1:] - a[:-1]
989 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
990 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
993 ePerDist = path.extrusion[1:] / lens
994 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
996 normal[:,0] *= lineWidth
997 normal[:,1] *= lineWidth
999 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1000 b = numpy.concatenate((b, a[:-1] + normal), 1)
1001 b = numpy.concatenate((b, a[1:] + normal), 1)
1002 b = numpy.concatenate((b, a[1:] - normal), 1)
1003 b = numpy.concatenate((b, a[:-1] - normal), 1)
1004 b = b.reshape((len(b) * 4, 3))
1006 pointList = numpy.concatenate((pointList, b))
1007 ret.append(opengl.GLVBO(pointList))
1010 def getObjectCenterPos(self):
1011 if self._selectedObj is None:
1012 return [0.0, 0.0, 0.0]
1013 pos = self._selectedObj.getPosition()
1014 size = self._selectedObj.getSize()
1015 return [pos[0], pos[1], size[2]/2]
1017 def getObjectBoundaryCircle(self):
1018 if self._selectedObj is None:
1020 return self._selectedObj.getBoundaryCircle()
1022 def getObjectSize(self):
1023 if self._selectedObj is None:
1024 return [0.0, 0.0, 0.0]
1025 return self._selectedObj.getSize()
1027 def getObjectMatrix(self):
1028 if self._selectedObj is None:
1029 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1030 return self._selectedObj.getMatrix()
1032 class shaderEditor(wx.Dialog):
1033 def __init__(self, parent, callback, v, f):
1034 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1035 self._callback = callback
1036 s = wx.BoxSizer(wx.VERTICAL)
1038 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1039 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1040 s.Add(self._vertex, 1, flag=wx.EXPAND)
1041 s.Add(self._fragment, 1, flag=wx.EXPAND)
1043 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1044 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1046 self.SetPosition(self.GetParent().GetPosition())
1047 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1050 def OnText(self, e):
1051 self._callback(self._vertex.GetValue(), self._fragment.GetValue())