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 print self._slicer.getFilamentAmount()
366 print self._slicer.getPrintTime()
367 print self._slicer.getFilamentCost()
368 self._gcode = gcodeInterpreter.gcode()
369 self._gcode.progressCallback = self._gcodeLoadCallback
370 self._thread = threading.Thread(target=self._loadGCode)
371 self._thread.daemon = True
375 def _loadGCode(self):
376 self._gcode.load(self._slicer.getGCodeFilename())
378 def _gcodeLoadCallback(self, progress):
379 if self._gcode is None:
381 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
382 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
383 self.layerSelect.setValue(self.layerSelect.getMaxValue())
385 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
388 def loadScene(self, fileList):
389 for filename in fileList:
391 objList = meshLoader.loadMeshes(filename)
393 traceback.print_exc()
396 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
398 self._scene.centerAll()
399 self._selectObject(obj)
402 def _deleteObject(self, obj):
403 if obj == self._selectedObj:
404 self._selectObject(None)
405 if obj == self._focusObj:
406 self._focusObj = None
407 self._scene.remove(obj)
408 for m in obj._meshList:
409 if m.vbo is not None and m.vbo.decRef():
410 self.glReleaseList.append(m.vbo)
411 if self._isSimpleMode:
412 self._scene.arrangeAll()
415 def _selectObject(self, obj, zoom = True):
416 if obj != self._selectedObj:
417 self._selectedObj = obj
418 self.updateProfileToControls()
419 self.updateToolButtons()
420 if zoom and obj is not None:
421 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
422 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
423 newZoom = obj.getBoundaryCircle() * 6
424 if newZoom > numpy.max(self._machineSize) * 3:
425 newZoom = numpy.max(self._machineSize) * 3
426 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
428 def updateProfileToControls(self):
429 oldSimpleMode = self._isSimpleMode
430 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
431 if self._isSimpleMode and not oldSimpleMode:
432 self._scene.arrangeAll()
434 self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
435 self._objColors[0] = profile.getPreferenceColour('model_colour')
436 self._objColors[1] = profile.getPreferenceColour('model_colour2')
437 self._objColors[2] = profile.getPreferenceColour('model_colour3')
438 self._objColors[3] = profile.getPreferenceColour('model_colour4')
439 self._scene.setMachineSize(self._machineSize)
440 self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
441 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'))
443 if self._selectedObj is not None:
444 scale = self._selectedObj.getScale()
445 size = self._selectedObj.getSize()
446 self.scaleXctrl.setValue(round(scale[0], 2))
447 self.scaleYctrl.setValue(round(scale[1], 2))
448 self.scaleZctrl.setValue(round(scale[2], 2))
449 self.scaleXmmctrl.setValue(round(size[0], 2))
450 self.scaleYmmctrl.setValue(round(size[1], 2))
451 self.scaleZmmctrl.setValue(round(size[2], 2))
453 def OnKeyChar(self, keyCode):
454 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
455 if self._selectedObj is not None:
456 self._deleteObject(self._selectedObj)
458 if keyCode == wx.WXK_UP:
459 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
461 elif keyCode == wx.WXK_DOWN:
462 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
464 elif keyCode == wx.WXK_PAGEUP:
465 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
467 elif keyCode == wx.WXK_PAGEDOWN:
468 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
471 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
472 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
474 def ShaderUpdate(self, v, f):
475 s = opengl.GLShader(v, f)
477 self._objectLoadShader.release()
478 self._objectLoadShader = s
479 for obj in self._scene.objects():
480 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
483 def OnMouseDown(self,e):
484 self._mouseX = e.GetX()
485 self._mouseY = e.GetY()
486 self._mouseClick3DPos = self._mouse3Dpos
487 self._mouseClickFocus = self._focusObj
489 self._mouseState = 'doubleClick'
491 self._mouseState = 'dragOrClick'
492 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
493 p0 -= self.getObjectCenterPos() - self._viewTarget
494 p1 -= self.getObjectCenterPos() - self._viewTarget
495 if self.tool.OnDragStart(p0, p1):
496 self._mouseState = 'tool'
497 if self._mouseState == 'dragOrClick':
498 if e.GetButton() == 1:
499 if self._focusObj is not None:
500 self._selectObject(self._focusObj, False)
503 def OnMouseUp(self, e):
504 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
506 if self._mouseState == 'dragOrClick':
507 if e.GetButton() == 1:
508 self._selectObject(self._focusObj)
509 if e.GetButton() == 3:
511 if self._focusObj is not None:
512 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, 'Delete'))
513 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, 'Multiply'))
514 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, 'Split'))
515 if self._selectedObj != self._focusObj and self._focusObj is not None and int(profile.getPreference('extruder_amount')) > 1:
516 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, 'Dual extrusion merge'))
517 if len(self._scene.objects()) > 0:
518 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, 'Delete all'))
519 if menu.MenuItemCount > 0:
522 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
523 self._scene.pushFree()
525 elif self._mouseState == 'tool':
526 if self.tempMatrix is not None and self._selectedObj is not None:
527 self._selectedObj.applyMatrix(self.tempMatrix)
528 self._scene.pushFree()
529 self._selectObject(self._selectedObj)
530 self.tempMatrix = None
531 self.tool.OnDragEnd()
533 self._mouseState = None
535 def OnMouseMotion(self,e):
536 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
537 p0 -= self.getObjectCenterPos() - self._viewTarget
538 p1 -= self.getObjectCenterPos() - self._viewTarget
540 if e.Dragging() and self._mouseState is not None:
541 if self._mouseState == 'tool':
542 self.tool.OnDrag(p0, p1)
543 elif not e.LeftIsDown() and e.RightIsDown():
544 self._mouseState = 'drag'
545 if wx.GetKeyState(wx.WXK_SHIFT):
546 a = math.cos(math.radians(self._yaw)) / 3.0
547 b = math.sin(math.radians(self._yaw)) / 3.0
548 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
549 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
550 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
551 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
553 self._yaw += e.GetX() - self._mouseX
554 self._pitch -= e.GetY() - self._mouseY
555 if self._pitch > 170:
559 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
560 self._mouseState = 'drag'
561 self._zoom += e.GetY() - self._mouseY
564 if self._zoom > numpy.max(self._machineSize) * 3:
565 self._zoom = numpy.max(self._machineSize) * 3
566 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
567 self._mouseState = 'dragObject'
568 z = max(0, self._mouseClick3DPos[2])
569 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
570 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
575 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
576 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
577 diff = cursorZ1 - cursorZ0
578 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
579 if not e.Dragging() or self._mouseState != 'tool':
580 self.tool.OnMouseMove(p0, p1)
582 self._mouseX = e.GetX()
583 self._mouseY = e.GetY()
585 def OnMouseWheel(self, e):
586 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
587 delta = max(min(delta,4),-4)
588 self._zoom *= 1.0 - delta / 10.0
591 if self._zoom > numpy.max(self._machineSize) * 3:
592 self._zoom = numpy.max(self._machineSize) * 3
595 def getMouseRay(self, x, y):
596 if self._viewport is None:
597 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
598 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
599 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
600 p0 -= self._viewTarget
601 p1 -= self._viewTarget
604 def _init3DView(self):
605 # set viewing projection
606 size = self.GetSize()
607 glViewport(0, 0, size.GetWidth(), size.GetHeight())
610 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
612 glDisable(GL_RESCALE_NORMAL)
613 glDisable(GL_LIGHTING)
615 glEnable(GL_DEPTH_TEST)
616 glDisable(GL_CULL_FACE)
618 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
620 glClearColor(0.8, 0.8, 0.8, 1.0)
624 glMatrixMode(GL_PROJECTION)
626 aspect = float(size.GetWidth()) / float(size.GetHeight())
627 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
629 glMatrixMode(GL_MODELVIEW)
631 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
634 if machineCom.machineIsConnected():
635 self.printButton._imageID = 6
636 self.printButton._tooltip = 'Print'
637 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
638 self.printButton._imageID = 2
639 self.printButton._tooltip = 'Toolpath to SD'
641 self.printButton._imageID = 3
642 self.printButton._tooltip = 'Save toolpath'
644 if self._animView is not None:
645 self._viewTarget = self._animView.getPosition()
646 if self._animView.isDone():
647 self._animView = None
648 if self._animZoom is not None:
649 self._zoom = self._animZoom.getPosition()
650 if self._animZoom.isDone():
651 self._animZoom = None
652 if self._objectShader is None:
653 self._objectShader = opengl.GLShader("""
654 varying float light_amount;
658 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
659 gl_FrontColor = gl_Color;
661 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
665 varying float light_amount;
669 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
672 self._objectLoadShader = opengl.GLShader("""
673 uniform float intensity;
675 varying float light_amount;
679 vec4 tmp = gl_Vertex;
680 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
681 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
682 gl_Position = gl_ModelViewProjectionMatrix * tmp;
683 gl_FrontColor = gl_Color;
685 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
689 uniform float intensity;
690 varying float light_amount;
694 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
698 glTranslate(0,0,-self._zoom)
699 glRotate(-self._pitch, 1,0,0)
700 glRotate(self._yaw, 0,0,1)
701 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
703 self._viewport = glGetIntegerv(GL_VIEWPORT)
704 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
705 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
707 glClearColor(1,1,1,1)
708 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
710 if self.viewMode != 'gcode':
711 for n in xrange(0, len(self._scene.objects())):
712 obj = self._scene.objects()[n]
713 glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
714 self._renderObject(obj)
716 if self._mouseX > -1:
717 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
718 if n < len(self._scene.objects()):
719 self._focusObj = self._scene.objects()[n]
721 self._focusObj = None
722 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
723 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
724 self._mouse3Dpos -= self._viewTarget
727 glTranslate(0,0,-self._zoom)
728 glRotate(-self._pitch, 1,0,0)
729 glRotate(self._yaw, 0,0,1)
730 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
732 if self.viewMode == 'gcode':
733 if self._gcode is not None:
735 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
737 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
738 for n in xrange(0, drawUpTill):
739 c = 1.0 - float(drawUpTill - n) / 15
741 if len(self._gcodeVBOs) < n + 1:
742 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
743 if time.time() - t > 0.5:
746 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
747 if n == drawUpTill - 1:
748 if len(self._gcodeVBOs[n]) < 6:
749 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
751 self._gcodeVBOs[n][5].render(GL_QUADS)
753 self._gcodeVBOs[n][6].render(GL_QUADS)
754 glColor3f(c/2, c/2, 0.0)
755 self._gcodeVBOs[n][7].render(GL_QUADS)
757 self._gcodeVBOs[n][8].render(GL_QUADS)
758 self._gcodeVBOs[n][9].render(GL_QUADS)
761 self._gcodeVBOs[n][0].render(GL_LINES)
763 self._gcodeVBOs[n][1].render(GL_LINES)
764 glColor3f(c/2, c/2, 0.0)
765 self._gcodeVBOs[n][2].render(GL_LINES)
767 self._gcodeVBOs[n][3].render(GL_LINES)
768 self._gcodeVBOs[n][4].render(GL_LINES)
771 glStencilFunc(GL_ALWAYS, 1, 1)
772 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
774 self._objectShader.bind()
775 for obj in self._scene.objects():
776 if obj._loadAnim is not None:
777 if obj._loadAnim.isDone():
782 if self._focusObj == obj:
784 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
787 if self._selectedObj == obj or self._selectedObj is None:
788 #If we want transparent, then first render a solid black model to remove the printer size lines.
789 if self.viewMode == 'transparent':
790 glColor4f(0, 0, 0, 0)
791 self._renderObject(obj)
793 glBlendFunc(GL_ONE, GL_ONE)
794 glDisable(GL_DEPTH_TEST)
796 if self.viewMode == 'xray':
797 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
798 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
799 glEnable(GL_STENCIL_TEST)
801 if not self._scene.checkPlatform(obj):
802 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
803 self._renderObject(obj)
805 self._renderObject(obj, brightness)
806 glDisable(GL_STENCIL_TEST)
808 glEnable(GL_DEPTH_TEST)
809 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
811 if self.viewMode == 'xray':
814 glEnable(GL_STENCIL_TEST)
815 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
816 glDisable(GL_DEPTH_TEST)
817 for i in xrange(2, 15, 2):
818 glStencilFunc(GL_EQUAL, i, 0xFF)
819 glColor(float(i)/10, float(i)/10, float(i)/5)
821 glVertex3f(-1000,-1000,-10)
822 glVertex3f( 1000,-1000,-10)
823 glVertex3f( 1000, 1000,-10)
824 glVertex3f(-1000, 1000,-10)
826 for i in xrange(1, 15, 2):
827 glStencilFunc(GL_EQUAL, i, 0xFF)
828 glColor(float(i)/10, 0, 0)
830 glVertex3f(-1000,-1000,-10)
831 glVertex3f( 1000,-1000,-10)
832 glVertex3f( 1000, 1000,-10)
833 glVertex3f(-1000, 1000,-10)
836 glDisable(GL_STENCIL_TEST)
837 glEnable(GL_DEPTH_TEST)
839 self._objectShader.unbind()
841 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
843 self._objectLoadShader.bind()
844 glColor4f(0.2, 0.6, 1.0, 1.0)
845 for obj in self._scene.objects():
846 if obj._loadAnim is None:
848 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
849 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
850 self._renderObject(obj)
851 self._objectLoadShader.unbind()
856 if self.viewMode == 'gcode':
859 #Draw the object box-shadow, so you can see where it will collide with other objects.
860 if self._selectedObj is not None and len(self._scene.objects()) > 1:
861 size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
863 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
865 glEnable(GL_CULL_FACE)
866 glColor4f(0,0,0,0.12)
868 glVertex3f(-size[0], size[1], 0.1)
869 glVertex3f(-size[0], -size[1], 0.1)
870 glVertex3f( size[0], -size[1], 0.1)
871 glVertex3f( size[0], size[1], 0.1)
873 glDisable(GL_CULL_FACE)
876 #Draw the outline of the selected object, on top of everything else except the GUI.
877 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
878 glDisable(GL_DEPTH_TEST)
879 glEnable(GL_CULL_FACE)
880 glEnable(GL_STENCIL_TEST)
882 glStencilFunc(GL_EQUAL, 0, 255)
884 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
887 self._renderObject(self._selectedObj)
888 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
890 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
891 glDisable(GL_STENCIL_TEST)
892 glDisable(GL_CULL_FACE)
893 glEnable(GL_DEPTH_TEST)
895 if self._selectedObj is not None:
897 pos = self.getObjectCenterPos()
898 glTranslate(pos[0], pos[1], pos[2])
902 def _renderObject(self, obj, brightness = False):
904 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
906 if self.tempMatrix is not None and obj == self._selectedObj:
907 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
908 glMultMatrixf(tempMatrix)
910 offset = obj.getDrawOffset()
911 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
913 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
914 glMultMatrixf(tempMatrix)
917 for m in obj._meshList:
919 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
921 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
926 def _drawMachine(self):
927 glEnable(GL_CULL_FACE)
930 if profile.getPreference('machine_type') == 'ultimaker':
932 self._objectShader.bind()
933 self._renderObject(self._platformMesh)
934 self._objectShader.unbind()
936 size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
937 v0 = [ size[0] / 2, size[1] / 2, size[2]]
938 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
939 v2 = [-size[0] / 2, size[1] / 2, size[2]]
940 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
941 v4 = [ size[0] / 2, size[1] / 2, 0]
942 v5 = [ size[0] / 2,-size[1] / 2, 0]
943 v6 = [-size[0] / 2, size[1] / 2, 0]
944 v7 = [-size[0] / 2,-size[1] / 2, 0]
946 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
947 glEnableClientState(GL_VERTEX_ARRAY)
948 glVertexPointer(3, GL_FLOAT, 3*4, vList)
950 glColor4ub(5, 171, 231, 64)
951 glDrawArrays(GL_QUADS, 0, 4)
952 glColor4ub(5, 171, 231, 96)
953 glDrawArrays(GL_QUADS, 4, 8)
954 glColor4ub(5, 171, 231, 128)
955 glDrawArrays(GL_QUADS, 12, 8)
957 sx = self._machineSize[0]
958 sy = self._machineSize[1]
959 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
960 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
965 x1 = max(min(x1, sx/2), -sx/2)
966 y1 = max(min(y1, sy/2), -sy/2)
967 x2 = max(min(x2, sx/2), -sx/2)
968 y2 = max(min(y2, sy/2), -sy/2)
969 if (x & 1) == (y & 1):
970 glColor4ub(5, 171, 231, 127)
972 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
974 glVertex3f(x1, y1, -0.02)
975 glVertex3f(x2, y1, -0.02)
976 glVertex3f(x2, y2, -0.02)
977 glVertex3f(x1, y2, -0.02)
980 glDisableClientState(GL_VERTEX_ARRAY)
982 glDisable(GL_CULL_FACE)
984 def _generateGCodeVBOs(self, layer):
986 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
987 pointList = numpy.zeros((0,3), numpy.float32)
989 if path.type == 'extrude' and path.pathType == extrudeType:
990 a = numpy.array(path.points, numpy.float32)
991 a = numpy.concatenate((a[:-1], a[1:]), 1)
992 a = a.reshape((len(a) * 2, 3))
993 pointList = numpy.concatenate((pointList, a))
994 ret.append(opengl.GLVBO(pointList))
997 def _generateGCodeVBOs2(self, layer):
998 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
999 filamentArea = math.pi * filamentRadius * filamentRadius
1002 for extrudeType in ['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1003 pointList = numpy.zeros((0,3), numpy.float32)
1005 if path.type == 'extrude' and path.pathType == extrudeType:
1006 a = numpy.array(path.points, numpy.float32)
1007 if extrudeType == 'FILL':
1010 normal = a[1:] - a[:-1]
1011 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1012 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1015 ePerDist = path.extrusion[1:] / lens
1016 lineWidth = ePerDist * (filamentArea / path.layerThickness / 2)
1018 normal[:,0] *= lineWidth
1019 normal[:,1] *= lineWidth
1021 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1022 b = numpy.concatenate((b, a[:-1] + normal), 1)
1023 b = numpy.concatenate((b, a[1:] + normal), 1)
1024 b = numpy.concatenate((b, a[1:] - normal), 1)
1025 b = numpy.concatenate((b, a[:-1] - normal), 1)
1026 b = b.reshape((len(b) * 4, 3))
1028 pointList = numpy.concatenate((pointList, b))
1029 ret.append(opengl.GLVBO(pointList))
1032 def getObjectCenterPos(self):
1033 if self._selectedObj is None:
1034 return [0.0, 0.0, 0.0]
1035 pos = self._selectedObj.getPosition()
1036 size = self._selectedObj.getSize()
1037 return [pos[0], pos[1], size[2]/2]
1039 def getObjectBoundaryCircle(self):
1040 if self._selectedObj is None:
1042 return self._selectedObj.getBoundaryCircle()
1044 def getObjectSize(self):
1045 if self._selectedObj is None:
1046 return [0.0, 0.0, 0.0]
1047 return self._selectedObj.getSize()
1049 def getObjectMatrix(self):
1050 if self._selectedObj is None:
1051 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1052 return self._selectedObj.getMatrix()
1054 class shaderEditor(wx.Dialog):
1055 def __init__(self, parent, callback, v, f):
1056 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1057 self._callback = callback
1058 s = wx.BoxSizer(wx.VERTICAL)
1060 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1061 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1062 s.Add(self._vertex, 1, flag=wx.EXPAND)
1063 s.Add(self._fragment, 1, flag=wx.EXPAND)
1065 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1066 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1068 self.SetPosition(self.GetParent().GetPosition())
1069 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1072 def OnText(self, e):
1073 self._callback(self._vertex.GetValue(), self._fragment.GetValue())