1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
4 import wx.lib.inspection
12 import cStringIO as StringIO
15 OpenGL.ERROR_CHECKING = False
16 from OpenGL.GLU import *
17 from OpenGL.GL import *
19 from Cura.gui import printWindow
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 pluginInfo
26 from Cura.util import removableStorage
27 from Cura.util import explorer
28 from Cura.util.printerConnection import printerConnectionManager
29 from Cura.gui.util import previewTools
30 from Cura.gui.util import openglHelpers
31 from Cura.gui.util import openglGui
32 from Cura.gui.util import engineResultView
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(self)
44 self._objectShader = None
45 self._objectLoadShader = None
47 self._selectedObj = None
48 self._objColors = [None,None,None,None]
51 self._mouseState = None
52 self._viewTarget = numpy.array([0,0,0], numpy.float32)
53 self._mouse3Dpos = numpy.array([0,0,0], numpy.float32)
56 self._lastObjectSink = None
57 self._platformMesh = {}
58 self.glReleaseList = []
59 self._platformTexture = None
60 self._isSimpleMode = True
61 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
64 self._modelMatrix = None
65 self._projMatrix = None
66 self.tempMatrix = None
68 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
69 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
70 self.printButton.setDisabled(True)
73 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
74 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
75 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
77 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
78 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
80 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
81 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
83 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
84 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
85 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
87 self.rotateToolButton.setExpandArrow(True)
88 self.scaleToolButton.setExpandArrow(True)
89 self.mirrorToolButton.setExpandArrow(True)
91 self.scaleForm = openglGui.glFrame(self, (2, -2))
92 openglGui.glGuiLayoutGrid(self.scaleForm)
93 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
94 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
95 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
96 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
97 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
98 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
99 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
100 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
101 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
102 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
103 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
104 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
105 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
106 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
108 self.viewSelection = openglGui.glComboButton(self, _("View mode"), 7, [26,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange, self.OnViewStateChange)
109 self.viewSelection.setDisabled(True)
110 #self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
111 #self.youMagineButton.setDisabled(True)
113 self.notification = openglGui.glNotification(self, (0, 0))
115 self._engine = sliceEngine.Engine(self._updateEngineProgress)
116 self._engineResultView = engineResultView.engineResultView(self)
117 self._sceneUpdateTimer = wx.Timer(self)
118 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
119 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
120 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
124 self.updateToolButtons()
125 self.updateProfileToControls()
128 # Delete all objects first
129 self.OnDeleteAll(None)
130 self._engine.cleanup()
131 if self._objectShader is not None:
132 self._objectShader.release()
133 if self._objectLoadShader is not None:
134 self._objectLoadShader.release()
135 if self._objectOverhangShader is not None:
136 self._objectOverhangShader.release()
137 for obj in self.glReleaseList:
140 def loadGCodeFile(self, filename):
141 self.OnDeleteAll(None)
142 #Cheat the engine results to load a GCode file into it.
143 self._engine._result = sliceEngine.EngineResult()
144 with open(filename, "r") as f:
145 self._engine._result.setGCode(f.read())
146 self._engine._result.setFinished(True)
147 self._engineResultView.setResult(self._engine._result)
148 self.printButton.setBottomText('')
149 self.viewSelection.setDisabled(False)
150 self.viewSelection.setValue(4)
151 self.printButton.setDisabled(False)
152 #self.youMagineButton.setDisabled(True)
155 def loadSceneFiles(self, filenames):
156 #self.youMagineButton.setDisabled(False)
157 #if self.viewSelection.getValue() == 4:
158 # self.viewSelection.setValue(0)
159 # self.OnViewChange()
160 self.loadScene(filenames)
162 def loadFiles(self, filenames):
163 mainWindow = self.GetTopLevelParent()
164 # only one GCODE file can be active
165 # so if single gcode file, process this
166 # otherwise ignore all gcode files
168 if len(filenames) == 1:
169 filename = filenames[0]
170 ext = os.path.splitext(filename)[1].lower()
171 if ext == '.g' or ext == '.gcode':
172 gcodeFilename = filename
173 mainWindow.addToModelMRU(filename)
174 if gcodeFilename is not None:
175 self.loadGCodeFile(gcodeFilename)
177 # process directories and special file types
178 # and keep scene files for later processing
180 ignored_types = dict()
181 # use file list as queue
182 # pop first entry for processing and append new files at end
184 filename = filenames.pop(0)
185 if os.path.isdir(filename):
186 # directory: queue all included files and directories
187 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
189 ext = os.path.splitext(filename)[1].lower()
191 profile.loadProfile(filename)
192 mainWindow.addToProfileMRU(filename)
193 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
194 scene_filenames.append(filename)
195 mainWindow.addToModelMRU(filename)
197 ignored_types[ext] = 1
199 ignored_types = ignored_types.keys()
201 self.notification.message(_("ignored: ") + " ".join("*" + type for type in ignored_types))
202 mainWindow.updateProfileToAllControls()
203 # now process all the scene files
205 self.loadSceneFiles(scene_filenames)
206 self._selectObject(None)
208 newZoom = numpy.max(self._machineSize)
209 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
210 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
212 def reloadScene(self, e):
213 # Copy the list before DeleteAll clears it
215 pms_transforms = [] #position, rotation matrix, scale
216 for obj in self._scene.objects():
217 fileList.append(obj.getOriginFilename())
218 pms_transforms.append((obj.getPosition(), obj.getMatrix(), obj.getScale()))
220 self.OnDeleteAll(None)
221 self.loadScene(fileList, pms_transforms)
223 def OnResetPositions(self, e):
225 self._scene.arrangeAll(True)
226 self._scene.centerAll()
230 def OnResetTransformations(self, e):
231 for obj in self._scene.objects():
235 self._scene.arrangeAll()
236 self._scene.centerAll()
239 def showLoadModel(self, button = 1):
241 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)
243 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
244 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
245 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
246 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
247 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
248 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
249 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
250 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
252 dlg.SetWildcard(wildcardFilter)
253 if dlg.ShowModal() != wx.ID_OK:
256 filenames = dlg.GetPaths()
258 if len(filenames) < 1:
260 profile.putPreference('lastFile', filenames[0])
261 self.loadFiles(filenames)
263 def showSaveModel(self):
264 if len(self._scene.objects()) < 1:
266 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
267 fileExtensions = meshLoader.saveSupportedExtensions()
268 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
269 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
270 dlg.SetWildcard(wildcardFilter)
271 if dlg.ShowModal() != wx.ID_OK:
274 filename = dlg.GetPath()
276 meshLoader.saveMeshes(filename, self._scene.objects())
278 def OnPrintButton(self, button):
280 connectionGroup = self._printerConnectionManager.getAvailableGroup()
281 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
282 drives = removableStorage.getPossibleSDcardDrives()
284 choices = map(lambda n: n[0], drives)
285 choices += (_("Custom file destination"), )
286 title = _("Multiple removable drives have been found")
288 choices = [drives[0][0], _("Custom file destination")]
289 title = _("A removable drive has been found")
291 dlg = wx.SingleChoiceDialog(self, _("Select destination SD card drive\nYou can also select a custom file to save to"), title, choices)
292 if dlg.ShowModal() != wx.ID_OK:
296 drive = drives[dlg.GetSelection()]
304 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
306 #check if the file is part of the root folder.
307 # If so, create folders on sd card to get the same folder hierarchy.
308 repDir = profile.getPreference("sdcard_rootfolder")
310 if os.path.exists(repDir) and os.path.isdir(repDir):
311 repDir = os.path.abspath(repDir)
312 originFilename = os.path.abspath( self._scene._objectList[0].getOriginFilename() )
313 if os.path.dirname(originFilename).startswith(repDir):
314 new_filename = os.path.splitext(originFilename[len(repDir):])[0] + profile.getGCodeExtension()
315 sdPath = os.path.dirname(os.path.join( drive[1], new_filename))
316 if not os.path.exists(sdPath):
317 print "Creating replication directory:", sdPath
319 filename = new_filename
323 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
324 elif connectionGroup is not None:
325 connections = connectionGroup.getAvailableConnections()
326 if len(connections) < 2:
327 connection = connections[0]
329 dlg = wx.SingleChoiceDialog(self, _("Select the %s connection to use") % (connectionGroup.getName()), _("Multiple %s connections found") % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
330 if dlg.ShowModal() != wx.ID_OK:
333 connection = connections[dlg.GetSelection()]
335 self._openPrintWindowForConnection(connection)
340 connections = self._printerConnectionManager.getAvailableConnections()
341 menu.connectionMap = {}
342 for connection in connections:
343 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
344 menu.connectionMap[i.GetId()] = connection
345 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
346 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
347 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
351 def _openPrintWindowForConnection(self, connection):
352 if connection.window is None or not connection.window:
353 connection.window = printWindow.printWindowAdvanced(self, connection)
355 connection.window.Show()
356 connection.window.Raise()
357 if not connection.loadGCodeData(self._engine.getResult().getGCode()):
358 if connection.isPrinting():
359 self.notification.message(_("Cannot start print, because other print still running."))
361 self.notification.message(_("Failed to start print..."))
363 def showSaveGCode(self):
364 if len(self._scene._objectList) < 1:
366 if not self._engine.getResult().isFinished():
368 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
369 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
370 dlg.SetFilename(filename)
371 dlg.SetWildcard('Toolpath (*%s)|*%s;*%s' % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
372 if dlg.ShowModal() != wx.ID_OK:
375 filename = dlg.GetPath()
378 threading.Thread(target=self._saveGCode,args=(filename,)).start()
380 def _saveGCode(self, targetFilename, ejectDrive = False):
381 gcode = self._engine.getResult().getGCode()
383 size = float(len(gcode))
385 with open(targetFilename, 'wb') as fdst:
387 buf = gcode.read(16*1024)
392 self.printButton.setProgressBar(read_pos / size)
395 import sys, traceback
396 traceback.print_exc()
397 self.notification.message(_("Failed to save"))
400 self.notification.message(_("Saved as %s") % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
401 elif explorer.hasExplorer():
402 self.notification.message(_("Saved as %s") % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, _('Open folder'))
404 self.notification.message(_("Saved as %s") % (targetFilename))
405 self.printButton.setProgressBar(None)
407 def _doEjectSD(self, drive):
408 if removableStorage.ejectDrive(drive):
409 self.notification.message(_('You can now eject the card.'))
411 self.notification.message(_('Safe remove failed...'))
413 def _showEngineLog(self):
414 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._engine.getResult().getLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
418 def OnToolSelect(self, button):
419 if self.rotateToolButton.getSelected():
420 self.tool = previewTools.toolRotate(self)
421 elif self.scaleToolButton.getSelected():
422 self.tool = previewTools.toolScale(self)
423 elif self.mirrorToolButton.getSelected():
424 self.tool = previewTools.toolNone(self)
426 self.tool = previewTools.toolNone(self)
427 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
428 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
429 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
430 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
431 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
432 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
433 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
434 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
436 def updateToolButtons(self):
437 if self._selectedObj is None:
441 self.rotateToolButton.setHidden(hidden)
442 self.scaleToolButton.setHidden(hidden)
443 self.mirrorToolButton.setHidden(hidden)
445 self.rotateToolButton.setSelected(False)
446 self.scaleToolButton.setSelected(False)
447 self.mirrorToolButton.setSelected(False)
450 def OnViewChange(self):
451 if self.viewSelection.getValue() == 4:
452 self.viewMode = 'gcode'
453 self.tool = previewTools.toolNone(self)
454 elif self.viewSelection.getValue() == 1:
455 self.viewMode = 'overhang'
456 elif self.viewSelection.getValue() == 2:
457 self.viewMode = 'transparent'
458 elif self.viewSelection.getValue() == 3:
459 self.viewMode = 'xray'
461 self.viewMode = 'normal'
462 self._engineResultView.setEnabled(self.viewMode == 'gcode')
465 def OnViewStateChange(self, state):
466 self._engineResultView.layerSelect.setHidden(self.viewMode != 'gcode' or state)
467 self._engineResultView.singleLayerToggle.setHidden(self.viewMode != 'gcode' or state)
469 def OnRotateReset(self, button):
470 if self._selectedObj is None:
472 self._selectedObj.resetRotation()
473 self._scene.pushFree(self._selectedObj)
474 self._selectObject(self._selectedObj)
477 def OnLayFlat(self, button):
478 if self._selectedObj is None:
480 self._selectedObj.layFlat()
481 self._scene.pushFree(self._selectedObj)
482 self._selectObject(self._selectedObj)
485 def OnScaleReset(self, button):
486 if self._selectedObj is None:
488 self._selectedObj.resetScale()
489 self._selectObject(self._selectedObj)
490 self.updateProfileToControls()
493 def OnScaleMax(self, button):
494 if self._selectedObj is None:
496 machine = profile.getMachineSetting('machine_type')
497 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
498 self._scene.pushFree(self._selectedObj)
500 if machine == "ultimaker2":
501 #This is bad and Jaime should feel bad!
502 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
503 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
504 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
505 self._scene.pushFree(self._selectedObj)
507 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
508 self._scene.pushFree(self._selectedObj)
509 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
510 self._scene.pushFree(self._selectedObj)
511 self._selectObject(self._selectedObj)
512 self.updateProfileToControls()
515 def OnMirror(self, axis):
516 if self._selectedObj is None:
518 self._selectedObj.mirror(axis)
521 def OnScaleEntry(self, value, axis):
522 if self._selectedObj is None:
528 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
529 self.updateProfileToControls()
530 self._scene.pushFree(self._selectedObj)
531 self._selectObject(self._selectedObj)
534 def OnScaleEntryMM(self, value, axis):
535 if self._selectedObj is None:
541 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
542 self.updateProfileToControls()
543 self._scene.pushFree(self._selectedObj)
544 self._selectObject(self._selectedObj)
547 def OnDeleteAll(self, e):
548 while len(self._scene.objects()) > 0:
549 self._deleteObject(self._scene.objects()[0])
550 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
551 self._engineResultView.setResult(None)
552 self.viewSelection.setDisabled(True)
553 self.printButton.setDisabled(True)
555 def OnMultiply(self, e):
556 if self._focusObj is None:
559 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
560 if dlg.ShowModal() != wx.ID_OK:
566 # 0:unrequested arrange all. Objects should not move.
567 # 1:requested arrange all but refused.
568 # 2:arrange all and center from now on.
569 requestedArrangeAll = 0
575 self._scene.add(newObj)
576 if requestedArrangeAll == 2:
577 self._scene.centerAll()
579 if not self._scene.checkPlatform(newObj):
580 if requestedArrangeAll == 0:
581 requestedArrangeAll = 1
582 dlg = wx.MessageDialog(self, _("Cannot fit all the requested duplicates. Do you want to try and reset object positions?"), _("Reset Positions"), wx.YES_NO)
584 if dlg.ShowModal() == wx.ID_YES:
586 requestedArrangeAll = 2
587 self._scene.remove(newObj)
588 self.OnResetPositions(None)
598 self.notification.message(_("Could not create more than %d items") % (n - 1))
599 self._scene.remove(newObj)
600 if requestedArrangeAll == 2:
601 self._scene.centerAll()
605 def OnSplitObject(self, e):
606 if self._focusObj is None:
608 self._scene.remove(self._focusObj)
609 for obj in self._focusObj.split(self._splitCallback):
610 if numpy.max(obj.getSize()) > 2.0:
612 self._scene.centerAll()
613 self._selectObject(None)
616 def OnCenter(self, e):
617 if self._focusObj is None:
619 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
620 self._scene.pushFree(self._selectedObj)
621 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
622 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
625 def _splitCallback(self, progress):
628 def OnMergeObjects(self, e):
629 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
630 if len(self._scene.objects()) == 2:
631 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
634 self._scene.merge(self._selectedObj, self._focusObj)
637 def getObjectSink(self):
638 if self._isSimpleMode:
639 return float(profile.settingsDictionary["object_sink"].getDefault())
641 return profile.getProfileSettingFloat("object_sink")
643 def sceneUpdated(self):
644 objectSink = self.getObjectSink()
645 if self._lastObjectSink != objectSink:
646 self._lastObjectSink = objectSink
647 self._scene.updateHeadSize()
649 wx.CallAfter(self._sceneUpdateTimer.Start, 500, True)
650 self._engine.abortEngine()
651 self._scene.updateSizeOffsets()
654 def _onRunEngine(self, e):
655 if self._isSimpleMode:
656 self._engine.runEngine(self._scene, self.GetTopLevelParent().simpleSettingsPanel.getSettingOverrides())
658 self._engine.runEngine(self._scene)
660 def _updateEngineProgress(self, progressValue):
661 result = self._engine.getResult()
662 finished = result is not None and result.isFinished()
664 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
666 self.printButton.setDisabled(not finished)
667 self.viewSelection.setDisabled(not finished)
668 if progressValue >= 0.0:
669 self.printButton.setProgressBar(progressValue)
671 self.printButton.setProgressBar(None)
672 self._engineResultView.setResult(result)
674 self.printButton.setProgressBar(None)
675 text = '%s' % (result.getPrintTime())
676 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
677 amount = result.getFilamentAmount(e)
680 text += '\n%s' % (amount)
681 cost = result.getFilamentCost(e)
683 text += '\n%s' % (cost)
684 self.printButton.setBottomText(text)
686 self.printButton.setBottomText('')
689 def loadScene(self, fileList, pms_transforms=None):
691 for filename in fileList:
694 ext = os.path.splitext(filename)[1].lower()
695 if ext in imageToMesh.supportedExtensions():
696 imageToMesh.convertImageDialog(self, filename).Show()
699 objList = meshLoader.loadMeshes(filename)
701 traceback.print_exc()
704 if self._objectLoadShader is not None:
705 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
709 if pms_transforms is not None and len(pms_transforms) == len(fileList):
710 obj.setPosition(pms_transforms[objIndex][0])
711 obj.applyMatrix(pms_transforms[objIndex][1])
712 obj.setScale(pms_transforms[objIndex][2][0], 0, False)
713 obj.setScale(pms_transforms[objIndex][2][1], 1, False)
714 obj.setScale(pms_transforms[objIndex][2][2], 2, False)
716 if not self._scene.checkPlatform(obj):
717 self._scene.centerAll()
718 self._selectObject(obj)
719 if obj.getScale()[0] < 1.0:
720 self.notification.message(_("Warning: Object scaled down."))
723 def _deleteObject(self, obj):
724 if obj == self._selectedObj:
725 self._selectObject(None)
726 if obj == self._focusObj:
727 self._focusObj = None
728 self._scene.remove(obj)
729 for m in obj._meshList:
730 if m.vbo is not None and m.vbo.decRef():
731 self.glReleaseList.append(m.vbo)
732 if len(self._scene.objects()) == 0:
733 self._engineResultView.setResult(None)
734 self.printButton.setDisabled(True)
735 self.viewSelection.setDisabled(True)
736 self.printButton.setBottomText('')
741 def _selectObject(self, obj, zoom = True):
742 if obj != self._selectedObj:
743 self._selectedObj = obj
744 self.updateModelSettingsToControls()
745 self.updateToolButtons()
746 if zoom and obj is not None:
747 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
748 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
749 newZoom = obj.getBoundaryCircle() * 6
750 if newZoom > numpy.max(self._machineSize) * 3:
751 newZoom = numpy.max(self._machineSize) * 3
752 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
754 def updateProfileToControls(self):
755 oldSimpleMode = self._isSimpleMode
756 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
757 if self._isSimpleMode != oldSimpleMode:
758 self._scene.arrangeAll()
760 self._scene.updateSizeOffsets(True)
761 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
762 self._objColors[0] = profile.getPreferenceColour('model_colour')
763 self._objColors[1] = profile.getPreferenceColour('model_colour2')
764 self._objColors[2] = profile.getPreferenceColour('model_colour3')
765 self._objColors[3] = profile.getPreferenceColour('model_colour4')
766 self._scene.updateMachineDimensions()
767 if self._zoom > numpy.max(self._machineSize) * 3:
768 self._animZoom = openglGui.animation(self, self._zoom, numpy.max(self._machineSize) * 3, 0.5)
769 self.updateModelSettingsToControls()
771 def updateModelSettingsToControls(self):
772 if self._selectedObj is not None:
773 scale = self._selectedObj.getScale()
774 size = self._selectedObj.getSize()
775 self.scaleXctrl.setValue(round(scale[0], 2))
776 self.scaleYctrl.setValue(round(scale[1], 2))
777 self.scaleZctrl.setValue(round(scale[2], 2))
778 self.scaleXmmctrl.setValue(round(size[0], 2))
779 self.scaleYmmctrl.setValue(round(size[1], 2))
780 self.scaleZmmctrl.setValue(round(size[2], 2))
782 def OnKeyChar(self, keyCode):
783 if self._engineResultView.OnKeyChar(keyCode):
785 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and sys.platform.startswith("darwin")):
786 if self._selectedObj is not None:
787 self._deleteObject(self._selectedObj)
789 if keyCode == wx.WXK_UP:
790 if wx.GetKeyState(wx.WXK_SHIFT):
797 elif keyCode == wx.WXK_DOWN:
798 if wx.GetKeyState(wx.WXK_SHIFT):
800 if self._zoom > numpy.max(self._machineSize) * 3:
801 self._zoom = numpy.max(self._machineSize) * 3
805 elif keyCode == wx.WXK_LEFT:
808 elif keyCode == wx.WXK_RIGHT:
811 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
816 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
818 if self._zoom > numpy.max(self._machineSize) * 3:
819 self._zoom = numpy.max(self._machineSize) * 3
821 elif keyCode == wx.WXK_HOME:
825 elif keyCode == wx.WXK_PAGEUP:
829 elif keyCode == wx.WXK_PAGEDOWN:
833 elif keyCode == wx.WXK_END:
838 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
839 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
840 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
841 from collections import defaultdict
842 from gc import get_objects
843 self._beforeLeakTest = defaultdict(int)
844 for i in get_objects():
845 self._beforeLeakTest[type(i)] += 1
846 if keyCode == wx.WXK_F6 and wx.GetKeyState(wx.WXK_SHIFT):
847 # Show the WX widget inspection tool
848 wx.lib.inspection.InspectionTool().Show()
849 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
850 from collections import defaultdict
851 from gc import get_objects
852 self._afterLeakTest = defaultdict(int)
853 for i in get_objects():
854 self._afterLeakTest[type(i)] += 1
855 for k in self._afterLeakTest:
856 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
857 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
859 def ShaderUpdate(self, v, f):
860 s = openglHelpers.GLShader(v, f)
862 self._objectLoadShader.release()
863 self._objectLoadShader = s
864 for obj in self._scene.objects():
865 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
868 def OnMouseDown(self,e):
869 self._mouseX = e.GetX()
870 self._mouseY = e.GetY()
871 self._mouseClick3DPos = self._mouse3Dpos
872 self._mouseClickFocus = self._focusObj
874 self._mouseState = 'doubleClick'
876 if self._mouseState == 'dragObject' and self._selectedObj is not None:
877 self._scene.pushFree(self._selectedObj)
879 self._mouseState = 'dragOrClick'
880 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
881 p0 -= self.getObjectCenterPos() - self._viewTarget
882 p1 -= self.getObjectCenterPos() - self._viewTarget
883 if self.tool.OnDragStart(p0, p1):
884 self._mouseState = 'tool'
885 if self._mouseState == 'dragOrClick':
886 if e.GetButton() == 1:
887 if self._focusObj is not None:
888 self._selectObject(self._focusObj, False)
891 def OnMouseUp(self, e):
892 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
894 if self._mouseState == 'dragOrClick':
895 if e.GetButton() == 1:
896 self._selectObject(self._focusObj)
897 if e.GetButton() == 3:
899 if self._focusObj is not None:
901 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
902 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
903 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
904 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
906 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:
907 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
908 if len(self._scene.objects()) > 0:
909 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
910 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
911 self.Bind(wx.EVT_MENU, self.OnResetPositions, menu.Append(-1, _("Reset all objects positions")))
912 self.Bind(wx.EVT_MENU, self.OnResetTransformations, menu.Append(-1, _("Reset all objects transformations")))
914 if menu.MenuItemCount > 0:
917 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
918 self._scene.pushFree(self._selectedObj)
920 elif self._mouseState == 'tool':
921 if self.tempMatrix is not None and self._selectedObj is not None:
922 self._selectedObj.applyMatrix(self.tempMatrix)
923 self._scene.pushFree(self._selectedObj)
924 self._selectObject(self._selectedObj)
925 self.tempMatrix = None
926 self.tool.OnDragEnd()
928 self._mouseState = None
930 def OnMouseMotion(self,e):
931 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
932 p0 -= self.getObjectCenterPos() - self._viewTarget
933 p1 -= self.getObjectCenterPos() - self._viewTarget
935 if e.Dragging() and self._mouseState is not None:
936 if self._mouseState == 'tool':
937 self.tool.OnDrag(p0, p1)
938 elif not e.LeftIsDown() and e.RightIsDown():
939 self._mouseState = 'drag'
940 if wx.GetKeyState(wx.WXK_SHIFT):
941 a = math.cos(math.radians(self._yaw)) / 3.0
942 b = math.sin(math.radians(self._yaw)) / 3.0
943 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
944 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
945 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
946 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
948 self._yaw += e.GetX() - self._mouseX
949 self._pitch -= e.GetY() - self._mouseY
950 if self._pitch > 170:
954 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
955 self._mouseState = 'drag'
956 self._zoom += e.GetY() - self._mouseY
959 if self._zoom > numpy.max(self._machineSize) * 3:
960 self._zoom = numpy.max(self._machineSize) * 3
961 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
962 self._mouseState = 'dragObject'
963 z = max(0, self._mouseClick3DPos[2])
964 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
965 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
970 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
971 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
972 diff = cursorZ1 - cursorZ0
973 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
974 if not e.Dragging() or self._mouseState != 'tool':
975 self.tool.OnMouseMove(p0, p1)
977 self._mouseX = e.GetX()
978 self._mouseY = e.GetY()
980 def OnMouseWheel(self, e):
981 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
982 delta = max(min(delta,4),-4)
983 self._zoom *= 1.0 - delta / 10.0
986 if self._zoom > numpy.max(self._machineSize) * 3:
987 self._zoom = numpy.max(self._machineSize) * 3
990 def OnMouseLeave(self, e):
994 def getMouseRay(self, x, y):
995 if self._viewport is None:
996 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
997 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
998 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
999 p0 -= self._viewTarget
1000 p1 -= self._viewTarget
1003 def _init3DView(self):
1004 # set viewing projection
1005 size = self.GetSize()
1006 glViewport(0, 0, size.GetWidth(), size.GetHeight())
1009 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
1011 glDisable(GL_RESCALE_NORMAL)
1012 glDisable(GL_LIGHTING)
1013 glDisable(GL_LIGHT0)
1014 glEnable(GL_DEPTH_TEST)
1015 glDisable(GL_CULL_FACE)
1017 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1019 #scene background color
1020 glClearColor(0.85, 0.85, 0.85, 1.0)
1024 glMatrixMode(GL_PROJECTION)
1026 aspect = float(size.GetWidth()) / float(size.GetHeight())
1027 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
1029 glMatrixMode(GL_MODELVIEW)
1031 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1033 def OnPaint(self,e):
1034 connectionGroup = self._printerConnectionManager.getAvailableGroup()
1035 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
1036 self.printButton._imageID = 2
1037 self.printButton._tooltip = _("Toolpath to SD")
1038 elif connectionGroup is not None:
1039 self.printButton._imageID = connectionGroup.getIconID()
1040 #self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
1041 self.printButton._tooltip = _("Print/Control")
1043 self.printButton._imageID = 3
1044 self.printButton._tooltip = _("Save toolpath")
1046 if self._animView is not None:
1047 self._viewTarget = self._animView.getPosition()
1048 if self._animView.isDone():
1049 self._animView = None
1050 if self._animZoom is not None:
1051 self._zoom = self._animZoom.getPosition()
1052 if self._animZoom.isDone():
1053 self._animZoom = None
1054 if self._objectShader is None: #TODO: add loading shaders from file(s)
1055 if openglHelpers.hasShaderSupport():
1056 self._objectShader = openglHelpers.GLShader("""
1057 varying float light_amount;
1061 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1062 gl_FrontColor = gl_Color;
1064 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1065 light_amount += 0.2;
1068 varying float light_amount;
1072 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1075 self._objectOverhangShader = openglHelpers.GLShader("""
1076 uniform float cosAngle;
1077 uniform mat3 rotMatrix;
1078 varying float light_amount;
1082 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1083 gl_FrontColor = gl_Color;
1085 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1086 light_amount += 0.2;
1087 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
1089 light_amount = -10.0;
1093 varying float light_amount;
1097 if (light_amount == -10.0)
1099 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1101 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1105 self._objectLoadShader = openglHelpers.GLShader("""
1106 uniform float intensity;
1107 uniform float scale;
1108 varying float light_amount;
1112 vec4 tmp = gl_Vertex;
1113 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1114 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1115 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1116 gl_FrontColor = gl_Color;
1118 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1119 light_amount += 0.2;
1122 uniform float intensity;
1123 varying float light_amount;
1127 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1130 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
1131 self._objectShader = openglHelpers.GLFakeShader()
1132 self._objectOverhangShader = openglHelpers.GLFakeShader()
1133 self._objectLoadShader = None
1135 glTranslate(0,0,-self._zoom)
1136 glRotate(-self._pitch, 1,0,0)
1137 glRotate(self._yaw, 0,0,1)
1138 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1140 self._viewport = glGetIntegerv(GL_VIEWPORT)
1141 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1142 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1144 glClearColor(1,1,1,1)
1145 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1147 if self.viewMode != 'gcode':
1148 for n in xrange(0, len(self._scene.objects())):
1149 obj = self._scene.objects()[n]
1150 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1151 self._renderObject(obj)
1153 if self._mouseX > -1: # mouse has not passed over the opengl window.
1155 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1156 if n < len(self._scene.objects()):
1157 self._focusObj = self._scene.objects()[n]
1159 self._focusObj = None
1160 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1161 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1162 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1163 self._mouse3Dpos -= self._viewTarget
1166 glTranslate(0,0,-self._zoom)
1167 glRotate(-self._pitch, 1,0,0)
1168 glRotate(self._yaw, 0,0,1)
1169 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1171 self._objectShader.unbind()
1172 self._engineResultView.OnDraw()
1173 if self.viewMode != 'gcode':
1174 glStencilFunc(GL_ALWAYS, 1, 1)
1175 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1177 if self.viewMode == 'overhang':
1178 self._objectOverhangShader.bind()
1179 support_angle = profile.getProfileSettingFloat('support_angle')
1180 if self._isSimpleMode:
1181 support_angle = float(profile.settingsDictionary['support_angle'].getDefault())
1182 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - support_angle)))
1184 self._objectShader.bind()
1185 for obj in self._scene.objects():
1186 if obj._loadAnim is not None:
1187 if obj._loadAnim.isDone():
1188 obj._loadAnim = None
1192 if self._focusObj == obj:
1194 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1197 if self._selectedObj == obj or self._selectedObj is None:
1198 #If we want transparent, then first render a solid black model to remove the printer size lines.
1199 if self.viewMode == 'transparent':
1200 glColor4f(0, 0, 0, 0)
1201 self._renderObject(obj)
1203 glBlendFunc(GL_ONE, GL_ONE)
1204 glDisable(GL_DEPTH_TEST)
1206 if self.viewMode == 'xray':
1207 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1208 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1209 glEnable(GL_STENCIL_TEST)
1211 if self.viewMode == 'overhang':
1212 if self._selectedObj == obj and self.tempMatrix is not None:
1213 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1215 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1217 if not self._scene.checkPlatform(obj):
1218 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1219 self._renderObject(obj)
1221 self._renderObject(obj, brightness)
1222 glDisable(GL_STENCIL_TEST)
1224 glEnable(GL_DEPTH_TEST)
1225 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1227 if self.viewMode == 'xray':
1230 glEnable(GL_STENCIL_TEST)
1231 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1232 glDisable(GL_DEPTH_TEST)
1233 for i in xrange(2, 15, 2): #All even values
1234 glStencilFunc(GL_EQUAL, i, 0xFF)
1235 glColor(float(i)/10, float(i)/10, float(i)/5)
1237 glVertex3f(-1000,-1000,-10)
1238 glVertex3f( 1000,-1000,-10)
1239 glVertex3f( 1000, 1000,-10)
1240 glVertex3f(-1000, 1000,-10)
1242 for i in xrange(1, 15, 2): #All odd values
1243 glStencilFunc(GL_EQUAL, i, 0xFF)
1244 glColor(float(i)/10, 0, 0)
1246 glVertex3f(-1000,-1000,-10)
1247 glVertex3f( 1000,-1000,-10)
1248 glVertex3f( 1000, 1000,-10)
1249 glVertex3f(-1000, 1000,-10)
1252 glDisable(GL_STENCIL_TEST)
1253 glEnable(GL_DEPTH_TEST)
1255 self._objectShader.unbind()
1257 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1259 if self._objectLoadShader is not None:
1260 self._objectLoadShader.bind()
1261 #Model color during load animation
1262 glColor4ub(177, 205, 54, 255)
1263 for obj in self._scene.objects():
1264 if obj._loadAnim is None:
1266 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1267 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1268 self._renderObject(obj)
1269 self._objectLoadShader.unbind()
1274 if self.viewMode != 'gcode':
1275 #Draw the object box-shadow, so you can see where it will collide with other objects.
1276 if self._selectedObj is not None:
1278 glEnable(GL_CULL_FACE)
1279 glColor4f(0,0,0,0.16)
1281 for obj in self._scene.objects():
1283 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1284 glBegin(GL_TRIANGLE_FAN)
1285 for p in obj._boundaryHull[::-1]:
1286 glVertex3f(p[0], p[1], 0)
1289 if self._scene.isOneAtATime(): #Check print sequence mode.
1291 glColor4f(0,0,0,0.06)
1292 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1293 glBegin(GL_TRIANGLE_FAN)
1294 for p in self._selectedObj._printAreaHull[::-1]:
1295 glVertex3f(p[0], p[1], 0)
1297 glBegin(GL_TRIANGLE_FAN)
1298 for p in self._selectedObj._headAreaMinHull[::-1]:
1299 glVertex3f(p[0], p[1], 0)
1303 glDisable(GL_CULL_FACE)
1305 #Draw the outline of the selected object on top of everything else except the GUI.
1306 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1307 glDisable(GL_DEPTH_TEST)
1308 glEnable(GL_CULL_FACE)
1309 glEnable(GL_STENCIL_TEST)
1311 glStencilFunc(GL_EQUAL, 0, 255)
1313 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1315 glColor4f(1,1,1,0.5)
1316 self._renderObject(self._selectedObj)
1317 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1319 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1320 glDisable(GL_STENCIL_TEST)
1321 glDisable(GL_CULL_FACE)
1322 glEnable(GL_DEPTH_TEST)
1324 if self._selectedObj is not None:
1326 pos = self.getObjectCenterPos()
1327 glTranslate(pos[0], pos[1], pos[2])
1330 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1331 glDisable(GL_DEPTH_TEST)
1334 glTranslate(0,-4,-10)
1335 glColor4ub(60,60,60,255)
1336 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1339 def _renderObject(self, obj, brightness = 0, addSink = True):
1342 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - self.getObjectSink())
1344 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1346 if self.tempMatrix is not None and obj == self._selectedObj:
1347 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1349 offset = obj.getDrawOffset()
1350 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1352 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1355 for m in obj._meshList:
1357 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1359 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1364 def _drawMachine(self):
1365 glEnable(GL_CULL_FACE)
1368 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1370 #Due to NC licensing of the stl files, temporarily removing platform mesh loading for Ultimaker and Witbox
1371 '''machine_type = profile.getMachineSetting('machine_type')
1372 if machine_type not in self._platformMesh:
1373 self._platformMesh[machine_type] = None
1378 texture_offset = [0,0,0]
1380 if machine_type == 'ultimaker2' or machine_type == 'ultimaker2extended':
1381 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1382 offset = [-9,-37,145]
1383 texture_name = 'Ultimaker2backplate.png'
1384 texture_offset = [9,150,-5]
1385 elif machine_type == 'ultimaker2go':
1386 filename = resources.getPathForMesh('ultimaker2go_platform.stl')
1387 offset = [0,-42,145]
1388 texture_offset = [0,105,-5]
1389 texture_name = 'Ultimaker2backplate.png'
1391 elif machine_type == 'ultimaker_plus':
1392 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1393 offset = [0,-37,145]
1394 texture_offset = [0,150,-5]
1395 texture_name = 'UltimakerPlusbackplate.png'
1396 elif machine_type == 'ultimaker':
1397 filename = resources.getPathForMesh('ultimaker_platform.stl')
1399 elif machine_type == 'Witbox':
1400 filename = resources.getPathForMesh('Witbox_platform.stl')
1401 offset = [0,-37,145]
1403 if filename is not None:
1404 meshes = meshLoader.loadMeshes(filename)
1406 self._platformMesh[machine_type] = meshes[0]
1407 self._platformMesh[machine_type]._drawOffset = numpy.array(offset, numpy.float32)
1408 self._platformMesh[machine_type].texture = None
1409 if texture_name is not None:
1410 self._platformMesh[machine_type].texture = openglHelpers.loadGLTexture(texture_name)
1411 self._platformMesh[machine_type].texture_offset = texture_offset
1412 self._platformMesh[machine_type].texture_scale = texture_scale
1413 if self._platformMesh[machine_type] is not None:
1414 mesh = self._platformMesh[machine_type]
1415 glColor4f(1,1,1,0.5)
1416 self._objectShader.bind()
1417 self._renderObject(mesh, False, False)
1418 self._objectShader.unbind()
1420 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1421 if mesh.texture is not None:
1422 glBindTexture(GL_TEXTURE_2D, mesh.texture)
1423 glEnable(GL_TEXTURE_2D)
1427 glTranslate(mesh.texture_offset[0], mesh.texture_offset[1], mesh.texture_offset[2])
1428 glScalef(mesh.texture_scale, mesh.texture_scale, mesh.texture_scale)
1433 glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA)
1434 glEnable(GL_ALPHA_TEST)
1435 glAlphaFunc(GL_GREATER, 0.0)
1438 glVertex3f( w, 0, h)
1440 glVertex3f(-w, 0, h)
1442 glVertex3f(-w, 0, 0)
1444 glVertex3f( w, 0, 0)
1447 glVertex3f(-w, d, h)
1449 glVertex3f( w, d, h)
1451 glVertex3f( w, d, 0)
1453 glVertex3f(-w, d, 0)
1455 glDisable(GL_TEXTURE_2D)
1456 glDisable(GL_ALPHA_TEST)
1457 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1460 # until glEnd() goes inside the else
1464 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1465 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1466 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1467 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1468 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1469 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1474 polys = profile.getMachineSizePolygons()
1475 height = profile.getMachineSettingFloat('machine_height')
1476 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1478 # Draw the sides of the build volume.
1479 for n in xrange(0, len(polys[0])):
1482 glColor4ub(210, 235, 103, 100)
1484 glColor4ub(223, 241, 145, 100)
1486 glColor4ub(223, 241, 145, 100)
1488 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1489 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1490 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1491 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1494 #Draw top of build volume.
1495 glColor4ub(183, 209, 90, 100)
1496 glBegin(GL_TRIANGLE_FAN)
1497 for p in polys[0][::-1]:
1498 glVertex3f(p[0], p[1], height)
1502 if self._platformTexture is None:
1503 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1504 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1505 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1506 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1507 #Dark checkerboard color
1508 glColor4f(1,1,1,0.7)
1509 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1510 glEnable(GL_TEXTURE_2D)
1511 glBegin(GL_TRIANGLE_FAN)
1513 glTexCoord2f(p[0]/20, p[1]/20)
1514 glVertex3f(p[0], p[1], 0)
1517 #Draw no-go zones. (clips in case of UM2)
1518 glDisable(GL_TEXTURE_2D)
1519 glColor4ub(127, 127, 127, 200)
1520 for poly in polys[1:]:
1521 glBegin(GL_TRIANGLE_FAN)
1523 glTexCoord2f(p[0]/20, p[1]/20)
1524 glVertex3f(p[0], p[1], 0)
1529 glDisable(GL_CULL_FACE)
1531 def getObjectCenterPos(self):
1532 if self._selectedObj is None:
1533 return [0.0, 0.0, 0.0]
1534 pos = self._selectedObj.getPosition()
1535 size = self._selectedObj.getSize()
1536 return [pos[0], pos[1], size[2]/2 - self.getObjectSink()]
1538 def getObjectBoundaryCircle(self):
1539 if self._selectedObj is None:
1541 return self._selectedObj.getBoundaryCircle()
1543 def getObjectSize(self):
1544 if self._selectedObj is None:
1545 return [0.0, 0.0, 0.0]
1546 return self._selectedObj.getSize()
1548 def getObjectMatrix(self):
1549 if self._selectedObj is None:
1550 return numpy.matrix(numpy.identity(3))
1551 return self._selectedObj.getMatrix()
1553 #TODO: Remove this or put it in a seperate file
1554 class shaderEditor(wx.Frame):
1555 def __init__(self, parent, callback, v, f):
1556 super(shaderEditor, self).__init__(parent, title=_("Shader editor"), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1557 self._callback = callback
1558 s = wx.BoxSizer(wx.VERTICAL)
1560 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1561 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1562 s.Add(self._vertex, 1, flag=wx.EXPAND)
1563 s.Add(self._fragment, 1, flag=wx.EXPAND)
1565 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1566 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1568 self.SetPosition(self.GetParent().GetPosition())
1569 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1572 def OnText(self, e):
1573 self._callback(self._vertex.GetValue(), self._fragment.GetValue())