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)
52 self._mouse3Dpos = numpy.array([0,0,0], numpy.float32)
55 self._lastObjectSink = None
56 self._platformMesh = {}
57 self.glReleaseList = []
58 self._platformTexture = None
59 self._isSimpleMode = True
60 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
63 self._modelMatrix = None
64 self._projMatrix = None
65 self.tempMatrix = None
67 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
68 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
69 self.printButton.setDisabled(True)
72 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
73 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
74 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
76 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
77 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
79 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
80 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
82 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
83 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
84 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
86 self.rotateToolButton.setExpandArrow(True)
87 self.scaleToolButton.setExpandArrow(True)
88 self.mirrorToolButton.setExpandArrow(True)
90 self.scaleForm = openglGui.glFrame(self, (2, -2))
91 openglGui.glGuiLayoutGrid(self.scaleForm)
92 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
93 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
94 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
95 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
96 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
97 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
98 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
99 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
100 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
101 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
102 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
103 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
104 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
105 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
107 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)
108 self.viewSelection.setDisabled(True)
109 #self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
110 #self.youMagineButton.setDisabled(True)
112 self.notification = openglGui.glNotification(self, (0, 0))
114 self._engine = sliceEngine.Engine(self._updateEngineProgress)
115 self._engineResultView = engineResultView.engineResultView(self)
116 self._sceneUpdateTimer = wx.Timer(self)
117 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
118 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
119 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
123 self.updateToolButtons()
124 self.updateProfileToControls()
127 # Delete all objects first
128 self.OnDeleteAll(None)
129 self._engine.cleanup()
130 if self._objectShader is not None:
131 self._objectShader.release()
132 if self._objectLoadShader is not None:
133 self._objectLoadShader.release()
134 if self._objectOverhangShader is not None:
135 self._objectOverhangShader.release()
136 for obj in self.glReleaseList:
139 def loadGCodeFile(self, filename):
140 self.OnDeleteAll(None)
141 #Cheat the engine results to load a GCode file into it.
142 self._engine._result = sliceEngine.EngineResult()
143 with open(filename, "r") as f:
144 self._engine._result.setGCode(f.read())
145 self._engine._result.setFinished(True)
146 self._engineResultView.setResult(self._engine._result)
147 self.printButton.setBottomText('')
148 self.viewSelection.setDisabled(False)
149 self.viewSelection.setValue(4)
150 self.printButton.setDisabled(False)
151 #self.youMagineButton.setDisabled(True)
154 def loadSceneFiles(self, filenames):
155 #self.youMagineButton.setDisabled(False)
156 #if self.viewSelection.getValue() == 4:
157 # self.viewSelection.setValue(0)
158 # self.OnViewChange()
159 self.loadScene(filenames)
161 def loadFiles(self, filenames):
162 mainWindow = self.GetParent().GetParent().GetParent()
163 # only one GCODE file can be active
164 # so if single gcode file, process this
165 # otherwise ignore all gcode files
167 if len(filenames) == 1:
168 filename = filenames[0]
169 ext = os.path.splitext(filename)[1].lower()
170 if ext == '.g' or ext == '.gcode':
171 gcodeFilename = filename
172 mainWindow.addToModelMRU(filename)
173 if gcodeFilename is not None:
174 self.loadGCodeFile(gcodeFilename)
176 # process directories and special file types
177 # and keep scene files for later processing
179 ignored_types = dict()
180 # use file list as queue
181 # pop first entry for processing and append new files at end
183 filename = filenames.pop(0)
184 if os.path.isdir(filename):
185 # directory: queue all included files and directories
186 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
188 ext = os.path.splitext(filename)[1].lower()
190 profile.loadProfile(filename)
191 mainWindow.addToProfileMRU(filename)
192 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
193 scene_filenames.append(filename)
194 mainWindow.addToModelMRU(filename)
196 ignored_types[ext] = 1
198 ignored_types = ignored_types.keys()
200 self.notification.message(_("ignored: ") + " ".join("*" + type for type in ignored_types))
201 mainWindow.updateProfileToAllControls()
202 # now process all the scene files
204 self.loadSceneFiles(scene_filenames)
205 self._selectObject(None)
207 newZoom = numpy.max(self._machineSize)
208 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
209 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
211 def reloadScene(self, e):
212 # Copy the list before DeleteAll clears it
214 pms_transforms = [] #position, rotation matrix, scale
215 for obj in self._scene.objects():
216 fileList.append(obj.getOriginFilename())
217 pms_transforms.append((obj.getPosition(), obj.getMatrix(), obj.getScale()))
219 self.OnDeleteAll(None)
220 self.loadScene(fileList, pms_transforms)
222 def OnResetPositions(self, e):
224 self._scene.arrangeAll(True)
225 self._scene.centerAll()
228 def OnResetTransformations(self, e):
229 for obj in self._scene.objects():
233 self._scene.arrangeAll()
234 self._scene.centerAll()
236 def showLoadModel(self, button = 1):
238 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)
240 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
241 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
242 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
243 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
244 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
245 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
246 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
247 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
249 dlg.SetWildcard(wildcardFilter)
250 if dlg.ShowModal() != wx.ID_OK:
253 filenames = dlg.GetPaths()
255 if len(filenames) < 1:
257 profile.putPreference('lastFile', filenames[0])
258 self.loadFiles(filenames)
260 def showSaveModel(self):
261 if len(self._scene.objects()) < 1:
263 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
264 fileExtensions = meshLoader.saveSupportedExtensions()
265 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
266 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
267 dlg.SetWildcard(wildcardFilter)
268 if dlg.ShowModal() != wx.ID_OK:
271 filename = dlg.GetPath()
273 meshLoader.saveMeshes(filename, self._scene.objects())
275 def OnPrintButton(self, button):
277 connectionGroup = self._printerConnectionManager.getAvailableGroup()
278 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
279 drives = removableStorage.getPossibleSDcardDrives()
281 choices = map(lambda n: n[0], drives)
282 choices += (_("Custom file destination"), )
283 title = _("Multiple removable drives have been found")
285 choices = [drives[0][0], _("Custom file destination")]
286 title = _("A removable drive has been found")
288 dlg = wx.SingleChoiceDialog(self, _("Select destination SD card drive\nYou can also select a custom file to save to"), title, choices)
289 if dlg.ShowModal() != wx.ID_OK:
293 drive = drives[dlg.GetSelection()]
301 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
303 #check if the file is part of the root folder.
304 # If so, create folders on sd card to get the same folder hierarchy.
305 repDir = profile.getPreference("sdcard_rootfolder")
307 if os.path.exists(repDir) and os.path.isdir(repDir):
308 repDir = os.path.abspath(repDir)
309 originFilename = os.path.abspath( self._scene._objectList[0].getOriginFilename() )
310 if os.path.dirname(originFilename).startswith(repDir):
311 new_filename = os.path.splitext(originFilename[len(repDir):])[0] + profile.getGCodeExtension()
312 sdPath = os.path.dirname(os.path.join( drive[1], new_filename))
313 if not os.path.exists(sdPath):
314 print "Creating replication directory:", sdPath
316 filename = new_filename
320 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
321 elif connectionGroup is not None:
322 connections = connectionGroup.getAvailableConnections()
323 if len(connections) < 2:
324 connection = connections[0]
326 dlg = wx.SingleChoiceDialog(self, _("Select the %s connection to use") % (connectionGroup.getName()), _("Multiple %s connections found") % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
327 if dlg.ShowModal() != wx.ID_OK:
330 connection = connections[dlg.GetSelection()]
332 self._openPrintWindowForConnection(connection)
337 connections = self._printerConnectionManager.getAvailableConnections()
338 menu.connectionMap = {}
339 for connection in connections:
340 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
341 menu.connectionMap[i.GetId()] = connection
342 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
343 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
344 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
348 def _openPrintWindowForConnection(self, connection):
349 if connection.window is None or not connection.window:
350 connection.window = None
351 windowType = profile.getPreference('printing_window')
352 for p in pluginInfo.getPluginList('printwindow'):
353 if p.getName() == windowType:
354 connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
356 if connection.window is None:
357 connection.window = printWindow.printWindowBasic(self, connection)
358 connection.window.Show()
359 connection.window.Raise()
360 if not connection.loadGCodeData(self._engine.getResult().getGCode()):
361 if connection.isPrinting():
362 self.notification.message(_("Cannot start print, because other print still running."))
364 self.notification.message(_("Failed to start print..."))
366 def showSaveGCode(self):
367 if len(self._scene._objectList) < 1:
369 if not self._engine.getResult().isFinished():
371 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
372 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
373 dlg.SetFilename(filename)
374 dlg.SetWildcard('Toolpath (*%s)|*%s;*%s' % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
375 if dlg.ShowModal() != wx.ID_OK:
378 filename = dlg.GetPath()
381 threading.Thread(target=self._saveGCode,args=(filename,)).start()
383 def _saveGCode(self, targetFilename, ejectDrive = False):
384 gcode = self._engine.getResult().getGCode()
386 size = float(len(gcode))
388 with open(targetFilename, 'wb') as fdst:
390 buf = gcode.read(16*1024)
395 self.printButton.setProgressBar(read_pos / size)
398 import sys, traceback
399 traceback.print_exc()
400 self.notification.message(_("Failed to save"))
403 self.notification.message(_("Saved as %s") % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
404 elif explorer.hasExplorer():
405 self.notification.message(_("Saved as %s") % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, _('Open folder'))
407 self.notification.message(_("Saved as %s") % (targetFilename))
408 self.printButton.setProgressBar(None)
410 def _doEjectSD(self, drive):
411 if removableStorage.ejectDrive(drive):
412 self.notification.message(_('You can now eject the card.'))
414 self.notification.message(_('Safe remove failed...'))
416 def _showEngineLog(self):
417 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)
421 def OnToolSelect(self, button):
422 if self.rotateToolButton.getSelected():
423 self.tool = previewTools.toolRotate(self)
424 elif self.scaleToolButton.getSelected():
425 self.tool = previewTools.toolScale(self)
426 elif self.mirrorToolButton.getSelected():
427 self.tool = previewTools.toolNone(self)
429 self.tool = previewTools.toolNone(self)
430 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
431 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
432 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
433 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
434 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
435 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
436 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
437 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
439 def updateToolButtons(self):
440 if self._selectedObj is None:
444 self.rotateToolButton.setHidden(hidden)
445 self.scaleToolButton.setHidden(hidden)
446 self.mirrorToolButton.setHidden(hidden)
448 self.rotateToolButton.setSelected(False)
449 self.scaleToolButton.setSelected(False)
450 self.mirrorToolButton.setSelected(False)
453 def OnViewChange(self):
454 if self.viewSelection.getValue() == 4:
455 self.viewMode = 'gcode'
456 self.tool = previewTools.toolNone(self)
457 elif self.viewSelection.getValue() == 1:
458 self.viewMode = 'overhang'
459 elif self.viewSelection.getValue() == 2:
460 self.viewMode = 'transparent'
461 elif self.viewSelection.getValue() == 3:
462 self.viewMode = 'xray'
464 self.viewMode = 'normal'
465 self._engineResultView.setEnabled(self.viewMode == 'gcode')
468 def OnViewStateChange(self, state):
469 self._engineResultView.layerSelect.setHidden(self.viewMode != 'gcode' or state)
470 self._engineResultView.singleLayerToggle.setHidden(self.viewMode != 'gcode' or state)
472 def OnRotateReset(self, button):
473 if self._selectedObj is None:
475 self._selectedObj.resetRotation()
476 self._scene.pushFree(self._selectedObj)
477 self._selectObject(self._selectedObj)
480 def OnLayFlat(self, button):
481 if self._selectedObj is None:
483 self._selectedObj.layFlat()
484 self._scene.pushFree(self._selectedObj)
485 self._selectObject(self._selectedObj)
488 def OnScaleReset(self, button):
489 if self._selectedObj is None:
491 self._selectedObj.resetScale()
492 self._selectObject(self._selectedObj)
493 self.updateProfileToControls()
496 def OnScaleMax(self, button):
497 if self._selectedObj is None:
499 machine = profile.getMachineSetting('machine_type')
500 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
501 self._scene.pushFree(self._selectedObj)
503 if machine == "ultimaker2":
504 #This is bad and Jaime should feel bad!
505 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
506 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
507 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
508 self._scene.pushFree(self._selectedObj)
510 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
511 self._scene.pushFree(self._selectedObj)
512 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
513 self._scene.pushFree(self._selectedObj)
514 self._selectObject(self._selectedObj)
515 self.updateProfileToControls()
518 def OnMirror(self, axis):
519 if self._selectedObj is None:
521 self._selectedObj.mirror(axis)
524 def OnScaleEntry(self, value, axis):
525 if self._selectedObj is None:
531 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
532 self.updateProfileToControls()
533 self._scene.pushFree(self._selectedObj)
534 self._selectObject(self._selectedObj)
537 def OnScaleEntryMM(self, value, axis):
538 if self._selectedObj is None:
544 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
545 self.updateProfileToControls()
546 self._scene.pushFree(self._selectedObj)
547 self._selectObject(self._selectedObj)
550 def OnDeleteAll(self, e):
551 while len(self._scene.objects()) > 0:
552 self._deleteObject(self._scene.objects()[0])
553 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
554 self._engineResultView.setResult(None)
555 self.viewSelection.setDisabled(True)
556 self.printButton.setDisabled(True)
558 def OnMultiply(self, e):
559 if self._focusObj is None:
562 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
563 if dlg.ShowModal() != wx.ID_OK:
569 # 0:unrequested arrange all. Objects should not move.
570 # 1:requested arrange all but refused.
571 # 2:arrange all and center from now on.
572 requestedArrangeAll = 0
578 self._scene.add(newObj)
579 if requestedArrangeAll == 2:
580 self._scene.centerAll()
582 if not self._scene.checkPlatform(newObj):
583 if requestedArrangeAll == 0:
584 requestedArrangeAll = 1
585 dlg = wx.MessageDialog(self, _("Cannot fit all the requested duplicates. Do you want to try and reset object positions?"), _("Reset Positions"), wx.YES_NO)
587 if dlg.ShowModal() == wx.ID_YES:
589 requestedArrangeAll = 2
590 self._scene.remove(newObj)
591 self.OnResetPositions(None)
601 self.notification.message(_("Could not create more than %d items") % (n - 1))
602 self._scene.remove(newObj)
603 if requestedArrangeAll == 2:
604 self._scene.centerAll()
608 def OnSplitObject(self, e):
609 if self._focusObj is None:
611 self._scene.remove(self._focusObj)
612 for obj in self._focusObj.split(self._splitCallback):
613 if numpy.max(obj.getSize()) > 2.0:
615 self._scene.centerAll()
616 self._selectObject(None)
619 def OnCenter(self, e):
620 if self._focusObj is None:
622 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
623 self._scene.pushFree(self._selectedObj)
624 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
625 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
628 def _splitCallback(self, progress):
631 def OnMergeObjects(self, e):
632 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
633 if len(self._scene.objects()) == 2:
634 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
637 self._scene.merge(self._selectedObj, self._focusObj)
640 def sceneUpdated(self):
642 objectSink = profile.getProfileSettingFloat("object_sink")
643 if self._lastObjectSink != objectSink:
644 self._lastObjectSink = objectSink
645 self._scene.updateHeadSize()
647 wx.CallAfter(self._sceneUpdateTimer.Start, 500, True)
648 self._engine.abortEngine()
649 self._scene.updateSizeOffsets()
652 def _onRunEngine(self, e):
653 if self._isSimpleMode:
654 self._engine.runEngine(self._scene, self.GetTopLevelParent().simpleSettingsPanel.getSettingOverrides())
656 self._engine.runEngine(self._scene)
658 def _updateEngineProgress(self, progressValue):
659 result = self._engine.getResult()
660 finished = result is not None and result.isFinished()
662 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
664 self.printButton.setDisabled(not finished)
665 self.viewSelection.setDisabled(not finished)
666 if progressValue >= 0.0:
667 self.printButton.setProgressBar(progressValue)
669 self.printButton.setProgressBar(None)
670 self._engineResultView.setResult(result)
672 self.printButton.setProgressBar(None)
673 text = '%s' % (result.getPrintTime())
674 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
675 amount = result.getFilamentAmount(e)
678 text += '\n%s' % (amount)
679 cost = result.getFilamentCost(e)
681 text += '\n%s' % (cost)
682 self.printButton.setBottomText(text)
684 self.printButton.setBottomText('')
687 def loadScene(self, fileList, pms_transforms=None):
689 for filename in fileList:
692 ext = os.path.splitext(filename)[1].lower()
693 if ext in imageToMesh.supportedExtensions():
694 imageToMesh.convertImageDialog(self, filename).Show()
697 objList = meshLoader.loadMeshes(filename)
699 traceback.print_exc()
702 if self._objectLoadShader is not None:
703 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
707 if pms_transforms is not None and len(pms_transforms) == len(fileList):
708 obj.setPosition(pms_transforms[objIndex][0])
709 obj.applyMatrix(pms_transforms[objIndex][1])
710 obj.setScale(pms_transforms[objIndex][2][0], 0, False)
711 obj.setScale(pms_transforms[objIndex][2][1], 1, False)
712 obj.setScale(pms_transforms[objIndex][2][2], 2, False)
714 if not self._scene.checkPlatform(obj):
715 self._scene.centerAll()
716 self._selectObject(obj)
717 if obj.getScale()[0] < 1.0:
718 self.notification.message(_("Warning: Object scaled down."))
721 def _deleteObject(self, obj):
722 if obj == self._selectedObj:
723 self._selectObject(None)
724 if obj == self._focusObj:
725 self._focusObj = None
726 self._scene.remove(obj)
727 for m in obj._meshList:
728 if m.vbo is not None and m.vbo.decRef():
729 self.glReleaseList.append(m.vbo)
730 if len(self._scene.objects()) == 0:
731 self._engineResultView.setResult(None)
732 self.printButton.setDisabled(True)
733 self.viewSelection.setDisabled(True)
734 self.printButton.setBottomText('')
739 def _selectObject(self, obj, zoom = True):
740 if obj != self._selectedObj:
741 self._selectedObj = obj
742 self.updateModelSettingsToControls()
743 self.updateToolButtons()
744 if zoom and obj is not None:
745 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
746 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
747 newZoom = obj.getBoundaryCircle() * 6
748 if newZoom > numpy.max(self._machineSize) * 3:
749 newZoom = numpy.max(self._machineSize) * 3
750 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
752 def updateProfileToControls(self):
753 oldSimpleMode = self._isSimpleMode
754 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
755 if self._isSimpleMode != oldSimpleMode:
756 self._scene.arrangeAll()
758 self._scene.updateSizeOffsets(True)
759 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
760 self._objColors[0] = profile.getPreferenceColour('model_colour')
761 self._objColors[1] = profile.getPreferenceColour('model_colour2')
762 self._objColors[2] = profile.getPreferenceColour('model_colour3')
763 self._objColors[3] = profile.getPreferenceColour('model_colour4')
764 self._scene.updateMachineDimensions()
765 if self._zoom > numpy.max(self._machineSize) * 3:
766 self._animZoom = openglGui.animation(self, self._zoom, numpy.max(self._machineSize) * 3, 0.5)
767 self.updateModelSettingsToControls()
769 def updateModelSettingsToControls(self):
770 if self._selectedObj is not None:
771 scale = self._selectedObj.getScale()
772 size = self._selectedObj.getSize()
773 self.scaleXctrl.setValue(round(scale[0], 2))
774 self.scaleYctrl.setValue(round(scale[1], 2))
775 self.scaleZctrl.setValue(round(scale[2], 2))
776 self.scaleXmmctrl.setValue(round(size[0], 2))
777 self.scaleYmmctrl.setValue(round(size[1], 2))
778 self.scaleZmmctrl.setValue(round(size[2], 2))
780 def OnKeyChar(self, keyCode):
781 if self._engineResultView.OnKeyChar(keyCode):
783 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and sys.platform.startswith("darwin")):
784 if self._selectedObj is not None:
785 self._deleteObject(self._selectedObj)
787 if keyCode == wx.WXK_UP:
788 if wx.GetKeyState(wx.WXK_SHIFT):
795 elif keyCode == wx.WXK_DOWN:
796 if wx.GetKeyState(wx.WXK_SHIFT):
798 if self._zoom > numpy.max(self._machineSize) * 3:
799 self._zoom = numpy.max(self._machineSize) * 3
803 elif keyCode == wx.WXK_LEFT:
806 elif keyCode == wx.WXK_RIGHT:
809 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
814 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
816 if self._zoom > numpy.max(self._machineSize) * 3:
817 self._zoom = numpy.max(self._machineSize) * 3
819 elif keyCode == wx.WXK_HOME:
823 elif keyCode == wx.WXK_PAGEUP:
827 elif keyCode == wx.WXK_PAGEDOWN:
831 elif keyCode == wx.WXK_END:
836 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
837 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
838 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
839 from collections import defaultdict
840 from gc import get_objects
841 self._beforeLeakTest = defaultdict(int)
842 for i in get_objects():
843 self._beforeLeakTest[type(i)] += 1
844 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
845 from collections import defaultdict
846 from gc import get_objects
847 self._afterLeakTest = defaultdict(int)
848 for i in get_objects():
849 self._afterLeakTest[type(i)] += 1
850 for k in self._afterLeakTest:
851 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
852 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
854 def ShaderUpdate(self, v, f):
855 s = openglHelpers.GLShader(v, f)
857 self._objectLoadShader.release()
858 self._objectLoadShader = s
859 for obj in self._scene.objects():
860 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
863 def OnMouseDown(self,e):
864 self._mouseX = e.GetX()
865 self._mouseY = e.GetY()
866 self._mouseClick3DPos = self._mouse3Dpos
867 self._mouseClickFocus = self._focusObj
869 self._mouseState = 'doubleClick'
871 if self._mouseState == 'dragObject' and self._selectedObj is not None:
872 self._scene.pushFree(self._selectedObj)
874 self._mouseState = 'dragOrClick'
875 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
876 p0 -= self.getObjectCenterPos() - self._viewTarget
877 p1 -= self.getObjectCenterPos() - self._viewTarget
878 if self.tool.OnDragStart(p0, p1):
879 self._mouseState = 'tool'
880 if self._mouseState == 'dragOrClick':
881 if e.GetButton() == 1:
882 if self._focusObj is not None:
883 self._selectObject(self._focusObj, False)
886 def OnMouseUp(self, e):
887 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
889 if self._mouseState == 'dragOrClick':
890 if e.GetButton() == 1:
891 self._selectObject(self._focusObj)
892 if e.GetButton() == 3:
894 if self._focusObj is not None:
896 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
897 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
898 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
899 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
901 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:
902 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
903 if len(self._scene.objects()) > 0:
904 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
905 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
906 self.Bind(wx.EVT_MENU, self.OnResetPositions, menu.Append(-1, _("Reset all objects positions")))
907 self.Bind(wx.EVT_MENU, self.OnResetTransformations, menu.Append(-1, _("Reset all objects transformations")))
909 if menu.MenuItemCount > 0:
912 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
913 self._scene.pushFree(self._selectedObj)
915 elif self._mouseState == 'tool':
916 if self.tempMatrix is not None and self._selectedObj is not None:
917 self._selectedObj.applyMatrix(self.tempMatrix)
918 self._scene.pushFree(self._selectedObj)
919 self._selectObject(self._selectedObj)
920 self.tempMatrix = None
921 self.tool.OnDragEnd()
923 self._mouseState = None
925 def OnMouseMotion(self,e):
926 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
927 p0 -= self.getObjectCenterPos() - self._viewTarget
928 p1 -= self.getObjectCenterPos() - self._viewTarget
930 if e.Dragging() and self._mouseState is not None:
931 if self._mouseState == 'tool':
932 self.tool.OnDrag(p0, p1)
933 elif not e.LeftIsDown() and e.RightIsDown():
934 self._mouseState = 'drag'
935 if wx.GetKeyState(wx.WXK_SHIFT):
936 a = math.cos(math.radians(self._yaw)) / 3.0
937 b = math.sin(math.radians(self._yaw)) / 3.0
938 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
939 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
940 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
941 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
943 self._yaw += e.GetX() - self._mouseX
944 self._pitch -= e.GetY() - self._mouseY
945 if self._pitch > 170:
949 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
950 self._mouseState = 'drag'
951 self._zoom += e.GetY() - self._mouseY
954 if self._zoom > numpy.max(self._machineSize) * 3:
955 self._zoom = numpy.max(self._machineSize) * 3
956 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
957 self._mouseState = 'dragObject'
958 z = max(0, self._mouseClick3DPos[2])
959 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
960 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
965 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
966 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
967 diff = cursorZ1 - cursorZ0
968 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
969 if not e.Dragging() or self._mouseState != 'tool':
970 self.tool.OnMouseMove(p0, p1)
972 self._mouseX = e.GetX()
973 self._mouseY = e.GetY()
975 def OnMouseWheel(self, e):
976 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
977 delta = max(min(delta,4),-4)
978 self._zoom *= 1.0 - delta / 10.0
981 if self._zoom > numpy.max(self._machineSize) * 3:
982 self._zoom = numpy.max(self._machineSize) * 3
985 def OnMouseLeave(self, e):
989 def getMouseRay(self, x, y):
990 if self._viewport is None:
991 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
992 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
993 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
994 p0 -= self._viewTarget
995 p1 -= self._viewTarget
998 def _init3DView(self):
999 # set viewing projection
1000 size = self.GetSize()
1001 glViewport(0, 0, size.GetWidth(), size.GetHeight())
1004 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
1006 glDisable(GL_RESCALE_NORMAL)
1007 glDisable(GL_LIGHTING)
1008 glDisable(GL_LIGHT0)
1009 glEnable(GL_DEPTH_TEST)
1010 glDisable(GL_CULL_FACE)
1012 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1014 #scene background color
1015 glClearColor(0.85, 0.85, 0.85, 1.0)
1019 glMatrixMode(GL_PROJECTION)
1021 aspect = float(size.GetWidth()) / float(size.GetHeight())
1022 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
1024 glMatrixMode(GL_MODELVIEW)
1026 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1028 def OnPaint(self,e):
1029 connectionGroup = self._printerConnectionManager.getAvailableGroup()
1030 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
1031 self.printButton._imageID = 2
1032 self.printButton._tooltip = _("Toolpath to SD")
1033 elif connectionGroup is not None:
1034 self.printButton._imageID = connectionGroup.getIconID()
1035 #self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
1036 self.printButton._tooltip = _("Print/Control")
1038 self.printButton._imageID = 3
1039 self.printButton._tooltip = _("Save toolpath")
1041 if self._animView is not None:
1042 self._viewTarget = self._animView.getPosition()
1043 if self._animView.isDone():
1044 self._animView = None
1045 if self._animZoom is not None:
1046 self._zoom = self._animZoom.getPosition()
1047 if self._animZoom.isDone():
1048 self._animZoom = None
1049 if self._objectShader is None: #TODO: add loading shaders from file(s)
1050 if openglHelpers.hasShaderSupport():
1051 self._objectShader = openglHelpers.GLShader("""
1052 varying float light_amount;
1056 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1057 gl_FrontColor = gl_Color;
1059 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1060 light_amount += 0.2;
1063 varying float light_amount;
1067 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1070 self._objectOverhangShader = openglHelpers.GLShader("""
1071 uniform float cosAngle;
1072 uniform mat3 rotMatrix;
1073 varying float light_amount;
1077 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1078 gl_FrontColor = gl_Color;
1080 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1081 light_amount += 0.2;
1082 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
1084 light_amount = -10.0;
1088 varying float light_amount;
1092 if (light_amount == -10.0)
1094 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1096 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1100 self._objectLoadShader = openglHelpers.GLShader("""
1101 uniform float intensity;
1102 uniform float scale;
1103 varying float light_amount;
1107 vec4 tmp = gl_Vertex;
1108 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1109 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1110 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1111 gl_FrontColor = gl_Color;
1113 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1114 light_amount += 0.2;
1117 uniform float intensity;
1118 varying float light_amount;
1122 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1125 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
1126 self._objectShader = openglHelpers.GLFakeShader()
1127 self._objectOverhangShader = openglHelpers.GLFakeShader()
1128 self._objectLoadShader = None
1130 glTranslate(0,0,-self._zoom)
1131 glRotate(-self._pitch, 1,0,0)
1132 glRotate(self._yaw, 0,0,1)
1133 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1135 self._viewport = glGetIntegerv(GL_VIEWPORT)
1136 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1137 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1139 glClearColor(1,1,1,1)
1140 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1142 if self.viewMode != 'gcode':
1143 for n in xrange(0, len(self._scene.objects())):
1144 obj = self._scene.objects()[n]
1145 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1146 self._renderObject(obj)
1148 if self._mouseX > -1: # mouse has not passed over the opengl window.
1150 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1151 if n < len(self._scene.objects()):
1152 self._focusObj = self._scene.objects()[n]
1154 self._focusObj = None
1155 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1156 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1157 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1158 self._mouse3Dpos -= self._viewTarget
1161 glTranslate(0,0,-self._zoom)
1162 glRotate(-self._pitch, 1,0,0)
1163 glRotate(self._yaw, 0,0,1)
1164 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1166 self._objectShader.unbind()
1167 self._engineResultView.OnDraw()
1168 if self.viewMode != 'gcode':
1169 glStencilFunc(GL_ALWAYS, 1, 1)
1170 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1172 if self.viewMode == 'overhang':
1173 self._objectOverhangShader.bind()
1174 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1176 self._objectShader.bind()
1177 for obj in self._scene.objects():
1178 if obj._loadAnim is not None:
1179 if obj._loadAnim.isDone():
1180 obj._loadAnim = None
1184 if self._focusObj == obj:
1186 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1189 if self._selectedObj == obj or self._selectedObj is None:
1190 #If we want transparent, then first render a solid black model to remove the printer size lines.
1191 if self.viewMode == 'transparent':
1192 glColor4f(0, 0, 0, 0)
1193 self._renderObject(obj)
1195 glBlendFunc(GL_ONE, GL_ONE)
1196 glDisable(GL_DEPTH_TEST)
1198 if self.viewMode == 'xray':
1199 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1200 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1201 glEnable(GL_STENCIL_TEST)
1203 if self.viewMode == 'overhang':
1204 if self._selectedObj == obj and self.tempMatrix is not None:
1205 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1207 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1209 if not self._scene.checkPlatform(obj):
1210 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1211 self._renderObject(obj)
1213 self._renderObject(obj, brightness)
1214 glDisable(GL_STENCIL_TEST)
1216 glEnable(GL_DEPTH_TEST)
1217 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1219 if self.viewMode == 'xray':
1222 glEnable(GL_STENCIL_TEST)
1223 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1224 glDisable(GL_DEPTH_TEST)
1225 for i in xrange(2, 15, 2): #All even values
1226 glStencilFunc(GL_EQUAL, i, 0xFF)
1227 glColor(float(i)/10, float(i)/10, float(i)/5)
1229 glVertex3f(-1000,-1000,-10)
1230 glVertex3f( 1000,-1000,-10)
1231 glVertex3f( 1000, 1000,-10)
1232 glVertex3f(-1000, 1000,-10)
1234 for i in xrange(1, 15, 2): #All odd values
1235 glStencilFunc(GL_EQUAL, i, 0xFF)
1236 glColor(float(i)/10, 0, 0)
1238 glVertex3f(-1000,-1000,-10)
1239 glVertex3f( 1000,-1000,-10)
1240 glVertex3f( 1000, 1000,-10)
1241 glVertex3f(-1000, 1000,-10)
1244 glDisable(GL_STENCIL_TEST)
1245 glEnable(GL_DEPTH_TEST)
1247 self._objectShader.unbind()
1249 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1251 if self._objectLoadShader is not None:
1252 self._objectLoadShader.bind()
1253 #Model color during load animation
1254 glColor4ub(177, 205, 54, 255)
1255 for obj in self._scene.objects():
1256 if obj._loadAnim is None:
1258 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1259 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1260 self._renderObject(obj)
1261 self._objectLoadShader.unbind()
1266 if self.viewMode != 'gcode':
1267 #Draw the object box-shadow, so you can see where it will collide with other objects.
1268 if self._selectedObj is not None:
1270 glEnable(GL_CULL_FACE)
1271 glColor4f(0,0,0,0.16)
1273 for obj in self._scene.objects():
1275 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1276 glBegin(GL_TRIANGLE_FAN)
1277 for p in obj._boundaryHull[::-1]:
1278 glVertex3f(p[0], p[1], 0)
1281 if self._scene.isOneAtATime(): #Check print sequence mode.
1283 glColor4f(0,0,0,0.06)
1284 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1285 glBegin(GL_TRIANGLE_FAN)
1286 for p in self._selectedObj._printAreaHull[::-1]:
1287 glVertex3f(p[0], p[1], 0)
1289 glBegin(GL_TRIANGLE_FAN)
1290 for p in self._selectedObj._headAreaMinHull[::-1]:
1291 glVertex3f(p[0], p[1], 0)
1295 glDisable(GL_CULL_FACE)
1297 #Draw the outline of the selected object on top of everything else except the GUI.
1298 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1299 glDisable(GL_DEPTH_TEST)
1300 glEnable(GL_CULL_FACE)
1301 glEnable(GL_STENCIL_TEST)
1303 glStencilFunc(GL_EQUAL, 0, 255)
1305 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1307 glColor4f(1,1,1,0.5)
1308 self._renderObject(self._selectedObj)
1309 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1311 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1312 glDisable(GL_STENCIL_TEST)
1313 glDisable(GL_CULL_FACE)
1314 glEnable(GL_DEPTH_TEST)
1316 if self._selectedObj is not None:
1318 pos = self.getObjectCenterPos()
1319 glTranslate(pos[0], pos[1], pos[2])
1322 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1323 glDisable(GL_DEPTH_TEST)
1326 glTranslate(0,-4,-10)
1327 glColor4ub(60,60,60,255)
1328 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1331 def _renderObject(self, obj, brightness = 0, addSink = True):
1334 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1336 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1338 if self.tempMatrix is not None and obj == self._selectedObj:
1339 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1341 offset = obj.getDrawOffset()
1342 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1344 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1347 for m in obj._meshList:
1349 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1351 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1356 def _drawMachine(self):
1357 glEnable(GL_CULL_FACE)
1360 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1362 #Due to NC licensing of the stl files, temporarily removing platform mesh loading for Ultimaker and Witbox
1363 '''machine_type = profile.getMachineSetting('machine_type')
1364 if machine_type not in self._platformMesh:
1365 self._platformMesh[machine_type] = None
1370 texture_offset = [0,0,0]
1372 if machine_type == 'ultimaker2' or machine_type == 'ultimaker2extended':
1373 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1374 offset = [-9,-37,145]
1375 texture_name = 'Ultimaker2backplate.png'
1376 texture_offset = [9,150,-5]
1377 elif machine_type == 'ultimaker2go':
1378 filename = resources.getPathForMesh('ultimaker2go_platform.stl')
1379 offset = [0,-42,145]
1380 texture_offset = [0,105,-5]
1381 texture_name = 'Ultimaker2backplate.png'
1383 elif machine_type == 'ultimaker_plus':
1384 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1385 offset = [0,-37,145]
1386 texture_offset = [0,150,-5]
1387 texture_name = 'UltimakerPlusbackplate.png'
1388 elif machine_type == 'ultimaker':
1389 filename = resources.getPathForMesh('ultimaker_platform.stl')
1391 elif machine_type == 'Witbox':
1392 filename = resources.getPathForMesh('Witbox_platform.stl')
1393 offset = [0,-37,145]
1395 if filename is not None:
1396 meshes = meshLoader.loadMeshes(filename)
1398 self._platformMesh[machine_type] = meshes[0]
1399 self._platformMesh[machine_type]._drawOffset = numpy.array(offset, numpy.float32)
1400 self._platformMesh[machine_type].texture = None
1401 if texture_name is not None:
1402 self._platformMesh[machine_type].texture = openglHelpers.loadGLTexture(texture_name)
1403 self._platformMesh[machine_type].texture_offset = texture_offset
1404 self._platformMesh[machine_type].texture_scale = texture_scale
1405 if self._platformMesh[machine_type] is not None:
1406 mesh = self._platformMesh[machine_type]
1407 glColor4f(1,1,1,0.5)
1408 self._objectShader.bind()
1409 self._renderObject(mesh, False, False)
1410 self._objectShader.unbind()
1412 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1413 if mesh.texture is not None:
1414 glBindTexture(GL_TEXTURE_2D, mesh.texture)
1415 glEnable(GL_TEXTURE_2D)
1419 glTranslate(mesh.texture_offset[0], mesh.texture_offset[1], mesh.texture_offset[2])
1420 glScalef(mesh.texture_scale, mesh.texture_scale, mesh.texture_scale)
1425 glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA)
1426 glEnable(GL_ALPHA_TEST)
1427 glAlphaFunc(GL_GREATER, 0.0)
1430 glVertex3f( w, 0, h)
1432 glVertex3f(-w, 0, h)
1434 glVertex3f(-w, 0, 0)
1436 glVertex3f( w, 0, 0)
1439 glVertex3f(-w, d, h)
1441 glVertex3f( w, d, h)
1443 glVertex3f( w, d, 0)
1445 glVertex3f(-w, d, 0)
1447 glDisable(GL_TEXTURE_2D)
1448 glDisable(GL_ALPHA_TEST)
1449 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1452 # until glEnd() goes inside the else
1456 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1457 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1458 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1459 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1460 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1461 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1466 polys = profile.getMachineSizePolygons()
1467 height = profile.getMachineSettingFloat('machine_height')
1468 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1470 # Draw the sides of the build volume.
1471 for n in xrange(0, len(polys[0])):
1474 glColor4ub(210, 235, 103, 100)
1476 glColor4ub(223, 241, 145, 100)
1478 glColor4ub(223, 241, 145, 100)
1480 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1481 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1482 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1483 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1486 #Draw top of build volume.
1487 glColor4ub(183, 209, 90, 100)
1488 glBegin(GL_TRIANGLE_FAN)
1489 for p in polys[0][::-1]:
1490 glVertex3f(p[0], p[1], height)
1494 if self._platformTexture is None:
1495 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1496 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1497 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1498 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1499 #Dark checkerboard color
1500 glColor4f(1,1,1,0.7)
1501 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1502 glEnable(GL_TEXTURE_2D)
1503 glBegin(GL_TRIANGLE_FAN)
1505 glTexCoord2f(p[0]/20, p[1]/20)
1506 glVertex3f(p[0], p[1], 0)
1509 #Draw no-go zones. (clips in case of UM2)
1510 glDisable(GL_TEXTURE_2D)
1511 glColor4ub(127, 127, 127, 200)
1512 for poly in polys[1:]:
1513 glBegin(GL_TRIANGLE_FAN)
1515 glTexCoord2f(p[0]/20, p[1]/20)
1516 glVertex3f(p[0], p[1], 0)
1521 glDisable(GL_CULL_FACE)
1523 def getObjectCenterPos(self):
1524 if self._selectedObj is None:
1525 return [0.0, 0.0, 0.0]
1526 pos = self._selectedObj.getPosition()
1527 size = self._selectedObj.getSize()
1528 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1530 def getObjectBoundaryCircle(self):
1531 if self._selectedObj is None:
1533 return self._selectedObj.getBoundaryCircle()
1535 def getObjectSize(self):
1536 if self._selectedObj is None:
1537 return [0.0, 0.0, 0.0]
1538 return self._selectedObj.getSize()
1540 def getObjectMatrix(self):
1541 if self._selectedObj is None:
1542 return numpy.matrix(numpy.identity(3))
1543 return self._selectedObj.getMatrix()
1545 #TODO: Remove this or put it in a seperate file
1546 class shaderEditor(wx.Frame):
1547 def __init__(self, parent, callback, v, f):
1548 super(shaderEditor, self).__init__(parent, title=_("Shader editor"), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1549 self._callback = callback
1550 s = wx.BoxSizer(wx.VERTICAL)
1552 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1553 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1554 s.Add(self._vertex, 1, flag=wx.EXPAND)
1555 s.Add(self._fragment, 1, flag=wx.EXPAND)
1557 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1558 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1560 self.SetPosition(self.GetParent().GetPosition())
1561 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1564 def OnText(self, e):
1565 self._callback(self._vertex.GetValue(), self._fragment.GetValue())