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))
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...'))
158 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, 'Slice engine log...'))
162 def showSaveGCode(self):
163 defPath = profile.getPreference('lastFile')
164 defPath = defPath[0:defPath.rfind('.')] + '.gcode'
165 dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
166 dlg.SetFilename(defPath)
167 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
168 if dlg.ShowModal() != wx.ID_OK:
171 filename = dlg.GetPath()
175 shutil.copy(self._slicer.getGCodeFilename(), filename)
177 self.notification.message("Failed to save")
179 self.notification.message("Saved as %s" % (filename))
181 def _showSliceLog(self):
182 dlg = wx.TextEntryDialog(self, "The slicing engine reported the following", "Engine log...", '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
186 def OnToolSelect(self, button):
187 if self.rotateToolButton.getSelected():
188 self.tool = previewTools.toolRotate(self)
189 elif self.scaleToolButton.getSelected():
190 self.tool = previewTools.toolScale(self)
191 elif self.mirrorToolButton.getSelected():
192 self.tool = previewTools.toolNone(self)
194 self.tool = previewTools.toolNone(self)
195 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
196 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
197 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
198 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
199 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
200 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
201 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
202 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
204 def updateToolButtons(self):
205 if self._selectedObj is None:
209 self.rotateToolButton.setHidden(hidden)
210 self.scaleToolButton.setHidden(hidden)
211 self.mirrorToolButton.setHidden(hidden)
213 self.rotateToolButton.setSelected(False)
214 self.scaleToolButton.setSelected(False)
215 self.mirrorToolButton.setSelected(False)
218 def OnViewChange(self):
219 if self.viewSelection.getValue() == 3:
220 self.viewMode = 'gcode'
221 if self._gcode is not None:
222 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
223 self.layerSelect.setValue(len(self._gcode.layerList) - 1)
224 self._selectObject(None)
225 elif self.viewSelection.getValue() == 1:
226 self.viewMode = 'transparent'
227 elif self.viewSelection.getValue() == 2:
228 self.viewMode = 'xray'
230 self.viewMode = 'normal'
231 self.layerSelect.setHidden(self.viewMode != 'gcode')
232 self.openFileButton.setHidden(self.viewMode == 'gcode')
235 def OnRotateReset(self, button):
236 if self._selectedObj is None:
238 self._selectedObj.resetRotation()
239 self._scene.pushFree()
240 self._selectObject(self._selectedObj)
242 def OnLayFlat(self, button):
243 if self._selectedObj is None:
245 self._selectedObj.layFlat()
246 self._scene.pushFree()
247 self._selectObject(self._selectedObj)
249 def OnScaleReset(self, button):
250 if self._selectedObj is None:
252 self._selectedObj.resetScale()
254 def OnScaleMax(self, button):
255 if self._selectedObj is None:
257 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2)
258 self._scene.pushFree()
259 self._selectObject(self._selectedObj)
260 self.updateProfileToControls()
263 def OnMirror(self, axis):
264 if self._selectedObj is None:
266 self._selectedObj.mirror(axis)
269 def OnScaleEntry(self, value, axis):
270 if self._selectedObj is None:
276 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
277 self.updateProfileToControls()
278 self._scene.pushFree()
279 self._selectObject(self._selectedObj)
282 def OnScaleEntryMM(self, value, axis):
283 if self._selectedObj is None:
289 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
290 self.updateProfileToControls()
291 self._scene.pushFree()
292 self._selectObject(self._selectedObj)
295 def OnDeleteAll(self, e):
296 while len(self._scene.objects()) > 0:
297 self._deleteObject(self._scene.objects()[0])
298 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
300 def OnMultiply(self, e):
301 if self._focusObj is None:
304 dlg = wx.NumberEntryDialog(self, "How many items do you want?", "Copies", "Multiply", 2, 1, 100)
305 if dlg.ShowModal() != wx.ID_OK:
308 cnt = dlg.GetValue() - 1
314 self._scene.add(newObj)
315 self._scene.centerAll()
316 if not self._scene.checkPlatform(newObj):
321 self.notification.message("Could not create more then %d items" % (n))
322 self._scene.remove(newObj)
323 self._scene.centerAll()
326 def OnSplitObject(self, e):
327 if self._focusObj is None:
329 self._scene.remove(self._focusObj)
330 for obj in self._focusObj.split():
332 self._scene.centerAll()
333 self._selectObject(None)
336 def OnMergeObjects(self, e):
337 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
339 self._scene.merge(self._selectedObj, self._focusObj)
342 def sceneUpdated(self):
343 self._sceneUpdateTimer.Start(1, True)
344 self._slicer.abortSlicer()
345 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
348 def _onRunSlicer(self, e):
349 if self._isSimpleMode:
350 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
351 self._slicer.runSlicer(self._scene)
352 if self._isSimpleMode:
353 profile.resetTempOverride()
355 def _updateSliceProgress(self, progressValue, ready):
356 self.printButton.setDisabled(not ready)
357 self.printButton.setProgressBar(progressValue)
358 if self._gcode is not None:
360 for layerVBOlist in self._gcodeVBOs:
361 for vbo in layerVBOlist:
362 self.glReleaseList.append(vbo)
365 self._gcode = gcodeInterpreter.gcode()
366 self._gcode.progressCallback = self._gcodeLoadCallback
367 self._thread = threading.Thread(target=self._loadGCode)
368 self._thread.daemon = True
372 def _loadGCode(self):
373 self._gcode.load(self._slicer.getGCodeFilename())
375 def _gcodeLoadCallback(self, progress):
376 if self._gcode is None:
378 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
379 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
380 self.layerSelect.setValue(self.layerSelect.getMaxValue())
382 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
385 def loadScene(self, fileList):
386 for filename in fileList:
388 objList = meshLoader.loadMeshes(filename)
390 traceback.print_exc()
393 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
395 self._scene.centerAll()
396 self._selectObject(obj)
399 def _deleteObject(self, obj):
400 if obj == self._selectedObj:
401 self._selectObject(None)
402 if obj == self._focusObj:
403 self._focusObj = None
404 self._scene.remove(obj)
405 for m in obj._meshList:
406 if m.vbo is not None and m.vbo.decRef():
407 self.glReleaseList.append(m.vbo)
408 if self._isSimpleMode:
409 self._scene.arrangeAll()
412 def _selectObject(self, obj, zoom = True):
413 if obj != self._selectedObj:
414 self._selectedObj = obj
415 self.updateProfileToControls()
416 self.updateToolButtons()
417 if zoom and obj is not None:
418 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
419 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
420 newZoom = obj.getBoundaryCircle() * 6
421 if newZoom > numpy.max(self._machineSize) * 3:
422 newZoom = numpy.max(self._machineSize) * 3
423 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
425 def updateProfileToControls(self):
426 oldSimpleMode = self._isSimpleMode
427 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
428 if self._isSimpleMode and not oldSimpleMode:
429 self._scene.arrangeAll()
431 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
432 self._objColors[0] = profile.getPreferenceColour('model_colour')
433 self._objColors[1] = profile.getPreferenceColour('model_colour2')
434 self._objColors[2] = profile.getPreferenceColour('model_colour3')
435 self._objColors[3] = profile.getPreferenceColour('model_colour4')
436 self._scene.setMachineSize(self._machineSize)
437 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
438 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'))
440 if self._selectedObj is not None:
441 scale = self._selectedObj.getScale()
442 size = self._selectedObj.getSize()
443 self.scaleXctrl.setValue(round(scale[0], 2))
444 self.scaleYctrl.setValue(round(scale[1], 2))
445 self.scaleZctrl.setValue(round(scale[2], 2))
446 self.scaleXmmctrl.setValue(round(size[0], 2))
447 self.scaleYmmctrl.setValue(round(size[1], 2))
448 self.scaleZmmctrl.setValue(round(size[2], 2))
450 def OnKeyChar(self, keyCode):
451 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
452 if self._selectedObj is not None:
453 self._deleteObject(self._selectedObj)
455 if keyCode == wx.WXK_UP:
456 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
458 elif keyCode == wx.WXK_DOWN:
459 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
461 elif keyCode == wx.WXK_PAGEUP:
462 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
464 elif keyCode == wx.WXK_PAGEDOWN:
465 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
468 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
469 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
471 def ShaderUpdate(self, v, f):
472 s = opengl.GLShader(v, f)
474 self._objectLoadShader.release()
475 self._objectLoadShader = s
476 for obj in self._scene.objects():
477 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
480 def OnMouseDown(self,e):
481 self._mouseX = e.GetX()
482 self._mouseY = e.GetY()
483 self._mouseClick3DPos = self._mouse3Dpos
484 self._mouseClickFocus = self._focusObj
486 self._mouseState = 'doubleClick'
488 self._mouseState = 'dragOrClick'
489 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
490 p0 -= self.getObjectCenterPos() - self._viewTarget
491 p1 -= self.getObjectCenterPos() - self._viewTarget
492 if self.tool.OnDragStart(p0, p1):
493 self._mouseState = 'tool'
494 if self._mouseState == 'dragOrClick':
495 if e.GetButton() == 1:
496 if self._focusObj is not None:
497 self._selectObject(self._focusObj, False)
500 def OnMouseUp(self, e):
501 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
503 if self._mouseState == 'dragOrClick':
504 if e.GetButton() == 1:
505 self._selectObject(self._focusObj)
506 if e.GetButton() == 3:
508 if self._focusObj is not None:
509 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
510 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
511 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
512 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
513 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
514 if len(self._scene.objects()) > 0:
515 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
516 if menu.MenuItemCount > 0:
519 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
520 self._scene.pushFree()
522 elif self._mouseState == 'tool':
523 if self.tempMatrix is not None and self._selectedObj is not None:
524 self._selectedObj.applyMatrix(self.tempMatrix)
525 self._scene.pushFree()
526 self._selectObject(self._selectedObj)
527 self.tempMatrix = None
528 self.tool.OnDragEnd()
530 self._mouseState = None
532 def OnMouseMotion(self,e):
533 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
534 p0 -= self.getObjectCenterPos() - self._viewTarget
535 p1 -= self.getObjectCenterPos() - self._viewTarget
537 if e.Dragging() and self._mouseState is not None:
538 if self._mouseState == 'tool':
539 self.tool.OnDrag(p0, p1)
540 elif not e.LeftIsDown() and e.RightIsDown():
541 self._mouseState = 'drag'
542 self._yaw += e.GetX() - self._mouseX
543 self._pitch -= e.GetY() - self._mouseY
544 if self._pitch > 170:
548 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
549 self._mouseState = 'drag'
550 self._zoom += e.GetY() - self._mouseY
553 if self._zoom > numpy.max(self._machineSize) * 3:
554 self._zoom = numpy.max(self._machineSize) * 3
555 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
556 self._mouseState = 'dragObject'
557 z = max(0, self._mouseClick3DPos[2])
558 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
559 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
564 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
565 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
566 diff = cursorZ1 - cursorZ0
567 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
568 if not e.Dragging() or self._mouseState != 'tool':
569 self.tool.OnMouseMove(p0, p1)
571 self._mouseX = e.GetX()
572 self._mouseY = e.GetY()
574 def OnMouseWheel(self, e):
575 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
576 delta = max(min(delta,4),-4)
577 self._zoom *= 1.0 - delta / 10.0
580 if self._zoom > numpy.max(self._machineSize) * 3:
581 self._zoom = numpy.max(self._machineSize) * 3
584 def getMouseRay(self, x, y):
585 if self._viewport is None:
586 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
587 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
588 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
589 p0 -= self._viewTarget
590 p1 -= self._viewTarget
593 def _init3DView(self):
594 # set viewing projection
595 size = self.GetSize()
596 glViewport(0, 0, size.GetWidth(), size.GetHeight())
599 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
601 glDisable(GL_RESCALE_NORMAL)
602 glDisable(GL_LIGHTING)
604 glEnable(GL_DEPTH_TEST)
605 glDisable(GL_CULL_FACE)
607 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
609 glClearColor(0.8, 0.8, 0.8, 1.0)
613 glMatrixMode(GL_PROJECTION)
615 aspect = float(size.GetWidth()) / float(size.GetHeight())
616 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
618 glMatrixMode(GL_MODELVIEW)
620 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
623 if machineCom.machineIsConnected():
624 self.printButton._imageID = 6
625 self.printButton._tooltip = 'Print'
626 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
627 self.printButton._imageID = 2
628 self.printButton._tooltip = 'Toolpath to SD'
630 self.printButton._imageID = 3
631 self.printButton._tooltip = 'Save toolpath'
633 if self._animView is not None:
634 self._viewTarget = self._animView.getPosition()
635 if self._animView.isDone():
636 self._animView = None
637 if self._animZoom is not None:
638 self._zoom = self._animZoom.getPosition()
639 if self._animZoom.isDone():
640 self._animZoom = None
641 if self._objectShader is None:
642 self._objectShader = opengl.GLShader("""
643 varying float light_amount;
647 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
648 gl_FrontColor = gl_Color;
650 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
654 varying float light_amount;
658 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
661 self._objectLoadShader = opengl.GLShader("""
662 uniform float intensity;
664 varying float light_amount;
668 vec4 tmp = gl_Vertex;
669 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
670 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
671 gl_Position = gl_ModelViewProjectionMatrix * tmp;
672 gl_FrontColor = gl_Color;
674 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
678 uniform float intensity;
679 varying float light_amount;
683 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
687 glTranslate(0,0,-self._zoom)
688 glRotate(-self._pitch, 1,0,0)
689 glRotate(self._yaw, 0,0,1)
690 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
692 self._viewport = glGetIntegerv(GL_VIEWPORT)
693 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
694 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
696 glClearColor(1,1,1,1)
697 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
699 if self.viewMode != 'gcode':
700 for n in xrange(0, len(self._scene.objects())):
701 obj = self._scene.objects()[n]
702 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
703 self._renderObject(obj)
705 if self._mouseX > -1:
706 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
707 if n < len(self._scene.objects()):
708 self._focusObj = self._scene.objects()[n]
710 self._focusObj = None
711 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
712 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
713 self._mouse3Dpos -= self._viewTarget
716 glTranslate(0,0,-self._zoom)
717 glRotate(-self._pitch, 1,0,0)
718 glRotate(self._yaw, 0,0,1)
719 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
721 if self.viewMode == 'gcode':
722 if self._gcode is not None:
724 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
726 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
727 for n in xrange(0, drawUpTill):
728 c = 1.0 - float(drawUpTill - n) / 15
730 if len(self._gcodeVBOs) < n + 1:
731 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
732 if time.time() - t > 0.5:
735 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
736 if n == drawUpTill - 1:
737 if len(self._gcodeVBOs[n]) < 6:
738 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
740 self._gcodeVBOs[n][5].render(GL_QUADS)
742 self._gcodeVBOs[n][6].render(GL_QUADS)
743 glColor3f(c/2, c/2, 0.0)
744 self._gcodeVBOs[n][7].render(GL_QUADS)
746 self._gcodeVBOs[n][8].render(GL_QUADS)
747 self._gcodeVBOs[n][9].render(GL_QUADS)
750 self._gcodeVBOs[n][0].render(GL_LINES)
752 self._gcodeVBOs[n][1].render(GL_LINES)
753 glColor3f(c/2, c/2, 0.0)
754 self._gcodeVBOs[n][2].render(GL_LINES)
756 self._gcodeVBOs[n][3].render(GL_LINES)
757 self._gcodeVBOs[n][4].render(GL_LINES)
760 glStencilFunc(GL_ALWAYS, 1, 1)
761 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
763 self._objectShader.bind()
764 for obj in self._scene.objects():
765 if obj._loadAnim is not None:
766 if obj._loadAnim.isDone():
771 if self._focusObj == obj:
773 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
776 if self._selectedObj == obj or self._selectedObj is None:
777 #If we want transparent, then first render a solid black model to remove the printer size lines.
778 if self.viewMode == 'transparent':
779 glColor4f(0, 0, 0, 0)
780 self._renderObject(obj)
782 glBlendFunc(GL_ONE, GL_ONE)
783 glDisable(GL_DEPTH_TEST)
785 if self.viewMode == 'xray':
786 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
787 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
788 glEnable(GL_STENCIL_TEST)
790 if not self._scene.checkPlatform(obj):
791 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
792 self._renderObject(obj)
794 self._renderObject(obj, brightness)
795 glDisable(GL_STENCIL_TEST)
797 glEnable(GL_DEPTH_TEST)
798 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
800 if self.viewMode == 'xray':
803 glEnable(GL_STENCIL_TEST)
804 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
805 glDisable(GL_DEPTH_TEST)
806 for i in xrange(2, 15, 2):
807 glStencilFunc(GL_EQUAL, i, 0xFF)
808 glColor(float(i)/10, float(i)/10, float(i)/5)
810 glVertex3f(-1000,-1000,-10)
811 glVertex3f( 1000,-1000,-10)
812 glVertex3f( 1000, 1000,-10)
813 glVertex3f(-1000, 1000,-10)
815 for i in xrange(1, 15, 2):
816 glStencilFunc(GL_EQUAL, i, 0xFF)
817 glColor(float(i)/10, 0, 0)
819 glVertex3f(-1000,-1000,-10)
820 glVertex3f( 1000,-1000,-10)
821 glVertex3f( 1000, 1000,-10)
822 glVertex3f(-1000, 1000,-10)
825 glDisable(GL_STENCIL_TEST)
826 glEnable(GL_DEPTH_TEST)
828 self._objectShader.unbind()
830 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
832 self._objectLoadShader.bind()
833 glColor4f(0.2, 0.6, 1.0, 1.0)
834 for obj in self._scene.objects():
835 if obj._loadAnim is None:
837 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
838 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
839 self._renderObject(obj)
840 self._objectLoadShader.unbind()
845 if self.viewMode == 'gcode':
848 #Draw the object box-shadow, so you can see where it will collide with other objects.
849 if self._selectedObj is not None and len(self._scene.objects()) > 1:
850 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
852 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
854 glEnable(GL_CULL_FACE)
855 glColor4f(0,0,0,0.12)
857 glVertex3f(-size[0], size[1], 0.1)
858 glVertex3f(-size[0], -size[1], 0.1)
859 glVertex3f( size[0], -size[1], 0.1)
860 glVertex3f( size[0], size[1], 0.1)
862 glDisable(GL_CULL_FACE)
865 #Draw the outline of the selected object, on top of everything else except the GUI.
866 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
867 glDisable(GL_DEPTH_TEST)
868 glEnable(GL_CULL_FACE)
869 glEnable(GL_STENCIL_TEST)
871 glStencilFunc(GL_EQUAL, 0, 255)
873 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
876 self._renderObject(self._selectedObj)
877 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
879 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
880 glDisable(GL_STENCIL_TEST)
881 glDisable(GL_CULL_FACE)
882 glEnable(GL_DEPTH_TEST)
884 if self._selectedObj is not None:
886 pos = self.getObjectCenterPos()
887 glTranslate(pos[0], pos[1], pos[2])
891 def _renderObject(self, obj, brightness = False):
893 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
895 if self.tempMatrix is not None and obj == self._selectedObj:
896 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
897 glMultMatrixf(tempMatrix)
899 offset = obj.getDrawOffset()
900 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
902 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
903 glMultMatrixf(tempMatrix)
906 for m in obj._meshList:
908 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
910 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
915 def _drawMachine(self):
916 glEnable(GL_CULL_FACE)
919 if profile.getPreference('machine_type') == 'ultimaker':
921 self._objectShader.bind()
922 self._renderObject(self._platformMesh)
923 self._objectShader.unbind()
925 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
926 v0 = [ size[0] / 2, size[1] / 2, size[2]]
927 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
928 v2 = [-size[0] / 2, size[1] / 2, size[2]]
929 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
930 v4 = [ size[0] / 2, size[1] / 2, 0]
931 v5 = [ size[0] / 2,-size[1] / 2, 0]
932 v6 = [-size[0] / 2, size[1] / 2, 0]
933 v7 = [-size[0] / 2,-size[1] / 2, 0]
935 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
936 glEnableClientState(GL_VERTEX_ARRAY)
937 glVertexPointer(3, GL_FLOAT, 3*4, vList)
939 glColor4ub(5, 171, 231, 64)
940 glDrawArrays(GL_QUADS, 0, 4)
941 glColor4ub(5, 171, 231, 96)
942 glDrawArrays(GL_QUADS, 4, 8)
943 glColor4ub(5, 171, 231, 128)
944 glDrawArrays(GL_QUADS, 12, 8)
946 sx = self._machineSize[0]
947 sy = self._machineSize[1]
948 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
949 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
954 x1 = max(min(x1, sx/2), -sx/2)
955 y1 = max(min(y1, sy/2), -sy/2)
956 x2 = max(min(x2, sx/2), -sx/2)
957 y2 = max(min(y2, sy/2), -sy/2)
958 if (x & 1) == (y & 1):
959 glColor4ub(5, 171, 231, 127)
961 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
963 glVertex3f(x1, y1, -0.02)
964 glVertex3f(x2, y1, -0.02)
965 glVertex3f(x2, y2, -0.02)
966 glVertex3f(x1, y2, -0.02)
969 glDisableClientState(GL_VERTEX_ARRAY)
971 glDisable(GL_CULL_FACE)
973 def _generateGCodeVBOs(self, layer):
975 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
976 pointList = numpy.zeros((0,3), numpy.float32)
978 if path.type == 'extrude' and path.pathType == extrudeType:
979 a = numpy.array(path.points, numpy.float32)
980 a = numpy.concatenate((a[:-1], a[1:]), 1)
981 a = a.reshape((len(a) * 2, 3))
982 pointList = numpy.concatenate((pointList, a))
983 ret.append(opengl.GLVBO(pointList))
986 def _generateGCodeVBOs2(self, layer):
987 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
988 filamentArea = math.pi * filamentRadius * filamentRadius
991 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
992 pointList = numpy.zeros((0,3), numpy.float32)
994 if path.type == 'extrude' and path.pathType == extrudeType:
995 a = numpy.array(path.points, numpy.float32)
996 if extrudeType == 'FILL':
999 normal = a[1:] - a[:-1]
1000 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1001 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1004 ePerDist = path.extrusion[1:] / lens
1005 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1007 normal[:,0] *= lineWidth
1008 normal[:,1] *= lineWidth
1010 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1011 b = numpy.concatenate((b, a[:-1] + normal), 1)
1012 b = numpy.concatenate((b, a[1:] + normal), 1)
1013 b = numpy.concatenate((b, a[1:] - normal), 1)
1014 b = numpy.concatenate((b, a[:-1] - normal), 1)
1015 b = b.reshape((len(b) * 4, 3))
1017 pointList = numpy.concatenate((pointList, b))
1018 ret.append(opengl.GLVBO(pointList))
1021 def getObjectCenterPos(self):
1022 if self._selectedObj is None:
1023 return [0.0, 0.0, 0.0]
1024 pos = self._selectedObj.getPosition()
1025 size = self._selectedObj.getSize()
1026 return [pos[0], pos[1], size[2]/2]
1028 def getObjectBoundaryCircle(self):
1029 if self._selectedObj is None:
1031 return self._selectedObj.getBoundaryCircle()
1033 def getObjectSize(self):
1034 if self._selectedObj is None:
1035 return [0.0, 0.0, 0.0]
1036 return self._selectedObj.getSize()
1038 def getObjectMatrix(self):
1039 if self._selectedObj is None:
1040 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1041 return self._selectedObj.getMatrix()
1043 class shaderEditor(wx.Dialog):
1044 def __init__(self, parent, callback, v, f):
1045 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1046 self._callback = callback
1047 s = wx.BoxSizer(wx.VERTICAL)
1049 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1050 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1051 s.Add(self._vertex, 1, flag=wx.EXPAND)
1052 s.Add(self._fragment, 1, flag=wx.EXPAND)
1054 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1055 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1057 self.SetPosition(self.GetParent().GetPosition())
1058 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1061 def OnText(self, e):
1062 self._callback(self._vertex.GetValue(), self._fragment.GetValue())