1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
18 from Cura.gui import printWindow
19 from Cura.gui import printWindow2
20 from Cura.util import profile
21 from Cura.util import meshLoader
22 from Cura.util import objectScene
23 from Cura.util import resources
24 from Cura.util import sliceEngine
25 from Cura.util import machineCom
26 from Cura.util import removableStorage
27 from Cura.util import gcodeInterpreter
28 from Cura.util import explorer
29 from Cura.util.printerConnection import printerConnectionManager
30 from Cura.gui.util import previewTools
31 from Cura.gui.util import opengl
32 from Cura.gui.util import openglGui
33 from Cura.gui.tools import youmagineGui
34 from Cura.gui.tools import imageToMesh
36 class SceneView(openglGui.glGuiPanel):
37 def __init__(self, parent):
38 super(SceneView, self).__init__(parent)
43 self._scene = objectScene.Scene()
46 self._gcodeFilename = None
47 self._gcodeLoadThread = None
48 self._objectShader = None
49 self._objectLoadShader = None
51 self._selectedObj = None
52 self._objColors = [None,None,None,None]
55 self._mouseState = None
56 self._viewTarget = numpy.array([0,0,0], numpy.float32)
59 self._platformMesh = {}
60 self._platformTexture = None
61 self._isSimpleMode = True
62 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
63 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
66 self._modelMatrix = None
67 self._projMatrix = None
68 self.tempMatrix = None
70 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
71 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
72 self.printButton.setDisabled(True)
75 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
76 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
77 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
79 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
80 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
82 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
83 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
85 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
86 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
87 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
89 self.rotateToolButton.setExpandArrow(True)
90 self.scaleToolButton.setExpandArrow(True)
91 self.mirrorToolButton.setExpandArrow(True)
93 self.scaleForm = openglGui.glFrame(self, (2, -2))
94 openglGui.glGuiLayoutGrid(self.scaleForm)
95 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
96 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
97 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
98 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
99 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
100 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
101 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
102 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
103 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
104 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
105 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
106 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
107 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
108 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
110 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
111 self.layerSelect = openglGui.glSlider(self, 10000, 0, 1, (-1,-2), lambda : self.QueueRefresh())
113 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
114 self.youMagineButton.setDisabled(True)
116 self.notification = openglGui.glNotification(self, (0, 0))
118 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
119 self._sceneUpdateTimer = wx.Timer(self)
120 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
121 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
122 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
126 self.updateToolButtons()
127 self.updateProfileToControls()
129 def loadGCodeFile(self, filename):
130 self.OnDeleteAll(None)
131 if self._gcode is not None:
133 for layerVBOlist in self._gcodeVBOs:
134 for vbo in layerVBOlist:
135 self.glReleaseList.append(vbo)
137 self._gcode = gcodeInterpreter.gcode()
138 self._gcodeFilename = filename
139 self.printButton.setBottomText('')
140 self.viewSelection.setValue(4)
141 self.printButton.setDisabled(False)
142 self.youMagineButton.setDisabled(True)
145 def loadSceneFiles(self, filenames):
146 self.youMagineButton.setDisabled(False)
147 #if self.viewSelection.getValue() == 4:
148 # self.viewSelection.setValue(0)
149 # self.OnViewChange()
150 self.loadScene(filenames)
152 def loadFiles(self, filenames):
153 mainWindow = self.GetParent().GetParent().GetParent()
154 # only one GCODE file can be active
155 # so if single gcode file, process this
156 # otherwise ignore all gcode files
158 if len(filenames) == 1:
159 filename = filenames[0]
160 ext = os.path.splitext(filename)[1].lower()
161 if ext == '.g' or ext == '.gcode':
162 gcodeFilename = filename
163 mainWindow.addToModelMRU(filename)
164 if gcodeFilename is not None:
165 self.loadGCodeFile(gcodeFilename)
167 # process directories and special file types
168 # and keep scene files for later processing
170 ignored_types = dict()
171 # use file list as queue
172 # pop first entry for processing and append new files at end
174 filename = filenames.pop(0)
175 if os.path.isdir(filename):
176 # directory: queue all included files and directories
177 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
179 ext = os.path.splitext(filename)[1].lower()
181 profile.loadProfile(filename)
182 mainWindow.addToProfileMRU(filename)
183 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
184 scene_filenames.append(filename)
185 mainWindow.addToModelMRU(filename)
187 ignored_types[ext] = 1
189 ignored_types = ignored_types.keys()
191 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
192 mainWindow.updateProfileToAllControls()
193 # now process all the scene files
195 self.loadSceneFiles(scene_filenames)
196 self._selectObject(None)
198 newZoom = numpy.max(self._machineSize)
199 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
200 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
202 def showLoadModel(self, button = 1):
204 dlg=wx.FileDialog(self, _("Open 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
205 dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
206 if dlg.ShowModal() != wx.ID_OK:
209 filenames = dlg.GetPaths()
211 if len(filenames) < 1:
213 profile.putPreference('lastFile', filenames[0])
214 self.loadFiles(filenames)
216 def showSaveModel(self):
217 if len(self._scene.objects()) < 1:
219 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
220 dlg.SetWildcard(meshLoader.saveWildcardFilter())
221 if dlg.ShowModal() != wx.ID_OK:
224 filename = dlg.GetPath()
226 meshLoader.saveMeshes(filename, self._scene.objects())
228 def OnPrintButton(self, button):
230 connectionGroup = self._printerConnectionManager.getAvailableGroup()
231 if machineCom.machineIsConnected():
232 self.showPrintWindow()
233 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
234 drives = removableStorage.getPossibleSDcardDrives()
236 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))
237 if dlg.ShowModal() != wx.ID_OK:
240 drive = drives[dlg.GetSelection()]
244 filename = self._scene._objectList[0].getName() + '.gcode'
245 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
246 elif connectionGroup is not None:
247 connections = connectionGroup.getAvailableConnections()
248 if len(connections) < 2:
249 connection = connections[0]
251 dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
252 if dlg.ShowModal() != wx.ID_OK:
255 connection = connections[dlg.GetSelection()]
257 self._openPrintWindowForConnection(connection)
262 self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
263 connections = self._printerConnectionManager.getAvailableConnections()
264 menu.connectionMap = {}
265 for connection in connections:
266 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
267 menu.connectionMap[i.GetId()] = connection
268 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
269 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
270 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
274 def _openPrintWindowForConnection(self, connection):
275 print '_openPrintWindowForConnection', connection.getName()
276 if connection.window is None or not connection.window:
277 connection.window = printWindow2.printWindow(connection)
278 connection.window.Show()
279 connection.window.Raise()
280 if not connection.loadFile(self._gcodeFilename):
281 if connection.isPrinting():
282 self.notification.message("Cannot start print, because other print still running.")
284 self.notification.message("Failed to start print...")
286 def showPrintWindow(self):
287 if self._gcodeFilename is None:
289 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
290 wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
292 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
293 if self._gcodeFilename == self._slicer.getGCodeFilename():
294 self._slicer.submitSliceInfoOnline()
295 self.viewSelection.setValue(4)
297 def showSaveGCode(self):
298 if len(self._scene._objectList) < 1:
300 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
301 filename = self._scene._objectList[0].getName() + '.gcode'
302 dlg.SetFilename(filename)
303 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
304 if dlg.ShowModal() != wx.ID_OK:
307 filename = dlg.GetPath()
310 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
312 def _copyFile(self, fileA, fileB, allowEject = False):
314 size = float(os.stat(fileA).st_size)
315 with open(fileA, 'rb') as fsrc:
316 with open(fileB, 'wb') as fdst:
318 buf = fsrc.read(16*1024)
322 self.printButton.setProgressBar(float(fsrc.tell()) / size)
327 self.notification.message("Failed to save")
330 self.notification.message("Saved as %s" % (fileB), lambda : self._doEjectSD(allowEject), 31, 'Eject')
331 elif explorer.hasExplorer():
332 self.notification.message("Saved as %s" % (fileB), lambda : explorer.openExplorer(fileB), 4, 'Open folder')
334 self.notification.message("Saved as %s" % (fileB))
335 self.printButton.setProgressBar(None)
336 if fileA == self._slicer.getGCodeFilename():
337 self._slicer.submitSliceInfoOnline()
339 def _doEjectSD(self, drive):
340 if removableStorage.ejectDrive(drive):
341 self.notification.message('You can now eject the card.')
343 self.notification.message('Safe remove failed...')
345 def _showSliceLog(self):
346 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
350 def OnToolSelect(self, button):
351 if self.rotateToolButton.getSelected():
352 self.tool = previewTools.toolRotate(self)
353 elif self.scaleToolButton.getSelected():
354 self.tool = previewTools.toolScale(self)
355 elif self.mirrorToolButton.getSelected():
356 self.tool = previewTools.toolNone(self)
358 self.tool = previewTools.toolNone(self)
359 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
360 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
361 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
362 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
363 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
364 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
365 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
366 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
368 def updateToolButtons(self):
369 if self._selectedObj is None:
373 self.rotateToolButton.setHidden(hidden)
374 self.scaleToolButton.setHidden(hidden)
375 self.mirrorToolButton.setHidden(hidden)
377 self.rotateToolButton.setSelected(False)
378 self.scaleToolButton.setSelected(False)
379 self.mirrorToolButton.setSelected(False)
382 def OnViewChange(self):
383 if self.viewSelection.getValue() == 4:
384 self.viewMode = 'gcode'
385 if self._gcode is not None and self._gcode.layerList is not None:
386 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
387 self._selectObject(None)
388 elif self.viewSelection.getValue() == 1:
389 self.viewMode = 'overhang'
390 elif self.viewSelection.getValue() == 2:
391 self.viewMode = 'transparent'
392 elif self.viewSelection.getValue() == 3:
393 self.viewMode = 'xray'
395 self.viewMode = 'normal'
396 self.layerSelect.setHidden(self.viewMode != 'gcode')
399 def OnRotateReset(self, button):
400 if self._selectedObj is None:
402 self._selectedObj.resetRotation()
403 self._scene.pushFree(self._selectedObj)
404 self._selectObject(self._selectedObj)
407 def OnLayFlat(self, button):
408 if self._selectedObj is None:
410 self._selectedObj.layFlat()
411 self._scene.pushFree(self._selectedObj)
412 self._selectObject(self._selectedObj)
415 def OnScaleReset(self, button):
416 if self._selectedObj is None:
418 self._selectedObj.resetScale()
419 self._selectObject(self._selectedObj)
420 self.updateProfileToControls()
423 def OnScaleMax(self, button):
424 if self._selectedObj is None:
426 machine = profile.getMachineSetting('machine_type')
427 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
428 self._scene.pushFree(self._selectedObj)
430 if machine == "ultimaker2":
431 #This is bad and Jaime should feel bad!
432 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
433 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
434 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
435 self._scene.pushFree(self._selectedObj)
437 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
438 self._scene.pushFree(self._selectedObj)
439 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
440 self._scene.pushFree(self._selectedObj)
441 self._selectObject(self._selectedObj)
442 self.updateProfileToControls()
445 def OnMirror(self, axis):
446 if self._selectedObj is None:
448 self._selectedObj.mirror(axis)
451 def OnScaleEntry(self, value, axis):
452 if self._selectedObj is None:
458 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
459 self.updateProfileToControls()
460 self._scene.pushFree(self._selectedObj)
461 self._selectObject(self._selectedObj)
464 def OnScaleEntryMM(self, value, axis):
465 if self._selectedObj is None:
471 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
472 self.updateProfileToControls()
473 self._scene.pushFree(self._selectedObj)
474 self._selectObject(self._selectedObj)
477 def OnDeleteAll(self, e):
478 while len(self._scene.objects()) > 0:
479 self._deleteObject(self._scene.objects()[0])
480 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
482 def OnMultiply(self, e):
483 if self._focusObj is None:
486 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
487 if dlg.ShowModal() != wx.ID_OK:
496 self._scene.add(newObj)
497 self._scene.centerAll()
498 if not self._scene.checkPlatform(newObj):
503 self.notification.message("Could not create more then %d items" % (n - 1))
504 self._scene.remove(newObj)
505 self._scene.centerAll()
508 def OnSplitObject(self, e):
509 if self._focusObj is None:
511 self._scene.remove(self._focusObj)
512 for obj in self._focusObj.split(self._splitCallback):
513 if numpy.max(obj.getSize()) > 2.0:
515 self._scene.centerAll()
516 self._selectObject(None)
519 def OnCenter(self, e):
520 if self._focusObj is None:
522 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
523 self._scene.pushFree(self._selectedObj)
524 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
525 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
528 def _splitCallback(self, progress):
531 def OnMergeObjects(self, e):
532 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
533 if len(self._scene.objects()) == 2:
534 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
537 self._scene.merge(self._selectedObj, self._focusObj)
540 def sceneUpdated(self):
541 self._sceneUpdateTimer.Start(500, True)
542 self._slicer.abortSlicer()
543 self._scene.updateSizeOffsets()
546 def _onRunSlicer(self, e):
547 if self._isSimpleMode:
548 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
549 self._slicer.runSlicer(self._scene)
550 if self._isSimpleMode:
551 profile.resetTempOverride()
553 def _updateSliceProgress(self, progressValue, ready):
555 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
557 self.printButton.setDisabled(not ready)
558 if progressValue >= 0.0:
559 self.printButton.setProgressBar(progressValue)
561 self.printButton.setProgressBar(None)
562 if self._gcode is not None:
564 for layerVBOlist in self._gcodeVBOs:
565 for vbo in layerVBOlist:
566 self.glReleaseList.append(vbo)
569 self.printButton.setProgressBar(None)
570 text = '%s' % (self._slicer.getPrintTime())
571 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
572 amount = self._slicer.getFilamentAmount(e)
575 text += '\n%s' % (amount)
576 cost = self._slicer.getFilamentCost(e)
578 text += '\n%s' % (cost)
579 self.printButton.setBottomText(text)
580 self._gcode = gcodeInterpreter.gcode()
581 self._gcodeFilename = self._slicer.getGCodeFilename()
583 self.printButton.setBottomText('')
586 def _loadGCode(self):
587 self._gcode.progressCallback = self._gcodeLoadCallback
588 self._gcode.load(self._gcodeFilename)
590 def _gcodeLoadCallback(self, progress):
591 if not self or self._gcode is None:
593 if len(self._gcode.layerList) % 15 == 0:
595 if self._gcode is None:
597 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
598 if self.viewMode == 'gcode':
602 def loadScene(self, fileList):
603 for filename in fileList:
605 ext = os.path.splitext(filename)[1].lower()
606 if ext in imageToMesh.supportedExtensions():
607 imageToMesh.convertImageDialog(self, filename).Show()
610 objList = meshLoader.loadMeshes(filename)
612 traceback.print_exc()
615 if self._objectLoadShader is not None:
616 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
620 if not self._scene.checkPlatform(obj):
621 self._scene.centerAll()
622 self._selectObject(obj)
623 if obj.getScale()[0] < 1.0:
624 self.notification.message("Warning: Object scaled down.")
627 def _deleteObject(self, obj):
628 if obj == self._selectedObj:
629 self._selectObject(None)
630 if obj == self._focusObj:
631 self._focusObj = None
632 self._scene.remove(obj)
633 for m in obj._meshList:
634 if m.vbo is not None and m.vbo.decRef():
635 self.glReleaseList.append(m.vbo)
640 def _selectObject(self, obj, zoom = True):
641 if obj != self._selectedObj:
642 self._selectedObj = obj
643 self.updateModelSettingsToControls()
644 self.updateToolButtons()
645 if zoom and obj is not None:
646 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
647 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
648 newZoom = obj.getBoundaryCircle() * 6
649 if newZoom > numpy.max(self._machineSize) * 3:
650 newZoom = numpy.max(self._machineSize) * 3
651 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
653 def updateProfileToControls(self):
654 oldSimpleMode = self._isSimpleMode
655 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
656 if self._isSimpleMode != oldSimpleMode:
657 self._scene.arrangeAll()
659 self._scene.updateSizeOffsets(True)
660 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
661 self._objColors[0] = profile.getPreferenceColour('model_colour')
662 self._objColors[1] = profile.getPreferenceColour('model_colour2')
663 self._objColors[2] = profile.getPreferenceColour('model_colour3')
664 self._objColors[3] = profile.getPreferenceColour('model_colour4')
665 self._scene.updateMachineDimensions()
666 self.updateModelSettingsToControls()
668 def updateModelSettingsToControls(self):
669 if self._selectedObj is not None:
670 scale = self._selectedObj.getScale()
671 size = self._selectedObj.getSize()
672 self.scaleXctrl.setValue(round(scale[0], 2))
673 self.scaleYctrl.setValue(round(scale[1], 2))
674 self.scaleZctrl.setValue(round(scale[2], 2))
675 self.scaleXmmctrl.setValue(round(size[0], 2))
676 self.scaleYmmctrl.setValue(round(size[1], 2))
677 self.scaleZmmctrl.setValue(round(size[2], 2))
679 def OnKeyChar(self, keyCode):
680 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
681 if self._selectedObj is not None:
682 self._deleteObject(self._selectedObj)
684 if self.viewMode == 'gcode':
685 if keyCode == wx.WXK_UP:
686 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
688 elif keyCode == wx.WXK_DOWN:
689 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
691 elif keyCode == wx.WXK_PAGEUP:
692 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
694 elif keyCode == wx.WXK_PAGEDOWN:
695 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
698 if keyCode == wx.WXK_UP:
699 if wx.GetKeyState(wx.WXK_SHIFT):
706 elif keyCode == wx.WXK_DOWN:
707 if wx.GetKeyState(wx.WXK_SHIFT):
709 if self._zoom > numpy.max(self._machineSize) * 3:
710 self._zoom = numpy.max(self._machineSize) * 3
714 elif keyCode == wx.WXK_LEFT:
717 elif keyCode == wx.WXK_RIGHT:
720 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
725 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
727 if self._zoom > numpy.max(self._machineSize) * 3:
728 self._zoom = numpy.max(self._machineSize) * 3
730 elif keyCode == wx.WXK_HOME:
734 elif keyCode == wx.WXK_PAGEUP:
738 elif keyCode == wx.WXK_PAGEDOWN:
742 elif keyCode == wx.WXK_END:
747 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
748 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
749 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
750 from collections import defaultdict
751 from gc import get_objects
752 self._beforeLeakTest = defaultdict(int)
753 for i in get_objects():
754 self._beforeLeakTest[type(i)] += 1
755 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
756 from collections import defaultdict
757 from gc import get_objects
758 self._afterLeakTest = defaultdict(int)
759 for i in get_objects():
760 self._afterLeakTest[type(i)] += 1
761 for k in self._afterLeakTest:
762 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
763 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
765 def ShaderUpdate(self, v, f):
766 s = opengl.GLShader(v, f)
768 self._objectLoadShader.release()
769 self._objectLoadShader = s
770 for obj in self._scene.objects():
771 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
774 def OnMouseDown(self,e):
775 self._mouseX = e.GetX()
776 self._mouseY = e.GetY()
777 self._mouseClick3DPos = self._mouse3Dpos
778 self._mouseClickFocus = self._focusObj
780 self._mouseState = 'doubleClick'
782 self._mouseState = 'dragOrClick'
783 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
784 p0 -= self.getObjectCenterPos() - self._viewTarget
785 p1 -= self.getObjectCenterPos() - self._viewTarget
786 if self.tool.OnDragStart(p0, p1):
787 self._mouseState = 'tool'
788 if self._mouseState == 'dragOrClick':
789 if e.GetButton() == 1:
790 if self._focusObj is not None:
791 self._selectObject(self._focusObj, False)
794 def OnMouseUp(self, e):
795 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
797 if self._mouseState == 'dragOrClick':
798 if e.GetButton() == 1:
799 self._selectObject(self._focusObj)
800 if e.GetButton() == 3:
802 if self._focusObj is not None:
803 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
804 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
805 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
806 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
807 if ((self._selectedObj != self._focusObj and self._focusObj is not None and self._selectedObj is not None) or len(self._scene.objects()) == 2) and int(profile.getMachineSetting('extruder_amount')) > 1:
808 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
809 if len(self._scene.objects()) > 0:
810 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
811 if menu.MenuItemCount > 0:
814 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
815 self._scene.pushFree(self._selectedObj)
817 elif self._mouseState == 'tool':
818 if self.tempMatrix is not None and self._selectedObj is not None:
819 self._selectedObj.applyMatrix(self.tempMatrix)
820 self._scene.pushFree(self._selectedObj)
821 self._selectObject(self._selectedObj)
822 self.tempMatrix = None
823 self.tool.OnDragEnd()
825 self._mouseState = None
827 def OnMouseMotion(self,e):
828 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
829 p0 -= self.getObjectCenterPos() - self._viewTarget
830 p1 -= self.getObjectCenterPos() - self._viewTarget
832 if e.Dragging() and self._mouseState is not None:
833 if self._mouseState == 'tool':
834 self.tool.OnDrag(p0, p1)
835 elif not e.LeftIsDown() and e.RightIsDown():
836 self._mouseState = 'drag'
837 if wx.GetKeyState(wx.WXK_SHIFT):
838 a = math.cos(math.radians(self._yaw)) / 3.0
839 b = math.sin(math.radians(self._yaw)) / 3.0
840 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
841 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
842 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
843 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
845 self._yaw += e.GetX() - self._mouseX
846 self._pitch -= e.GetY() - self._mouseY
847 if self._pitch > 170:
851 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
852 self._mouseState = 'drag'
853 self._zoom += e.GetY() - self._mouseY
856 if self._zoom > numpy.max(self._machineSize) * 3:
857 self._zoom = numpy.max(self._machineSize) * 3
858 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
859 self._mouseState = 'dragObject'
860 z = max(0, self._mouseClick3DPos[2])
861 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
862 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
867 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
868 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
869 diff = cursorZ1 - cursorZ0
870 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
871 if not e.Dragging() or self._mouseState != 'tool':
872 self.tool.OnMouseMove(p0, p1)
874 self._mouseX = e.GetX()
875 self._mouseY = e.GetY()
877 def OnMouseWheel(self, e):
878 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
879 delta = max(min(delta,4),-4)
880 self._zoom *= 1.0 - delta / 10.0
883 if self._zoom > numpy.max(self._machineSize) * 3:
884 self._zoom = numpy.max(self._machineSize) * 3
887 def OnMouseLeave(self, e):
891 def getMouseRay(self, x, y):
892 if self._viewport is None:
893 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
894 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
895 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
896 p0 -= self._viewTarget
897 p1 -= self._viewTarget
900 def _init3DView(self):
901 # set viewing projection
902 size = self.GetSize()
903 glViewport(0, 0, size.GetWidth(), size.GetHeight())
906 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
908 glDisable(GL_RESCALE_NORMAL)
909 glDisable(GL_LIGHTING)
911 glEnable(GL_DEPTH_TEST)
912 glDisable(GL_CULL_FACE)
914 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
916 glClearColor(0.8, 0.8, 0.8, 1.0)
920 glMatrixMode(GL_PROJECTION)
922 aspect = float(size.GetWidth()) / float(size.GetHeight())
923 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
925 glMatrixMode(GL_MODELVIEW)
927 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
930 connectionGroup = self._printerConnectionManager.getAvailableGroup()
931 if machineCom.machineIsConnected():
932 self.printButton._imageID = 6
933 self.printButton._tooltip = _("Print")
934 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
935 self.printButton._imageID = 2
936 self.printButton._tooltip = _("Toolpath to SD")
937 elif connectionGroup is not None:
938 self.printButton._imageID = connectionGroup.getIconID()
939 self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
941 self.printButton._imageID = 3
942 self.printButton._tooltip = _("Save toolpath")
944 if self._animView is not None:
945 self._viewTarget = self._animView.getPosition()
946 if self._animView.isDone():
947 self._animView = None
948 if self._animZoom is not None:
949 self._zoom = self._animZoom.getPosition()
950 if self._animZoom.isDone():
951 self._animZoom = None
952 if self.viewMode == 'gcode' and self._gcode is not None:
954 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
957 if self._objectShader is None:
958 if opengl.hasShaderSupport():
959 self._objectShader = opengl.GLShader("""
960 varying float light_amount;
964 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
965 gl_FrontColor = gl_Color;
967 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
971 varying float light_amount;
975 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
978 self._objectOverhangShader = opengl.GLShader("""
979 uniform float cosAngle;
980 uniform mat3 rotMatrix;
981 varying float light_amount;
985 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
986 gl_FrontColor = gl_Color;
988 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
990 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
992 light_amount = -10.0;
996 varying float light_amount;
1000 if (light_amount == -10.0)
1002 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1004 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1008 self._objectLoadShader = opengl.GLShader("""
1009 uniform float intensity;
1010 uniform float scale;
1011 varying float light_amount;
1015 vec4 tmp = gl_Vertex;
1016 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1017 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1018 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1019 gl_FrontColor = gl_Color;
1021 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1022 light_amount += 0.2;
1025 uniform float intensity;
1026 varying float light_amount;
1030 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1033 if self._objectShader is None or not self._objectShader.isValid():
1034 self._objectShader = opengl.GLFakeShader()
1035 self._objectOverhangShader = opengl.GLFakeShader()
1036 self._objectLoadShader = None
1038 glTranslate(0,0,-self._zoom)
1039 glRotate(-self._pitch, 1,0,0)
1040 glRotate(self._yaw, 0,0,1)
1041 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1043 self._viewport = glGetIntegerv(GL_VIEWPORT)
1044 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1045 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1047 glClearColor(1,1,1,1)
1048 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1050 if self.viewMode != 'gcode':
1051 for n in xrange(0, len(self._scene.objects())):
1052 obj = self._scene.objects()[n]
1053 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1054 self._renderObject(obj)
1056 if self._mouseX > -1:
1058 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1059 if n < len(self._scene.objects()):
1060 self._focusObj = self._scene.objects()[n]
1062 self._focusObj = None
1063 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1064 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1065 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1066 self._mouse3Dpos -= self._viewTarget
1069 glTranslate(0,0,-self._zoom)
1070 glRotate(-self._pitch, 1,0,0)
1071 glRotate(self._yaw, 0,0,1)
1072 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1074 if self.viewMode == 'gcode':
1075 if self._gcode is not None and self._gcode.layerList is None:
1076 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
1077 self._gcodeLoadThread.daemon = True
1078 self._gcodeLoadThread.start()
1079 if self._gcode is not None and self._gcode.layerList is not None:
1081 if profile.getMachineSetting('machine_center_is_zero') != 'True':
1082 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
1084 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
1085 for n in xrange(0, drawUpTill):
1086 c = 1.0 - float(drawUpTill - n) / 15
1088 if len(self._gcodeVBOs) < n + 1:
1089 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
1090 if time.time() - t > 0.5:
1093 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
1094 if n == drawUpTill - 1:
1095 if len(self._gcodeVBOs[n]) < 9:
1096 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
1098 self._gcodeVBOs[n][8].render(GL_QUADS)
1099 glColor3f(c/2, 0, c)
1100 self._gcodeVBOs[n][9].render(GL_QUADS)
1101 glColor3f(0, c, c/2)
1102 self._gcodeVBOs[n][10].render(GL_QUADS)
1104 self._gcodeVBOs[n][11].render(GL_QUADS)
1107 self._gcodeVBOs[n][12].render(GL_QUADS)
1108 glColor3f(c/2, c/2, 0.0)
1109 self._gcodeVBOs[n][13].render(GL_QUADS)
1111 self._gcodeVBOs[n][14].render(GL_QUADS)
1112 self._gcodeVBOs[n][15].render(GL_QUADS)
1114 self._gcodeVBOs[n][16].render(GL_LINES)
1117 self._gcodeVBOs[n][0].render(GL_LINES)
1118 glColor3f(c/2, 0, c)
1119 self._gcodeVBOs[n][1].render(GL_LINES)
1120 glColor3f(0, c, c/2)
1121 self._gcodeVBOs[n][2].render(GL_LINES)
1123 self._gcodeVBOs[n][3].render(GL_LINES)
1126 self._gcodeVBOs[n][4].render(GL_LINES)
1127 glColor3f(c/2, c/2, 0.0)
1128 self._gcodeVBOs[n][5].render(GL_LINES)
1130 self._gcodeVBOs[n][6].render(GL_LINES)
1131 self._gcodeVBOs[n][7].render(GL_LINES)
1134 glStencilFunc(GL_ALWAYS, 1, 1)
1135 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1137 if self.viewMode == 'overhang':
1138 self._objectOverhangShader.bind()
1139 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1141 self._objectShader.bind()
1142 for obj in self._scene.objects():
1143 if obj._loadAnim is not None:
1144 if obj._loadAnim.isDone():
1145 obj._loadAnim = None
1149 if self._focusObj == obj:
1151 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1154 if self._selectedObj == obj or self._selectedObj is None:
1155 #If we want transparent, then first render a solid black model to remove the printer size lines.
1156 if self.viewMode == 'transparent':
1157 glColor4f(0, 0, 0, 0)
1158 self._renderObject(obj)
1160 glBlendFunc(GL_ONE, GL_ONE)
1161 glDisable(GL_DEPTH_TEST)
1163 if self.viewMode == 'xray':
1164 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1165 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1166 glEnable(GL_STENCIL_TEST)
1168 if self.viewMode == 'overhang':
1169 if self._selectedObj == obj and self.tempMatrix is not None:
1170 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1172 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1174 if not self._scene.checkPlatform(obj):
1175 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1176 self._renderObject(obj)
1178 self._renderObject(obj, brightness)
1179 glDisable(GL_STENCIL_TEST)
1181 glEnable(GL_DEPTH_TEST)
1182 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1184 if self.viewMode == 'xray':
1187 glEnable(GL_STENCIL_TEST)
1188 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1189 glDisable(GL_DEPTH_TEST)
1190 for i in xrange(2, 15, 2):
1191 glStencilFunc(GL_EQUAL, i, 0xFF)
1192 glColor(float(i)/10, float(i)/10, float(i)/5)
1194 glVertex3f(-1000,-1000,-10)
1195 glVertex3f( 1000,-1000,-10)
1196 glVertex3f( 1000, 1000,-10)
1197 glVertex3f(-1000, 1000,-10)
1199 for i in xrange(1, 15, 2):
1200 glStencilFunc(GL_EQUAL, i, 0xFF)
1201 glColor(float(i)/10, 0, 0)
1203 glVertex3f(-1000,-1000,-10)
1204 glVertex3f( 1000,-1000,-10)
1205 glVertex3f( 1000, 1000,-10)
1206 glVertex3f(-1000, 1000,-10)
1209 glDisable(GL_STENCIL_TEST)
1210 glEnable(GL_DEPTH_TEST)
1212 self._objectShader.unbind()
1214 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1216 if self._objectLoadShader is not None:
1217 self._objectLoadShader.bind()
1218 glColor4f(0.2, 0.6, 1.0, 1.0)
1219 for obj in self._scene.objects():
1220 if obj._loadAnim is None:
1222 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1223 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1224 self._renderObject(obj)
1225 self._objectLoadShader.unbind()
1230 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1231 z = self._usbPrintMonitor.getZ()
1232 if self.viewMode == 'gcode':
1233 layer_height = profile.getProfileSettingFloat('layer_height')
1234 layer1_height = profile.getProfileSettingFloat('bottom_thickness')
1235 if layer_height > 0:
1236 if layer1_height > 0:
1237 layer = int((z - layer1_height) / layer_height) + 1
1239 layer = int(z / layer_height)
1242 self.layerSelect.setValue(layer)
1244 size = self._machineSize
1246 glColor4ub(255,255,0,128)
1248 glVertex3f(-size[0]/2,-size[1]/2, z)
1249 glVertex3f( size[0]/2,-size[1]/2, z)
1250 glVertex3f( size[0]/2, size[1]/2, z)
1251 glVertex3f(-size[0]/2, size[1]/2, z)
1254 if self.viewMode == 'gcode':
1255 if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1256 glDisable(GL_DEPTH_TEST)
1259 glTranslate(0,-4,-10)
1260 glColor4ub(60,60,60,255)
1261 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1264 #Draw the object box-shadow, so you can see where it will collide with other objects.
1265 if self._selectedObj is not None:
1267 glEnable(GL_CULL_FACE)
1268 glColor4f(0,0,0,0.16)
1270 for obj in self._scene.objects():
1272 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1273 glBegin(GL_TRIANGLE_FAN)
1274 for p in obj._boundaryHull[::-1]:
1275 glVertex3f(p[0], p[1], 0)
1278 if self._scene.isOneAtATime():
1280 glColor4f(0,0,0,0.06)
1281 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1282 glBegin(GL_TRIANGLE_FAN)
1283 for p in self._selectedObj._printAreaHull[::-1]:
1284 glVertex3f(p[0], p[1], 0)
1286 glBegin(GL_TRIANGLE_FAN)
1287 for p in self._selectedObj._headAreaMinHull[::-1]:
1288 glVertex3f(p[0], p[1], 0)
1292 glDisable(GL_CULL_FACE)
1294 #Draw the outline of the selected object, on top of everything else except the GUI.
1295 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1296 glDisable(GL_DEPTH_TEST)
1297 glEnable(GL_CULL_FACE)
1298 glEnable(GL_STENCIL_TEST)
1300 glStencilFunc(GL_EQUAL, 0, 255)
1302 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1304 glColor4f(1,1,1,0.5)
1305 self._renderObject(self._selectedObj)
1306 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1308 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1309 glDisable(GL_STENCIL_TEST)
1310 glDisable(GL_CULL_FACE)
1311 glEnable(GL_DEPTH_TEST)
1313 if self._selectedObj is not None:
1315 pos = self.getObjectCenterPos()
1316 glTranslate(pos[0], pos[1], pos[2])
1319 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1320 glDisable(GL_DEPTH_TEST)
1323 glTranslate(0,-4,-10)
1324 glColor4ub(60,60,60,255)
1325 opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1328 def _renderObject(self, obj, brightness = False, addSink = True):
1331 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1333 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1335 if self.tempMatrix is not None and obj == self._selectedObj:
1336 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1337 glMultMatrixf(tempMatrix)
1339 offset = obj.getDrawOffset()
1340 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1342 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1343 glMultMatrixf(tempMatrix)
1346 for m in obj._meshList:
1348 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1350 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1355 def _drawMachine(self):
1356 glEnable(GL_CULL_FACE)
1359 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1361 machine = profile.getMachineSetting('machine_type')
1362 if machine.startswith('ultimaker'):
1363 if machine not in self._platformMesh:
1364 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1366 self._platformMesh[machine] = meshes[0]
1368 self._platformMesh[machine] = None
1369 if machine == 'ultimaker2':
1370 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1372 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1373 glColor4f(1,1,1,0.5)
1374 self._objectShader.bind()
1375 self._renderObject(self._platformMesh[machine], False, False)
1376 self._objectShader.unbind()
1378 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1379 if machine == 'ultimaker2':
1380 if not hasattr(self._platformMesh[machine], 'texture'):
1381 self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1382 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1383 glEnable(GL_TEXTURE_2D)
1387 glTranslate(0,150,-5)
1392 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1395 glVertex3f( w, 0, h)
1397 glVertex3f(-w, 0, h)
1399 glVertex3f(-w, 0, 0)
1401 glVertex3f( w, 0, 0)
1404 glVertex3f(-w, d, h)
1406 glVertex3f( w, d, h)
1408 glVertex3f( w, d, 0)
1410 glVertex3f(-w, d, 0)
1412 glDisable(GL_TEXTURE_2D)
1413 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1419 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1420 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1421 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1422 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1423 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1424 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1429 polys = profile.getMachineSizePolygons()
1430 height = profile.getMachineSettingFloat('machine_height')
1432 for n in xrange(0, len(polys[0])):
1434 glColor4ub(5, 171, 231, 96)
1436 glColor4ub(5, 171, 231, 64)
1437 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1438 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1439 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1440 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1442 glColor4ub(5, 171, 231, 128)
1443 glBegin(GL_TRIANGLE_FAN)
1444 for p in polys[0][::-1]:
1445 glVertex3f(p[0], p[1], height)
1449 if self._platformTexture is None:
1450 self._platformTexture = opengl.loadGLTexture('checkerboard.png')
1451 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1452 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1453 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1454 glColor4f(1,1,1,0.5)
1455 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1456 glEnable(GL_TEXTURE_2D)
1457 glBegin(GL_TRIANGLE_FAN)
1459 glTexCoord2f(p[0]/20, p[1]/20)
1460 glVertex3f(p[0], p[1], 0)
1462 glDisable(GL_TEXTURE_2D)
1463 glColor4ub(127, 127, 127, 200)
1464 for poly in polys[1:]:
1465 glBegin(GL_TRIANGLE_FAN)
1467 glTexCoord2f(p[0]/20, p[1]/20)
1468 glVertex3f(p[0], p[1], 0)
1473 glDisable(GL_CULL_FACE)
1475 def _generateGCodeVBOs(self, layer):
1477 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1478 if ':' in extrudeType:
1479 extruder = int(extrudeType[extrudeType.find(':')+1:])
1480 extrudeType = extrudeType[0:extrudeType.find(':')]
1483 pointList = numpy.zeros((0,3), numpy.float32)
1485 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1487 a = numpy.concatenate((a[:-1], a[1:]), 1)
1488 a = a.reshape((len(a) * 2, 3))
1489 pointList = numpy.concatenate((pointList, a))
1490 ret.append(opengl.GLVBO(pointList))
1493 def _generateGCodeVBOs2(self, layer):
1494 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1495 filamentArea = math.pi * filamentRadius * filamentRadius
1496 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1499 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1500 if ':' in extrudeType:
1501 extruder = int(extrudeType[extrudeType.find(':')+1:])
1502 extrudeType = extrudeType[0:extrudeType.find(':')]
1505 pointList = numpy.zeros((0,3), numpy.float32)
1507 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1509 if extrudeType == 'FILL':
1512 normal = a[1:] - a[:-1]
1513 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1514 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1517 ePerDist = path['extrusion'][1:] / lens
1519 lineWidth = ePerDist / path['layerThickness'] / 2.0
1521 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1523 normal[:,0] *= lineWidth
1524 normal[:,1] *= lineWidth
1526 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1527 b = numpy.concatenate((b, a[1:] + normal), 1)
1528 b = numpy.concatenate((b, a[1:] - normal), 1)
1529 b = numpy.concatenate((b, a[:-1] - normal), 1)
1530 b = numpy.concatenate((b, a[:-1] + normal), 1)
1531 b = b.reshape((len(b) * 4, 3))
1534 normal2 = normal[:-1] + normal[1:]
1535 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1536 normal2[:,0] /= lens2
1537 normal2[:,1] /= lens2
1538 normal2[:,0] *= lineWidth[:-1]
1539 normal2[:,1] *= lineWidth[:-1]
1541 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1542 c = numpy.concatenate((c, a[1:-1]), 1)
1543 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1544 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1545 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1547 c = numpy.concatenate((c, a[1:-1]), 1)
1548 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1549 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1550 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1552 c = c.reshape((len(c) * 8, 3))
1554 pointList = numpy.concatenate((pointList, b, c))
1556 pointList = numpy.concatenate((pointList, b))
1557 ret.append(opengl.GLVBO(pointList))
1559 pointList = numpy.zeros((0,3), numpy.float32)
1561 if path['type'] == 'move':
1562 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1563 a = numpy.concatenate((a[:-1], a[1:]), 1)
1564 a = a.reshape((len(a) * 2, 3))
1565 pointList = numpy.concatenate((pointList, a))
1566 if path['type'] == 'retract':
1567 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1568 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1569 a = a.reshape((len(a) * 2, 3))
1570 pointList = numpy.concatenate((pointList, a))
1571 ret.append(opengl.GLVBO(pointList))
1575 def getObjectCenterPos(self):
1576 if self._selectedObj is None:
1577 return [0.0, 0.0, 0.0]
1578 pos = self._selectedObj.getPosition()
1579 size = self._selectedObj.getSize()
1580 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1582 def getObjectBoundaryCircle(self):
1583 if self._selectedObj is None:
1585 return self._selectedObj.getBoundaryCircle()
1587 def getObjectSize(self):
1588 if self._selectedObj is None:
1589 return [0.0, 0.0, 0.0]
1590 return self._selectedObj.getSize()
1592 def getObjectMatrix(self):
1593 if self._selectedObj is None:
1594 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1595 return self._selectedObj.getMatrix()
1597 class shaderEditor(wx.Dialog):
1598 def __init__(self, parent, callback, v, f):
1599 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1600 self._callback = callback
1601 s = wx.BoxSizer(wx.VERTICAL)
1603 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1604 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1605 s.Add(self._vertex, 1, flag=wx.EXPAND)
1606 s.Add(self._fragment, 1, flag=wx.EXPAND)
1608 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1609 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1611 self.SetPosition(self.GetParent().GetPosition())
1612 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1615 def OnText(self, e):
1616 self._callback(self._vertex.GetValue(), self._fragment.GetValue())