1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
11 import cStringIO as StringIO
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
18 from Cura.gui import printWindow
19 from Cura.util import profile
20 from Cura.util import meshLoader
21 from Cura.util import objectScene
22 from Cura.util import resources
23 from Cura.util import sliceEngine
24 from Cura.util import pluginInfo
25 from Cura.util import removableStorage
26 from Cura.util import explorer
27 from Cura.util.printerConnection import printerConnectionManager
28 from Cura.gui.util import previewTools
29 from Cura.gui.util import openglHelpers
30 from Cura.gui.util import openglGui
31 from Cura.gui.util import engineResultView
32 from Cura.gui.tools import youmagineGui
33 from Cura.gui.tools import imageToMesh
35 class SceneView(openglGui.glGuiPanel):
36 def __init__(self, parent):
37 super(SceneView, self).__init__(parent)
42 self._scene = objectScene.Scene(self)
43 self._objectShader = None
44 self._objectLoadShader = None
46 self._selectedObj = None
47 self._objColors = [None,None,None,None]
50 self._mouseState = None
51 self._viewTarget = numpy.array([0,0,0], numpy.float32)
54 self._lastObjectSink = None
55 self._platformMesh = {}
56 self._platformTexture = None
57 self._isSimpleMode = True
58 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
61 self._modelMatrix = None
62 self._projMatrix = None
63 self.tempMatrix = None
65 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
66 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
67 self.printButton.setDisabled(True)
70 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
71 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
72 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
74 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
75 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
77 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
78 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
80 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
81 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
82 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
84 self.rotateToolButton.setExpandArrow(True)
85 self.scaleToolButton.setExpandArrow(True)
86 self.mirrorToolButton.setExpandArrow(True)
88 self.scaleForm = openglGui.glFrame(self, (2, -2))
89 openglGui.glGuiLayoutGrid(self.scaleForm)
90 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
91 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
92 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
93 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
94 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
95 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
96 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
97 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
98 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
99 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
100 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
101 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
102 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
103 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
105 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
107 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
108 self.youMagineButton.setDisabled(True)
110 self.notification = openglGui.glNotification(self, (0, 0))
112 self._engine = sliceEngine.Engine(self._updateEngineProgress)
113 self._engineResultView = engineResultView.engineResultView(self)
114 self._sceneUpdateTimer = wx.Timer(self)
115 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
116 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
117 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
121 self.updateToolButtons()
122 self.updateProfileToControls()
126 def loadGCodeFile(self, filename):
127 self.OnDeleteAll(None)
128 #Cheat the engine results to load a GCode file into it.
129 self._engine._result = sliceEngine.EngineResult()
130 with open(filename, "r") as f:
131 self._engine._result.setGCode(f.read())
132 self._engine._result.setFinished(True)
133 self._engineResultView.setResult(self._engine._result)
134 self.printButton.setBottomText('')
135 self.viewSelection.setValue(4)
136 self.printButton.setDisabled(False)
137 self.youMagineButton.setDisabled(True)
140 def loadSceneFiles(self, filenames):
141 self.youMagineButton.setDisabled(False)
142 #if self.viewSelection.getValue() == 4:
143 # self.viewSelection.setValue(0)
144 # self.OnViewChange()
145 self.loadScene(filenames)
147 def loadFiles(self, filenames):
148 mainWindow = self.GetParent().GetParent().GetParent()
149 # only one GCODE file can be active
150 # so if single gcode file, process this
151 # otherwise ignore all gcode files
153 if len(filenames) == 1:
154 filename = filenames[0]
155 ext = os.path.splitext(filename)[1].lower()
156 if ext == '.g' or ext == '.gcode':
157 gcodeFilename = filename
158 mainWindow.addToModelMRU(filename)
159 if gcodeFilename is not None:
160 self.loadGCodeFile(gcodeFilename)
162 # process directories and special file types
163 # and keep scene files for later processing
165 ignored_types = dict()
166 # use file list as queue
167 # pop first entry for processing and append new files at end
169 filename = filenames.pop(0)
170 if os.path.isdir(filename):
171 # directory: queue all included files and directories
172 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
174 ext = os.path.splitext(filename)[1].lower()
176 profile.loadProfile(filename)
177 mainWindow.addToProfileMRU(filename)
178 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
179 scene_filenames.append(filename)
180 mainWindow.addToModelMRU(filename)
182 ignored_types[ext] = 1
184 ignored_types = ignored_types.keys()
186 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
187 mainWindow.updateProfileToAllControls()
188 # now process all the scene files
190 self.loadSceneFiles(scene_filenames)
191 self._selectObject(None)
193 newZoom = numpy.max(self._machineSize)
194 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
195 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
197 def reloadScene(self, e):
198 # Copy the list before DeleteAll clears it
200 pms_transforms = [] #position, rotation matrix, scale
201 for obj in self._scene.objects():
202 fileList.append(obj.getOriginFilename())
203 pms_transforms.append((obj.getPosition(), obj.getMatrix(), obj.getScale()))
205 self.OnDeleteAll(None)
206 self.loadScene(fileList, pms_transforms)
208 def OnResetPositions(self, e):
210 self._scene.arrangeAll(True)
211 self._scene.centerAll()
214 def OnResetTransformations(self, e):
215 for obj in self._scene.objects():
219 self._scene.arrangeAll()
220 self._scene.centerAll()
222 def showLoadModel(self, button = 1):
224 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)
226 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
227 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
228 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
229 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
230 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
231 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
232 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
233 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
235 dlg.SetWildcard(wildcardFilter)
236 if dlg.ShowModal() != wx.ID_OK:
239 filenames = dlg.GetPaths()
241 if len(filenames) < 1:
243 profile.putPreference('lastFile', filenames[0])
244 self.loadFiles(filenames)
246 def showSaveModel(self):
247 if len(self._scene.objects()) < 1:
249 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
250 fileExtensions = meshLoader.saveSupportedExtensions()
251 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
252 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
253 dlg.SetWildcard(wildcardFilter)
254 if dlg.ShowModal() != wx.ID_OK:
257 filename = dlg.GetPath()
259 meshLoader.saveMeshes(filename, self._scene.objects())
261 def OnPrintButton(self, button):
263 connectionGroup = self._printerConnectionManager.getAvailableGroup()
264 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
265 drives = removableStorage.getPossibleSDcardDrives()
267 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))
268 if dlg.ShowModal() != wx.ID_OK:
271 drive = drives[dlg.GetSelection()]
275 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
277 #check if the file is part of the root folder. If so, create folders on sd card to get the same folder hierarchy.
278 repDir = profile.getPreference("sdcard_rootfolder")
280 if os.path.exists(repDir) and os.path.isdir(repDir):
281 repDir = os.path.abspath(repDir)
282 originFilename = os.path.abspath( self._scene._objectList[0].getOriginFilename() )
283 if os.path.dirname(originFilename).startswith(repDir):
284 new_filename = os.path.splitext(originFilename[len(repDir):])[0] + profile.getGCodeExtension()
285 sdPath = os.path.dirname(os.path.join(drive[1], new_filename))
286 if not os.path.exists(sdPath):
287 print "Creating replication directory:", sdPath
289 filename = new_filename
293 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
294 elif connectionGroup is not None:
295 connections = connectionGroup.getAvailableConnections()
296 if len(connections) < 2:
297 connection = connections[0]
299 dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
300 if dlg.ShowModal() != wx.ID_OK:
303 connection = connections[dlg.GetSelection()]
305 self._openPrintWindowForConnection(connection)
310 connections = self._printerConnectionManager.getAvailableConnections()
311 menu.connectionMap = {}
312 for connection in connections:
313 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
314 menu.connectionMap[i.GetId()] = connection
315 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
316 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
317 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
321 def _openPrintWindowForConnection(self, connection):
322 if connection.window is None or not connection.window:
323 connection.window = None
324 windowType = profile.getPreference('printing_window')
325 for p in pluginInfo.getPluginList('printwindow'):
326 if p.getName() == windowType:
327 connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
329 if connection.window is None:
330 connection.window = printWindow.printWindowBasic(self, connection)
331 connection.window.Show()
332 connection.window.Raise()
333 if not connection.loadGCodeData(self._engine.getResult().getGCode()):
334 if connection.isPrinting():
335 self.notification.message("Cannot start print, because other print still running.")
337 self.notification.message("Failed to start print...")
339 def showSaveGCode(self):
340 if len(self._scene._objectList) < 1:
342 if not self._engine.getResult().isFinished():
344 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
345 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
346 dlg.SetFilename(filename)
347 dlg.SetWildcard('Toolpath (*%s)|*%s;*%s' % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
348 if dlg.ShowModal() != wx.ID_OK:
351 filename = dlg.GetPath()
354 threading.Thread(target=self._saveGCode,args=(filename,)).start()
356 def _saveGCode(self, targetFilename, ejectDrive = False):
357 gcode = self._engine.getResult().getGCode()
359 size = float(len(gcode))
361 with open(targetFilename, 'wb') as fdst:
363 buf = gcode.read(16*1024)
368 self.printButton.setProgressBar(read_pos / size)
371 import sys, traceback
372 traceback.print_exc()
373 self.notification.message("Failed to save")
376 self.notification.message("Saved as %s" % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
377 elif explorer.hasExplorer():
378 self.notification.message("Saved as %s" % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, 'Open folder')
380 self.notification.message("Saved as %s" % (targetFilename))
381 self.printButton.setProgressBar(None)
382 self._engine.getResult().submitInfoOnline()
384 def _doEjectSD(self, drive):
385 if removableStorage.ejectDrive(drive):
386 self.notification.message('You can now eject the card.')
388 self.notification.message('Safe remove failed...')
390 def _showEngineLog(self):
391 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)
395 def OnToolSelect(self, button):
396 if self.rotateToolButton.getSelected():
397 self.tool = previewTools.toolRotate(self)
398 elif self.scaleToolButton.getSelected():
399 self.tool = previewTools.toolScale(self)
400 elif self.mirrorToolButton.getSelected():
401 self.tool = previewTools.toolNone(self)
403 self.tool = previewTools.toolNone(self)
404 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
405 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
406 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
407 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
408 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
409 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
410 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
411 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
413 def updateToolButtons(self):
414 if self._selectedObj is None:
418 self.rotateToolButton.setHidden(hidden)
419 self.scaleToolButton.setHidden(hidden)
420 self.mirrorToolButton.setHidden(hidden)
422 self.rotateToolButton.setSelected(False)
423 self.scaleToolButton.setSelected(False)
424 self.mirrorToolButton.setSelected(False)
427 def OnViewChange(self):
428 if self.viewSelection.getValue() == 4:
429 self.viewMode = 'gcode'
430 self.tool = previewTools.toolNone(self)
431 elif self.viewSelection.getValue() == 1:
432 self.viewMode = 'overhang'
433 elif self.viewSelection.getValue() == 2:
434 self.viewMode = 'transparent'
435 elif self.viewSelection.getValue() == 3:
436 self.viewMode = 'xray'
438 self.viewMode = 'normal'
439 self._engineResultView.setEnabled(self.viewMode == 'gcode')
442 def OnRotateReset(self, button):
443 if self._selectedObj is None:
445 self._selectedObj.resetRotation()
446 self._scene.pushFree(self._selectedObj)
447 self._selectObject(self._selectedObj)
450 def OnLayFlat(self, button):
451 if self._selectedObj is None:
453 self._selectedObj.layFlat()
454 self._scene.pushFree(self._selectedObj)
455 self._selectObject(self._selectedObj)
458 def OnScaleReset(self, button):
459 if self._selectedObj is None:
461 self._selectedObj.resetScale()
462 self._selectObject(self._selectedObj)
463 self.updateProfileToControls()
466 def OnScaleMax(self, button):
467 if self._selectedObj is None:
469 machine = profile.getMachineSetting('machine_type')
470 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
471 self._scene.pushFree(self._selectedObj)
473 if machine == "ultimaker2":
474 #This is bad and Jaime should feel bad!
475 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
476 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
477 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
478 self._scene.pushFree(self._selectedObj)
480 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
481 self._scene.pushFree(self._selectedObj)
482 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
483 self._scene.pushFree(self._selectedObj)
484 self._selectObject(self._selectedObj)
485 self.updateProfileToControls()
488 def OnMirror(self, axis):
489 if self._selectedObj is None:
491 self._selectedObj.mirror(axis)
494 def OnScaleEntry(self, value, axis):
495 if self._selectedObj is None:
501 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
502 self.updateProfileToControls()
503 self._scene.pushFree(self._selectedObj)
504 self._selectObject(self._selectedObj)
507 def OnScaleEntryMM(self, value, axis):
508 if self._selectedObj is None:
514 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
515 self.updateProfileToControls()
516 self._scene.pushFree(self._selectedObj)
517 self._selectObject(self._selectedObj)
520 def OnDeleteAll(self, e):
521 while len(self._scene.objects()) > 0:
522 self._deleteObject(self._scene.objects()[0])
523 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
524 self._engineResultView.setResult(None)
526 def OnMultiply(self, e):
527 if self._focusObj is None:
530 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
531 if dlg.ShowModal() != wx.ID_OK:
537 # 0:unrequested arrange all. Objects should not move.
538 # 1:requested arrange all but refused.
539 # 2:arrange all and center from now on.
540 requestedArrangeAll = 0
546 self._scene.add(newObj)
547 if requestedArrangeAll == 2:
548 self._scene.centerAll()
550 if not self._scene.checkPlatform(newObj):
551 if requestedArrangeAll == 0:
552 requestedArrangeAll = 1
553 dlg = wx.MessageDialog(self, _("Cannot fit all the requested duplicates. Do you want to try and reset object positions?"), _("Reset Positions"), wx.YES_NO)
555 if dlg.ShowModal() == wx.ID_YES:
557 requestedArrangeAll = 2
558 self._scene.remove(newObj)
559 self.OnResetPositions(None)
569 self.notification.message("Could not create more than %d items" % (n - 1))
570 self._scene.remove(newObj)
571 if requestedArrangeAll == 2:
572 self._scene.centerAll()
576 def OnSplitObject(self, e):
577 if self._focusObj is None:
579 self._scene.remove(self._focusObj)
580 for obj in self._focusObj.split(self._splitCallback):
581 if numpy.max(obj.getSize()) > 2.0:
583 self._scene.centerAll()
584 self._selectObject(None)
587 def OnCenter(self, e):
588 if self._focusObj is None:
590 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
591 self._scene.pushFree(self._selectedObj)
592 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
593 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
596 def _splitCallback(self, progress):
599 def OnMergeObjects(self, e):
600 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
601 if len(self._scene.objects()) == 2:
602 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
605 self._scene.merge(self._selectedObj, self._focusObj)
608 def sceneUpdated(self):
610 objectSink = profile.getProfileSettingFloat("object_sink")
611 if self._lastObjectSink != objectSink:
612 self._lastObjectSink = objectSink
613 self._scene.updateHeadSize()
615 wx.CallAfter(self._sceneUpdateTimer.Start, 500, True)
616 self._engine.abortEngine()
617 self._scene.updateSizeOffsets()
620 def _onRunEngine(self, e):
621 if self._isSimpleMode:
622 self._engine.runEngine(self._scene, self.GetTopLevelParent().simpleSettingsPanel.getSettingOverrides())
624 self._engine.runEngine(self._scene)
626 def _updateEngineProgress(self, progressValue):
627 result = self._engine.getResult()
628 finished = result is not None and result.isFinished()
630 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
632 self.printButton.setDisabled(not finished)
633 if progressValue >= 0.0:
634 self.printButton.setProgressBar(progressValue)
636 self.printButton.setProgressBar(None)
637 self._engineResultView.setResult(result)
639 self.printButton.setProgressBar(None)
640 text = '%s' % (result.getPrintTime())
641 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
642 amount = result.getFilamentAmount(e)
645 text += '\n%s' % (amount)
646 cost = result.getFilamentCost(e)
648 text += '\n%s' % (cost)
649 self.printButton.setBottomText(text)
651 self.printButton.setBottomText('')
654 def loadScene(self, fileList, pms_transforms=None):
656 for filename in fileList:
659 ext = os.path.splitext(filename)[1].lower()
660 if ext in imageToMesh.supportedExtensions():
661 imageToMesh.convertImageDialog(self, filename).Show()
664 objList = meshLoader.loadMeshes(filename)
666 traceback.print_exc()
669 if self._objectLoadShader is not None:
670 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
674 if pms_transforms is not None and len(pms_transforms) == len(fileList):
675 obj.setPosition(pms_transforms[objIndex][0])
676 obj.applyMatrix(pms_transforms[objIndex][1])
677 obj.setScale(pms_transforms[objIndex][2][0], 0, False)
678 obj.setScale(pms_transforms[objIndex][2][1], 1, False)
679 obj.setScale(pms_transforms[objIndex][2][2], 2, False)
681 if not self._scene.checkPlatform(obj):
682 self._scene.centerAll()
683 self._selectObject(obj)
684 if obj.getScale()[0] < 1.0:
685 self.notification.message("Warning: Object scaled down.")
688 def _deleteObject(self, obj):
689 if obj == self._selectedObj:
690 self._selectObject(None)
691 if obj == self._focusObj:
692 self._focusObj = None
693 self._scene.remove(obj)
694 for m in obj._meshList:
695 if m.vbo is not None and m.vbo.decRef():
696 self.glReleaseList.append(m.vbo)
697 if len(self._scene.objects()) == 0:
698 self._engineResultView.setResult(None)
703 def _selectObject(self, obj, zoom = True):
704 if obj != self._selectedObj:
705 self._selectedObj = obj
706 self.updateModelSettingsToControls()
707 self.updateToolButtons()
708 if zoom and obj is not None:
709 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
710 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
711 newZoom = obj.getBoundaryCircle() * 6
712 if newZoom > numpy.max(self._machineSize) * 3:
713 newZoom = numpy.max(self._machineSize) * 3
714 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
716 def updateProfileToControls(self):
717 oldSimpleMode = self._isSimpleMode
718 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
719 if self._isSimpleMode != oldSimpleMode:
720 self._scene.arrangeAll()
722 self._scene.updateSizeOffsets(True)
723 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
724 self._objColors[0] = profile.getPreferenceColour('model_colour')
725 self._objColors[1] = profile.getPreferenceColour('model_colour2')
726 self._objColors[2] = profile.getPreferenceColour('model_colour3')
727 self._objColors[3] = profile.getPreferenceColour('model_colour4')
728 self._scene.updateMachineDimensions()
729 if self._zoom > numpy.max(self._machineSize) * 3:
730 self._animZoom = openglGui.animation(self, self._zoom, numpy.max(self._machineSize) * 3, 0.5)
731 self.updateModelSettingsToControls()
733 def updateModelSettingsToControls(self):
734 if self._selectedObj is not None:
735 scale = self._selectedObj.getScale()
736 size = self._selectedObj.getSize()
737 self.scaleXctrl.setValue(round(scale[0], 2))
738 self.scaleYctrl.setValue(round(scale[1], 2))
739 self.scaleZctrl.setValue(round(scale[2], 2))
740 self.scaleXmmctrl.setValue(round(size[0], 2))
741 self.scaleYmmctrl.setValue(round(size[1], 2))
742 self.scaleZmmctrl.setValue(round(size[2], 2))
744 def OnKeyChar(self, keyCode):
745 if self._engineResultView.OnKeyChar(keyCode):
747 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and sys.platform.startswith("darwin")):
748 if self._selectedObj is not None:
749 self._deleteObject(self._selectedObj)
751 if keyCode == wx.WXK_UP:
752 if wx.GetKeyState(wx.WXK_SHIFT):
759 elif keyCode == wx.WXK_DOWN:
760 if wx.GetKeyState(wx.WXK_SHIFT):
762 if self._zoom > numpy.max(self._machineSize) * 3:
763 self._zoom = numpy.max(self._machineSize) * 3
767 elif keyCode == wx.WXK_LEFT:
770 elif keyCode == wx.WXK_RIGHT:
773 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
778 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
780 if self._zoom > numpy.max(self._machineSize) * 3:
781 self._zoom = numpy.max(self._machineSize) * 3
783 elif keyCode == wx.WXK_HOME:
787 elif keyCode == wx.WXK_PAGEUP:
791 elif keyCode == wx.WXK_PAGEDOWN:
795 elif keyCode == wx.WXK_END:
800 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
801 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
802 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
803 from collections import defaultdict
804 from gc import get_objects
805 self._beforeLeakTest = defaultdict(int)
806 for i in get_objects():
807 self._beforeLeakTest[type(i)] += 1
808 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
809 from collections import defaultdict
810 from gc import get_objects
811 self._afterLeakTest = defaultdict(int)
812 for i in get_objects():
813 self._afterLeakTest[type(i)] += 1
814 for k in self._afterLeakTest:
815 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
816 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
818 def ShaderUpdate(self, v, f):
819 s = openglHelpers.GLShader(v, f)
821 self._objectLoadShader.release()
822 self._objectLoadShader = s
823 for obj in self._scene.objects():
824 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
827 def OnMouseDown(self,e):
828 self._mouseX = e.GetX()
829 self._mouseY = e.GetY()
830 self._mouseClick3DPos = self._mouse3Dpos
831 self._mouseClickFocus = self._focusObj
833 self._mouseState = 'doubleClick'
835 if self._mouseState == 'dragObject' and self._selectedObj is not None:
836 self._scene.pushFree(self._selectedObj)
838 self._mouseState = 'dragOrClick'
839 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
840 p0 -= self.getObjectCenterPos() - self._viewTarget
841 p1 -= self.getObjectCenterPos() - self._viewTarget
842 if self.tool.OnDragStart(p0, p1):
843 self._mouseState = 'tool'
844 if self._mouseState == 'dragOrClick':
845 if e.GetButton() == 1:
846 if self._focusObj is not None:
847 self._selectObject(self._focusObj, False)
850 def OnMouseUp(self, e):
851 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
853 if self._mouseState == 'dragOrClick':
854 if e.GetButton() == 1:
855 self._selectObject(self._focusObj)
856 if e.GetButton() == 3:
858 if self._focusObj is not None:
860 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
861 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
862 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
863 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
865 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:
866 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
867 if len(self._scene.objects()) > 0:
868 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
869 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
870 self.Bind(wx.EVT_MENU, self.OnResetPositions, menu.Append(-1, _("Reset all objects positions")))
871 self.Bind(wx.EVT_MENU, self.OnResetTransformations, menu.Append(-1, _("Reset all objects transformations")))
873 if menu.MenuItemCount > 0:
876 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
877 self._scene.pushFree(self._selectedObj)
879 elif self._mouseState == 'tool':
880 if self.tempMatrix is not None and self._selectedObj is not None:
881 self._selectedObj.applyMatrix(self.tempMatrix)
882 self._scene.pushFree(self._selectedObj)
883 self._selectObject(self._selectedObj)
884 self.tempMatrix = None
885 self.tool.OnDragEnd()
887 self._mouseState = None
889 def OnMouseMotion(self,e):
890 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
891 p0 -= self.getObjectCenterPos() - self._viewTarget
892 p1 -= self.getObjectCenterPos() - self._viewTarget
894 if e.Dragging() and self._mouseState is not None:
895 if self._mouseState == 'tool':
896 self.tool.OnDrag(p0, p1)
897 elif not e.LeftIsDown() and e.RightIsDown():
898 self._mouseState = 'drag'
899 if wx.GetKeyState(wx.WXK_SHIFT):
900 a = math.cos(math.radians(self._yaw)) / 3.0
901 b = math.sin(math.radians(self._yaw)) / 3.0
902 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
903 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
904 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
905 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
907 self._yaw += e.GetX() - self._mouseX
908 self._pitch -= e.GetY() - self._mouseY
909 if self._pitch > 170:
913 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
914 self._mouseState = 'drag'
915 self._zoom += e.GetY() - self._mouseY
918 if self._zoom > numpy.max(self._machineSize) * 3:
919 self._zoom = numpy.max(self._machineSize) * 3
920 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
921 self._mouseState = 'dragObject'
922 z = max(0, self._mouseClick3DPos[2])
923 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
924 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
929 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
930 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
931 diff = cursorZ1 - cursorZ0
932 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
933 if not e.Dragging() or self._mouseState != 'tool':
934 self.tool.OnMouseMove(p0, p1)
936 self._mouseX = e.GetX()
937 self._mouseY = e.GetY()
939 def OnMouseWheel(self, e):
940 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
941 delta = max(min(delta,4),-4)
942 self._zoom *= 1.0 - delta / 10.0
945 if self._zoom > numpy.max(self._machineSize) * 3:
946 self._zoom = numpy.max(self._machineSize) * 3
949 def OnMouseLeave(self, e):
953 def getMouseRay(self, x, y):
954 if self._viewport is None:
955 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
956 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
957 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
958 p0 -= self._viewTarget
959 p1 -= self._viewTarget
962 def _init3DView(self):
963 # set viewing projection
964 size = self.GetSize()
965 glViewport(0, 0, size.GetWidth(), size.GetHeight())
968 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
970 glDisable(GL_RESCALE_NORMAL)
971 glDisable(GL_LIGHTING)
973 glEnable(GL_DEPTH_TEST)
974 glDisable(GL_CULL_FACE)
976 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
978 glClearColor(0.8, 0.8, 0.8, 1.0)
982 glMatrixMode(GL_PROJECTION)
984 aspect = float(size.GetWidth()) / float(size.GetHeight())
985 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
987 glMatrixMode(GL_MODELVIEW)
989 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
992 connectionGroup = self._printerConnectionManager.getAvailableGroup()
993 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
994 self.printButton._imageID = 2
995 self.printButton._tooltip = _("Toolpath to SD")
996 elif connectionGroup is not None:
997 self.printButton._imageID = connectionGroup.getIconID()
998 self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
1000 self.printButton._imageID = 3
1001 self.printButton._tooltip = _("Save toolpath")
1003 if self._animView is not None:
1004 self._viewTarget = self._animView.getPosition()
1005 if self._animView.isDone():
1006 self._animView = None
1007 if self._animZoom is not None:
1008 self._zoom = self._animZoom.getPosition()
1009 if self._animZoom.isDone():
1010 self._animZoom = None
1011 if self._objectShader is None: #TODO: add loading shaders from file(s)
1012 if openglHelpers.hasShaderSupport():
1013 self._objectShader = openglHelpers.GLShader("""
1014 varying float light_amount;
1018 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
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 varying float light_amount;
1029 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1032 self._objectOverhangShader = openglHelpers.GLShader("""
1033 uniform float cosAngle;
1034 uniform mat3 rotMatrix;
1035 varying float light_amount;
1039 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1040 gl_FrontColor = gl_Color;
1042 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1043 light_amount += 0.2;
1044 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
1046 light_amount = -10.0;
1050 varying float light_amount;
1054 if (light_amount == -10.0)
1056 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1058 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1062 self._objectLoadShader = openglHelpers.GLShader("""
1063 uniform float intensity;
1064 uniform float scale;
1065 varying float light_amount;
1069 vec4 tmp = gl_Vertex;
1070 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1071 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1072 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1073 gl_FrontColor = gl_Color;
1075 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1076 light_amount += 0.2;
1079 uniform float intensity;
1080 varying float light_amount;
1084 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1087 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
1088 self._objectShader = openglHelpers.GLFakeShader()
1089 self._objectOverhangShader = openglHelpers.GLFakeShader()
1090 self._objectLoadShader = None
1092 glTranslate(0,0,-self._zoom)
1093 glRotate(-self._pitch, 1,0,0)
1094 glRotate(self._yaw, 0,0,1)
1095 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1097 self._viewport = glGetIntegerv(GL_VIEWPORT)
1098 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1099 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1101 glClearColor(1,1,1,1)
1102 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1104 if self.viewMode != 'gcode':
1105 for n in xrange(0, len(self._scene.objects())):
1106 obj = self._scene.objects()[n]
1107 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1108 self._renderObject(obj)
1110 if self._mouseX > -1: # mouse has not passed over the opengl window.
1112 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1113 if n < len(self._scene.objects()):
1114 self._focusObj = self._scene.objects()[n]
1116 self._focusObj = None
1117 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1118 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1119 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1120 self._mouse3Dpos -= self._viewTarget
1123 glTranslate(0,0,-self._zoom)
1124 glRotate(-self._pitch, 1,0,0)
1125 glRotate(self._yaw, 0,0,1)
1126 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1128 self._objectShader.unbind()
1129 self._engineResultView.OnDraw()
1130 if self.viewMode != 'gcode':
1131 glStencilFunc(GL_ALWAYS, 1, 1)
1132 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1134 if self.viewMode == 'overhang':
1135 self._objectOverhangShader.bind()
1136 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1138 self._objectShader.bind()
1139 for obj in self._scene.objects():
1140 if obj._loadAnim is not None:
1141 if obj._loadAnim.isDone():
1142 obj._loadAnim = None
1146 if self._focusObj == obj:
1148 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1151 if self._selectedObj == obj or self._selectedObj is None:
1152 #If we want transparent, then first render a solid black model to remove the printer size lines.
1153 if self.viewMode == 'transparent':
1154 glColor4f(0, 0, 0, 0)
1155 self._renderObject(obj)
1157 glBlendFunc(GL_ONE, GL_ONE)
1158 glDisable(GL_DEPTH_TEST)
1160 if self.viewMode == 'xray':
1161 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1162 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1163 glEnable(GL_STENCIL_TEST)
1165 if self.viewMode == 'overhang':
1166 if self._selectedObj == obj and self.tempMatrix is not None:
1167 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1169 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1171 if not self._scene.checkPlatform(obj):
1172 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1173 self._renderObject(obj)
1175 self._renderObject(obj, brightness)
1176 glDisable(GL_STENCIL_TEST)
1178 glEnable(GL_DEPTH_TEST)
1179 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1181 if self.viewMode == 'xray':
1184 glEnable(GL_STENCIL_TEST)
1185 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1186 glDisable(GL_DEPTH_TEST)
1187 for i in xrange(2, 15, 2): #All even values
1188 glStencilFunc(GL_EQUAL, i, 0xFF)
1189 glColor(float(i)/10, float(i)/10, float(i)/5)
1191 glVertex3f(-1000,-1000,-10)
1192 glVertex3f( 1000,-1000,-10)
1193 glVertex3f( 1000, 1000,-10)
1194 glVertex3f(-1000, 1000,-10)
1196 for i in xrange(1, 15, 2): #All odd values
1197 glStencilFunc(GL_EQUAL, i, 0xFF)
1198 glColor(float(i)/10, 0, 0)
1200 glVertex3f(-1000,-1000,-10)
1201 glVertex3f( 1000,-1000,-10)
1202 glVertex3f( 1000, 1000,-10)
1203 glVertex3f(-1000, 1000,-10)
1206 glDisable(GL_STENCIL_TEST)
1207 glEnable(GL_DEPTH_TEST)
1209 self._objectShader.unbind()
1211 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1213 if self._objectLoadShader is not None:
1214 self._objectLoadShader.bind()
1215 glColor4f(0.2, 0.6, 1.0, 1.0)
1216 for obj in self._scene.objects():
1217 if obj._loadAnim is None:
1219 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1220 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1221 self._renderObject(obj)
1222 self._objectLoadShader.unbind()
1227 if self.viewMode != 'gcode':
1228 #Draw the object box-shadow, so you can see where it will collide with other objects.
1229 if self._selectedObj is not None:
1231 glEnable(GL_CULL_FACE)
1232 glColor4f(0,0,0,0.16)
1234 for obj in self._scene.objects():
1236 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1237 glBegin(GL_TRIANGLE_FAN)
1238 for p in obj._boundaryHull[::-1]:
1239 glVertex3f(p[0], p[1], 0)
1242 if self._scene.isOneAtATime(): #Check print sequence mode.
1244 glColor4f(0,0,0,0.06)
1245 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1246 glBegin(GL_TRIANGLE_FAN)
1247 for p in self._selectedObj._printAreaHull[::-1]:
1248 glVertex3f(p[0], p[1], 0)
1250 glBegin(GL_TRIANGLE_FAN)
1251 for p in self._selectedObj._headAreaMinHull[::-1]:
1252 glVertex3f(p[0], p[1], 0)
1256 glDisable(GL_CULL_FACE)
1258 #Draw the outline of the selected object on top of everything else except the GUI.
1259 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1260 glDisable(GL_DEPTH_TEST)
1261 glEnable(GL_CULL_FACE)
1262 glEnable(GL_STENCIL_TEST)
1264 glStencilFunc(GL_EQUAL, 0, 255)
1266 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1268 glColor4f(1,1,1,0.5)
1269 self._renderObject(self._selectedObj)
1270 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1272 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1273 glDisable(GL_STENCIL_TEST)
1274 glDisable(GL_CULL_FACE)
1275 glEnable(GL_DEPTH_TEST)
1277 if self._selectedObj is not None:
1279 pos = self.getObjectCenterPos()
1280 glTranslate(pos[0], pos[1], pos[2])
1283 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1284 glDisable(GL_DEPTH_TEST)
1287 glTranslate(0,-4,-10)
1288 glColor4ub(60,60,60,255)
1289 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1292 def _renderObject(self, obj, brightness = 0, addSink = True):
1295 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1297 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1299 if self.tempMatrix is not None and obj == self._selectedObj:
1300 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1302 offset = obj.getDrawOffset()
1303 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1305 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1308 for m in obj._meshList:
1310 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1312 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1317 def _drawMachine(self):
1318 glEnable(GL_CULL_FACE)
1321 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1323 machine_type = profile.getMachineSetting('machine_type')
1324 if machine_type not in self._platformMesh:
1325 self._platformMesh[machine_type] = None
1330 texture_offset = [0,0,0]
1332 if machine_type == 'ultimaker2' or machine_type == 'ultimaker2extended':
1333 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1334 offset = [-9,-37,145]
1335 texture_name = 'Ultimaker2backplate.png'
1336 texture_offset = [9,150,-5]
1337 elif machine_type == 'ultimaker2go':
1338 filename = resources.getPathForMesh('ultimaker2go_platform.stl')
1339 offset = [0,-42,145]
1340 texture_offset = [0,105,-5]
1341 texture_name = 'Ultimaker2backplate.png'
1343 elif machine_type == 'ultimaker_plus':
1344 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1345 offset = [0,-37,145]
1346 texture_offset = [0,150,-5]
1347 texture_name = 'UltimakerPlusbackplate.png'
1348 elif machine_type == 'ultimaker':
1349 filename = resources.getPathForMesh('ultimaker_platform.stl')
1351 elif machine_type == 'Witbox':
1352 filename = resources.getPathForMesh('Witbox_platform.stl')
1353 offset = [0,-37,145]
1355 if filename is not None:
1356 meshes = meshLoader.loadMeshes(filename)
1358 self._platformMesh[machine_type] = meshes[0]
1359 self._platformMesh[machine_type]._drawOffset = numpy.array(offset, numpy.float32)
1360 self._platformMesh[machine_type].texture = None
1361 if texture_name is not None:
1362 self._platformMesh[machine_type].texture = openglHelpers.loadGLTexture(texture_name)
1363 self._platformMesh[machine_type].texture_offset = texture_offset
1364 self._platformMesh[machine_type].texture_scale = texture_scale
1365 if self._platformMesh[machine_type] is not None:
1366 mesh = self._platformMesh[machine_type]
1367 glColor4f(1,1,1,0.5)
1368 self._objectShader.bind()
1369 self._renderObject(mesh, False, False)
1370 self._objectShader.unbind()
1372 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1373 if mesh.texture is not None:
1374 glBindTexture(GL_TEXTURE_2D, mesh.texture)
1375 glEnable(GL_TEXTURE_2D)
1379 glTranslate(mesh.texture_offset[0], mesh.texture_offset[1], mesh.texture_offset[2])
1380 glScalef(mesh.texture_scale, mesh.texture_scale, mesh.texture_scale)
1385 glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA)
1386 glEnable(GL_ALPHA_TEST)
1387 glAlphaFunc(GL_GREATER, 0.0)
1390 glVertex3f( w, 0, h)
1392 glVertex3f(-w, 0, h)
1394 glVertex3f(-w, 0, 0)
1396 glVertex3f( w, 0, 0)
1399 glVertex3f(-w, d, h)
1401 glVertex3f( w, d, h)
1403 glVertex3f( w, d, 0)
1405 glVertex3f(-w, d, 0)
1407 glDisable(GL_TEXTURE_2D)
1408 glDisable(GL_ALPHA_TEST)
1409 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1415 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1416 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1417 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1418 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1419 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1420 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1425 polys = profile.getMachineSizePolygons()
1426 height = profile.getMachineSettingFloat('machine_height')
1427 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1429 # Draw the sides of the build volume.
1430 for n in xrange(0, len(polys[0])):
1433 glColor4ub(5, 171, 231, 96)
1435 glColor4ub(5, 171, 231, 64)
1437 glColor4ub(5, 171, 231, 96)
1439 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1440 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1441 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1442 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1445 #Draw top of build volume.
1446 glColor4ub(5, 171, 231, 128)
1447 glBegin(GL_TRIANGLE_FAN)
1448 for p in polys[0][::-1]:
1449 glVertex3f(p[0], p[1], height)
1453 if self._platformTexture is None:
1454 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1455 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1456 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1457 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1458 glColor4f(1,1,1,0.5)
1459 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1460 glEnable(GL_TEXTURE_2D)
1461 glBegin(GL_TRIANGLE_FAN)
1463 glTexCoord2f(p[0]/20, p[1]/20)
1464 glVertex3f(p[0], p[1], 0)
1467 #Draw no-go zones. (clips in case of UM2)
1468 glDisable(GL_TEXTURE_2D)
1469 glColor4ub(127, 127, 127, 200)
1470 for poly in polys[1:]:
1471 glBegin(GL_TRIANGLE_FAN)
1473 glTexCoord2f(p[0]/20, p[1]/20)
1474 glVertex3f(p[0], p[1], 0)
1479 glDisable(GL_CULL_FACE)
1481 def getObjectCenterPos(self):
1482 if self._selectedObj is None:
1483 return [0.0, 0.0, 0.0]
1484 pos = self._selectedObj.getPosition()
1485 size = self._selectedObj.getSize()
1486 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1488 def getObjectBoundaryCircle(self):
1489 if self._selectedObj is None:
1491 return self._selectedObj.getBoundaryCircle()
1493 def getObjectSize(self):
1494 if self._selectedObj is None:
1495 return [0.0, 0.0, 0.0]
1496 return self._selectedObj.getSize()
1498 def getObjectMatrix(self):
1499 if self._selectedObj is None:
1500 return numpy.matrix(numpy.identity(3))
1501 return self._selectedObj.getMatrix()
1503 #TODO: Remove this or put it in a seperate file
1504 class shaderEditor(wx.Frame):
1505 def __init__(self, parent, callback, v, f):
1506 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1507 self._callback = callback
1508 s = wx.BoxSizer(wx.VERTICAL)
1510 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1511 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1512 s.Add(self._vertex, 1, flag=wx.EXPAND)
1513 s.Add(self._fragment, 1, flag=wx.EXPAND)
1515 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1516 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1518 self.SetPosition(self.GetParent().GetPosition())
1519 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1522 def OnText(self, e):
1523 self._callback(self._vertex.GetValue(), self._fragment.GetValue())