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()
229 def OnResetTransformations(self, e):
230 for obj in self._scene.objects():
234 self._scene.arrangeAll()
235 self._scene.centerAll()
238 def showLoadModel(self, button = 1):
240 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)
242 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
243 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
244 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
245 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
246 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
247 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
248 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
249 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
251 dlg.SetWildcard(wildcardFilter)
252 if dlg.ShowModal() != wx.ID_OK:
255 filenames = dlg.GetPaths()
257 if len(filenames) < 1:
259 profile.putPreference('lastFile', filenames[0])
260 self.loadFiles(filenames)
262 def showSaveModel(self):
263 if len(self._scene.objects()) < 1:
265 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
266 fileExtensions = meshLoader.saveSupportedExtensions()
267 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
268 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
269 dlg.SetWildcard(wildcardFilter)
270 if dlg.ShowModal() != wx.ID_OK:
273 filename = dlg.GetPath()
275 meshLoader.saveMeshes(filename, self._scene.objects())
277 def OnPrintButton(self, button):
279 connectionGroup = self._printerConnectionManager.getAvailableGroup()
280 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
281 drives = removableStorage.getPossibleSDcardDrives()
283 choices = map(lambda n: n[0], drives)
284 choices += (_("Custom file destination"), )
285 title = _("Multiple removable drives have been found")
287 choices = [drives[0][0], _("Custom file destination")]
288 title = _("A removable drive has been found")
290 dlg = wx.SingleChoiceDialog(self, _("Select destination SD card drive\nYou can also select a custom file to save to"), title, choices)
291 if dlg.ShowModal() != wx.ID_OK:
295 drive = drives[dlg.GetSelection()]
303 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
305 #check if the file is part of the root folder.
306 # If so, create folders on sd card to get the same folder hierarchy.
307 repDir = profile.getPreference("sdcard_rootfolder")
309 if os.path.exists(repDir) and os.path.isdir(repDir):
310 repDir = os.path.abspath(repDir)
311 originFilename = os.path.abspath( self._scene._objectList[0].getOriginFilename() )
312 if os.path.dirname(originFilename).startswith(repDir):
313 new_filename = os.path.splitext(originFilename[len(repDir):])[0] + profile.getGCodeExtension()
314 sdPath = os.path.dirname(os.path.join( drive[1], new_filename))
315 if not os.path.exists(sdPath):
316 print "Creating replication directory:", sdPath
318 filename = new_filename
322 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
323 elif connectionGroup is not None:
324 connections = connectionGroup.getAvailableConnections()
325 if len(connections) < 2:
326 connection = connections[0]
328 dlg = wx.SingleChoiceDialog(self, _("Select the %s connection to use") % (connectionGroup.getName()), _("Multiple %s connections found") % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
329 if dlg.ShowModal() != wx.ID_OK:
332 connection = connections[dlg.GetSelection()]
334 self._openPrintWindowForConnection(connection)
339 connections = self._printerConnectionManager.getAvailableConnections()
340 menu.connectionMap = {}
341 for connection in connections:
342 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
343 menu.connectionMap[i.GetId()] = connection
344 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
345 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
346 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
350 def _openPrintWindowForConnection(self, connection):
351 if connection.window is None or not connection.window:
352 connection.window = None
353 windowType = profile.getPreference('printing_window')
354 for p in pluginInfo.getPluginList('printwindow'):
355 if p.getName() == windowType:
356 connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
358 if connection.window is None:
359 connection.window = printWindow.printWindowBasic(self, connection)
360 connection.window.Show()
361 connection.window.Raise()
362 if not connection.loadGCodeData(self._engine.getResult().getGCode()):
363 if connection.isPrinting():
364 self.notification.message(_("Cannot start print, because other print still running."))
366 self.notification.message(_("Failed to start print..."))
368 def showSaveGCode(self):
369 if len(self._scene._objectList) < 1:
371 if not self._engine.getResult().isFinished():
373 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
374 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
375 dlg.SetFilename(filename)
376 dlg.SetWildcard('Toolpath (*%s)|*%s;*%s' % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
377 if dlg.ShowModal() != wx.ID_OK:
380 filename = dlg.GetPath()
383 threading.Thread(target=self._saveGCode,args=(filename,)).start()
385 def _saveGCode(self, targetFilename, ejectDrive = False):
386 gcode = self._engine.getResult().getGCode()
388 size = float(len(gcode))
390 with open(targetFilename, 'wb') as fdst:
392 buf = gcode.read(16*1024)
397 self.printButton.setProgressBar(read_pos / size)
400 import sys, traceback
401 traceback.print_exc()
402 self.notification.message(_("Failed to save"))
405 self.notification.message(_("Saved as %s") % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
406 elif explorer.hasExplorer():
407 self.notification.message(_("Saved as %s") % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, _('Open folder'))
409 self.notification.message(_("Saved as %s") % (targetFilename))
410 self.printButton.setProgressBar(None)
412 def _doEjectSD(self, drive):
413 if removableStorage.ejectDrive(drive):
414 self.notification.message(_('You can now eject the card.'))
416 self.notification.message(_('Safe remove failed...'))
418 def _showEngineLog(self):
419 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)
423 def OnToolSelect(self, button):
424 if self.rotateToolButton.getSelected():
425 self.tool = previewTools.toolRotate(self)
426 elif self.scaleToolButton.getSelected():
427 self.tool = previewTools.toolScale(self)
428 elif self.mirrorToolButton.getSelected():
429 self.tool = previewTools.toolNone(self)
431 self.tool = previewTools.toolNone(self)
432 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
433 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
434 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
435 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
436 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
437 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
438 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
439 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
441 def updateToolButtons(self):
442 if self._selectedObj is None:
446 self.rotateToolButton.setHidden(hidden)
447 self.scaleToolButton.setHidden(hidden)
448 self.mirrorToolButton.setHidden(hidden)
450 self.rotateToolButton.setSelected(False)
451 self.scaleToolButton.setSelected(False)
452 self.mirrorToolButton.setSelected(False)
455 def OnViewChange(self):
456 if self.viewSelection.getValue() == 4:
457 self.viewMode = 'gcode'
458 self.tool = previewTools.toolNone(self)
459 elif self.viewSelection.getValue() == 1:
460 self.viewMode = 'overhang'
461 elif self.viewSelection.getValue() == 2:
462 self.viewMode = 'transparent'
463 elif self.viewSelection.getValue() == 3:
464 self.viewMode = 'xray'
466 self.viewMode = 'normal'
467 self._engineResultView.setEnabled(self.viewMode == 'gcode')
470 def OnViewStateChange(self, state):
471 self._engineResultView.layerSelect.setHidden(self.viewMode != 'gcode' or state)
472 self._engineResultView.singleLayerToggle.setHidden(self.viewMode != 'gcode' or state)
474 def OnRotateReset(self, button):
475 if self._selectedObj is None:
477 self._selectedObj.resetRotation()
478 self._scene.pushFree(self._selectedObj)
479 self._selectObject(self._selectedObj)
482 def OnLayFlat(self, button):
483 if self._selectedObj is None:
485 self._selectedObj.layFlat()
486 self._scene.pushFree(self._selectedObj)
487 self._selectObject(self._selectedObj)
490 def OnScaleReset(self, button):
491 if self._selectedObj is None:
493 self._selectedObj.resetScale()
494 self._selectObject(self._selectedObj)
495 self.updateProfileToControls()
498 def OnScaleMax(self, button):
499 if self._selectedObj is None:
501 machine = profile.getMachineSetting('machine_type')
502 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
503 self._scene.pushFree(self._selectedObj)
505 if machine == "ultimaker2":
506 #This is bad and Jaime should feel bad!
507 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
508 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
509 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
510 self._scene.pushFree(self._selectedObj)
512 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
513 self._scene.pushFree(self._selectedObj)
514 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
515 self._scene.pushFree(self._selectedObj)
516 self._selectObject(self._selectedObj)
517 self.updateProfileToControls()
520 def OnMirror(self, axis):
521 if self._selectedObj is None:
523 self._selectedObj.mirror(axis)
526 def OnScaleEntry(self, value, axis):
527 if self._selectedObj is None:
533 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
534 self.updateProfileToControls()
535 self._scene.pushFree(self._selectedObj)
536 self._selectObject(self._selectedObj)
539 def OnScaleEntryMM(self, value, axis):
540 if self._selectedObj is None:
546 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
547 self.updateProfileToControls()
548 self._scene.pushFree(self._selectedObj)
549 self._selectObject(self._selectedObj)
552 def OnDeleteAll(self, e):
553 while len(self._scene.objects()) > 0:
554 self._deleteObject(self._scene.objects()[0])
555 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
556 self._engineResultView.setResult(None)
557 self.viewSelection.setDisabled(True)
558 self.printButton.setDisabled(True)
560 def OnMultiply(self, e):
561 if self._focusObj is None:
564 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
565 if dlg.ShowModal() != wx.ID_OK:
571 # 0:unrequested arrange all. Objects should not move.
572 # 1:requested arrange all but refused.
573 # 2:arrange all and center from now on.
574 requestedArrangeAll = 0
580 self._scene.add(newObj)
581 if requestedArrangeAll == 2:
582 self._scene.centerAll()
584 if not self._scene.checkPlatform(newObj):
585 if requestedArrangeAll == 0:
586 requestedArrangeAll = 1
587 dlg = wx.MessageDialog(self, _("Cannot fit all the requested duplicates. Do you want to try and reset object positions?"), _("Reset Positions"), wx.YES_NO)
589 if dlg.ShowModal() == wx.ID_YES:
591 requestedArrangeAll = 2
592 self._scene.remove(newObj)
593 self.OnResetPositions(None)
603 self.notification.message(_("Could not create more than %d items") % (n - 1))
604 self._scene.remove(newObj)
605 if requestedArrangeAll == 2:
606 self._scene.centerAll()
610 def OnSplitObject(self, e):
611 if self._focusObj is None:
613 self._scene.remove(self._focusObj)
614 for obj in self._focusObj.split(self._splitCallback):
615 if numpy.max(obj.getSize()) > 2.0:
617 self._scene.centerAll()
618 self._selectObject(None)
621 def OnCenter(self, e):
622 if self._focusObj is None:
624 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
625 self._scene.pushFree(self._selectedObj)
626 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
627 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
630 def _splitCallback(self, progress):
633 def OnMergeObjects(self, e):
634 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
635 if len(self._scene.objects()) == 2:
636 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
639 self._scene.merge(self._selectedObj, self._focusObj)
642 def sceneUpdated(self):
644 objectSink = profile.getProfileSettingFloat("object_sink")
645 if self._lastObjectSink != objectSink:
646 self._lastObjectSink = objectSink
647 self._scene.updateHeadSize()
649 wx.CallAfter(self._sceneUpdateTimer.Start, 500, True)
650 self._engine.abortEngine()
651 self._scene.updateSizeOffsets()
654 def _onRunEngine(self, e):
655 if self._isSimpleMode:
656 self._engine.runEngine(self._scene, self.GetTopLevelParent().simpleSettingsPanel.getSettingOverrides())
658 self._engine.runEngine(self._scene)
660 def _updateEngineProgress(self, progressValue):
661 result = self._engine.getResult()
662 finished = result is not None and result.isFinished()
664 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
666 self.printButton.setDisabled(not finished)
667 self.viewSelection.setDisabled(not finished)
668 if progressValue >= 0.0:
669 self.printButton.setProgressBar(progressValue)
671 self.printButton.setProgressBar(None)
672 self._engineResultView.setResult(result)
674 self.printButton.setProgressBar(None)
675 text = '%s' % (result.getPrintTime())
676 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
677 amount = result.getFilamentAmount(e)
680 text += '\n%s' % (amount)
681 cost = result.getFilamentCost(e)
683 text += '\n%s' % (cost)
684 self.printButton.setBottomText(text)
686 self.printButton.setBottomText('')
689 def loadScene(self, fileList, pms_transforms=None):
691 for filename in fileList:
694 ext = os.path.splitext(filename)[1].lower()
695 if ext in imageToMesh.supportedExtensions():
696 imageToMesh.convertImageDialog(self, filename).Show()
699 objList = meshLoader.loadMeshes(filename)
701 traceback.print_exc()
704 if self._objectLoadShader is not None:
705 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
709 if pms_transforms is not None and len(pms_transforms) == len(fileList):
710 obj.setPosition(pms_transforms[objIndex][0])
711 obj.applyMatrix(pms_transforms[objIndex][1])
712 obj.setScale(pms_transforms[objIndex][2][0], 0, False)
713 obj.setScale(pms_transforms[objIndex][2][1], 1, False)
714 obj.setScale(pms_transforms[objIndex][2][2], 2, False)
716 if not self._scene.checkPlatform(obj):
717 self._scene.centerAll()
718 self._selectObject(obj)
719 if obj.getScale()[0] < 1.0:
720 self.notification.message(_("Warning: Object scaled down."))
723 def _deleteObject(self, obj):
724 if obj == self._selectedObj:
725 self._selectObject(None)
726 if obj == self._focusObj:
727 self._focusObj = None
728 self._scene.remove(obj)
729 for m in obj._meshList:
730 if m.vbo is not None and m.vbo.decRef():
731 self.glReleaseList.append(m.vbo)
732 if len(self._scene.objects()) == 0:
733 self._engineResultView.setResult(None)
734 self.printButton.setDisabled(True)
735 self.viewSelection.setDisabled(True)
736 self.printButton.setBottomText('')
741 def _selectObject(self, obj, zoom = True):
742 if obj != self._selectedObj:
743 self._selectedObj = obj
744 self.updateModelSettingsToControls()
745 self.updateToolButtons()
746 if zoom and obj is not None:
747 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
748 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
749 newZoom = obj.getBoundaryCircle() * 6
750 if newZoom > numpy.max(self._machineSize) * 3:
751 newZoom = numpy.max(self._machineSize) * 3
752 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
754 def updateProfileToControls(self):
755 oldSimpleMode = self._isSimpleMode
756 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
757 if self._isSimpleMode != oldSimpleMode:
758 self._scene.arrangeAll()
760 self._scene.updateSizeOffsets(True)
761 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
762 self._objColors[0] = profile.getPreferenceColour('model_colour')
763 self._objColors[1] = profile.getPreferenceColour('model_colour2')
764 self._objColors[2] = profile.getPreferenceColour('model_colour3')
765 self._objColors[3] = profile.getPreferenceColour('model_colour4')
766 self._scene.updateMachineDimensions()
767 if self._zoom > numpy.max(self._machineSize) * 3:
768 self._animZoom = openglGui.animation(self, self._zoom, numpy.max(self._machineSize) * 3, 0.5)
769 self.updateModelSettingsToControls()
771 def updateModelSettingsToControls(self):
772 if self._selectedObj is not None:
773 scale = self._selectedObj.getScale()
774 size = self._selectedObj.getSize()
775 self.scaleXctrl.setValue(round(scale[0], 2))
776 self.scaleYctrl.setValue(round(scale[1], 2))
777 self.scaleZctrl.setValue(round(scale[2], 2))
778 self.scaleXmmctrl.setValue(round(size[0], 2))
779 self.scaleYmmctrl.setValue(round(size[1], 2))
780 self.scaleZmmctrl.setValue(round(size[2], 2))
782 def OnKeyChar(self, keyCode):
783 if self._engineResultView.OnKeyChar(keyCode):
785 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and sys.platform.startswith("darwin")):
786 if self._selectedObj is not None:
787 self._deleteObject(self._selectedObj)
789 if keyCode == wx.WXK_UP:
790 if wx.GetKeyState(wx.WXK_SHIFT):
797 elif keyCode == wx.WXK_DOWN:
798 if wx.GetKeyState(wx.WXK_SHIFT):
800 if self._zoom > numpy.max(self._machineSize) * 3:
801 self._zoom = numpy.max(self._machineSize) * 3
805 elif keyCode == wx.WXK_LEFT:
808 elif keyCode == wx.WXK_RIGHT:
811 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
816 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
818 if self._zoom > numpy.max(self._machineSize) * 3:
819 self._zoom = numpy.max(self._machineSize) * 3
821 elif keyCode == wx.WXK_HOME:
825 elif keyCode == wx.WXK_PAGEUP:
829 elif keyCode == wx.WXK_PAGEDOWN:
833 elif keyCode == wx.WXK_END:
838 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
839 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
840 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
841 from collections import defaultdict
842 from gc import get_objects
843 self._beforeLeakTest = defaultdict(int)
844 for i in get_objects():
845 self._beforeLeakTest[type(i)] += 1
846 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
847 from collections import defaultdict
848 from gc import get_objects
849 self._afterLeakTest = defaultdict(int)
850 for i in get_objects():
851 self._afterLeakTest[type(i)] += 1
852 for k in self._afterLeakTest:
853 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
854 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
856 def ShaderUpdate(self, v, f):
857 s = openglHelpers.GLShader(v, f)
859 self._objectLoadShader.release()
860 self._objectLoadShader = s
861 for obj in self._scene.objects():
862 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
865 def OnMouseDown(self,e):
866 self._mouseX = e.GetX()
867 self._mouseY = e.GetY()
868 self._mouseClick3DPos = self._mouse3Dpos
869 self._mouseClickFocus = self._focusObj
871 self._mouseState = 'doubleClick'
873 if self._mouseState == 'dragObject' and self._selectedObj is not None:
874 self._scene.pushFree(self._selectedObj)
876 self._mouseState = 'dragOrClick'
877 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
878 p0 -= self.getObjectCenterPos() - self._viewTarget
879 p1 -= self.getObjectCenterPos() - self._viewTarget
880 if self.tool.OnDragStart(p0, p1):
881 self._mouseState = 'tool'
882 if self._mouseState == 'dragOrClick':
883 if e.GetButton() == 1:
884 if self._focusObj is not None:
885 self._selectObject(self._focusObj, False)
888 def OnMouseUp(self, e):
889 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
891 if self._mouseState == 'dragOrClick':
892 if e.GetButton() == 1:
893 self._selectObject(self._focusObj)
894 if e.GetButton() == 3:
896 if self._focusObj is not None:
898 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
899 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
900 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
901 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
903 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:
904 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
905 if len(self._scene.objects()) > 0:
906 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
907 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
908 self.Bind(wx.EVT_MENU, self.OnResetPositions, menu.Append(-1, _("Reset all objects positions")))
909 self.Bind(wx.EVT_MENU, self.OnResetTransformations, menu.Append(-1, _("Reset all objects transformations")))
911 if menu.MenuItemCount > 0:
914 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
915 self._scene.pushFree(self._selectedObj)
917 elif self._mouseState == 'tool':
918 if self.tempMatrix is not None and self._selectedObj is not None:
919 self._selectedObj.applyMatrix(self.tempMatrix)
920 self._scene.pushFree(self._selectedObj)
921 self._selectObject(self._selectedObj)
922 self.tempMatrix = None
923 self.tool.OnDragEnd()
925 self._mouseState = None
927 def OnMouseMotion(self,e):
928 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
929 p0 -= self.getObjectCenterPos() - self._viewTarget
930 p1 -= self.getObjectCenterPos() - self._viewTarget
932 if e.Dragging() and self._mouseState is not None:
933 if self._mouseState == 'tool':
934 self.tool.OnDrag(p0, p1)
935 elif not e.LeftIsDown() and e.RightIsDown():
936 self._mouseState = 'drag'
937 if wx.GetKeyState(wx.WXK_SHIFT):
938 a = math.cos(math.radians(self._yaw)) / 3.0
939 b = math.sin(math.radians(self._yaw)) / 3.0
940 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
941 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
942 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
943 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
945 self._yaw += e.GetX() - self._mouseX
946 self._pitch -= e.GetY() - self._mouseY
947 if self._pitch > 170:
951 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
952 self._mouseState = 'drag'
953 self._zoom += e.GetY() - self._mouseY
956 if self._zoom > numpy.max(self._machineSize) * 3:
957 self._zoom = numpy.max(self._machineSize) * 3
958 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
959 self._mouseState = 'dragObject'
960 z = max(0, self._mouseClick3DPos[2])
961 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
962 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
967 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
968 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
969 diff = cursorZ1 - cursorZ0
970 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
971 if not e.Dragging() or self._mouseState != 'tool':
972 self.tool.OnMouseMove(p0, p1)
974 self._mouseX = e.GetX()
975 self._mouseY = e.GetY()
977 def OnMouseWheel(self, e):
978 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
979 delta = max(min(delta,4),-4)
980 self._zoom *= 1.0 - delta / 10.0
983 if self._zoom > numpy.max(self._machineSize) * 3:
984 self._zoom = numpy.max(self._machineSize) * 3
987 def OnMouseLeave(self, e):
991 def getMouseRay(self, x, y):
992 if self._viewport is None:
993 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
994 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
995 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
996 p0 -= self._viewTarget
997 p1 -= self._viewTarget
1000 def _init3DView(self):
1001 # set viewing projection
1002 size = self.GetSize()
1003 glViewport(0, 0, size.GetWidth(), size.GetHeight())
1006 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
1008 glDisable(GL_RESCALE_NORMAL)
1009 glDisable(GL_LIGHTING)
1010 glDisable(GL_LIGHT0)
1011 glEnable(GL_DEPTH_TEST)
1012 glDisable(GL_CULL_FACE)
1014 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1016 #scene background color
1017 glClearColor(0.85, 0.85, 0.85, 1.0)
1021 glMatrixMode(GL_PROJECTION)
1023 aspect = float(size.GetWidth()) / float(size.GetHeight())
1024 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
1026 glMatrixMode(GL_MODELVIEW)
1028 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1030 def OnPaint(self,e):
1031 connectionGroup = self._printerConnectionManager.getAvailableGroup()
1032 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
1033 self.printButton._imageID = 2
1034 self.printButton._tooltip = _("Toolpath to SD")
1035 elif connectionGroup is not None:
1036 self.printButton._imageID = connectionGroup.getIconID()
1037 #self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
1038 self.printButton._tooltip = _("Print/Control")
1040 self.printButton._imageID = 3
1041 self.printButton._tooltip = _("Save toolpath")
1043 if self._animView is not None:
1044 self._viewTarget = self._animView.getPosition()
1045 if self._animView.isDone():
1046 self._animView = None
1047 if self._animZoom is not None:
1048 self._zoom = self._animZoom.getPosition()
1049 if self._animZoom.isDone():
1050 self._animZoom = None
1051 if self._objectShader is None: #TODO: add loading shaders from file(s)
1052 if openglHelpers.hasShaderSupport():
1053 self._objectShader = openglHelpers.GLShader("""
1054 varying float light_amount;
1058 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1059 gl_FrontColor = gl_Color;
1061 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1062 light_amount += 0.2;
1065 varying float light_amount;
1069 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1072 self._objectOverhangShader = openglHelpers.GLShader("""
1073 uniform float cosAngle;
1074 uniform mat3 rotMatrix;
1075 varying float light_amount;
1079 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1080 gl_FrontColor = gl_Color;
1082 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1083 light_amount += 0.2;
1084 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
1086 light_amount = -10.0;
1090 varying float light_amount;
1094 if (light_amount == -10.0)
1096 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1098 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1102 self._objectLoadShader = openglHelpers.GLShader("""
1103 uniform float intensity;
1104 uniform float scale;
1105 varying float light_amount;
1109 vec4 tmp = gl_Vertex;
1110 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1111 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1112 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1113 gl_FrontColor = gl_Color;
1115 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1116 light_amount += 0.2;
1119 uniform float intensity;
1120 varying float light_amount;
1124 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1127 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
1128 self._objectShader = openglHelpers.GLFakeShader()
1129 self._objectOverhangShader = openglHelpers.GLFakeShader()
1130 self._objectLoadShader = None
1132 glTranslate(0,0,-self._zoom)
1133 glRotate(-self._pitch, 1,0,0)
1134 glRotate(self._yaw, 0,0,1)
1135 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1137 self._viewport = glGetIntegerv(GL_VIEWPORT)
1138 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1139 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1141 glClearColor(1,1,1,1)
1142 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1144 if self.viewMode != 'gcode':
1145 for n in xrange(0, len(self._scene.objects())):
1146 obj = self._scene.objects()[n]
1147 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1148 self._renderObject(obj)
1150 if self._mouseX > -1: # mouse has not passed over the opengl window.
1152 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1153 if n < len(self._scene.objects()):
1154 self._focusObj = self._scene.objects()[n]
1156 self._focusObj = None
1157 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1158 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1159 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1160 self._mouse3Dpos -= self._viewTarget
1163 glTranslate(0,0,-self._zoom)
1164 glRotate(-self._pitch, 1,0,0)
1165 glRotate(self._yaw, 0,0,1)
1166 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1168 self._objectShader.unbind()
1169 self._engineResultView.OnDraw()
1170 if self.viewMode != 'gcode':
1171 glStencilFunc(GL_ALWAYS, 1, 1)
1172 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1174 if self.viewMode == 'overhang':
1175 self._objectOverhangShader.bind()
1176 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1178 self._objectShader.bind()
1179 for obj in self._scene.objects():
1180 if obj._loadAnim is not None:
1181 if obj._loadAnim.isDone():
1182 obj._loadAnim = None
1186 if self._focusObj == obj:
1188 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1191 if self._selectedObj == obj or self._selectedObj is None:
1192 #If we want transparent, then first render a solid black model to remove the printer size lines.
1193 if self.viewMode == 'transparent':
1194 glColor4f(0, 0, 0, 0)
1195 self._renderObject(obj)
1197 glBlendFunc(GL_ONE, GL_ONE)
1198 glDisable(GL_DEPTH_TEST)
1200 if self.viewMode == 'xray':
1201 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1202 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1203 glEnable(GL_STENCIL_TEST)
1205 if self.viewMode == 'overhang':
1206 if self._selectedObj == obj and self.tempMatrix is not None:
1207 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1209 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1211 if not self._scene.checkPlatform(obj):
1212 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1213 self._renderObject(obj)
1215 self._renderObject(obj, brightness)
1216 glDisable(GL_STENCIL_TEST)
1218 glEnable(GL_DEPTH_TEST)
1219 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1221 if self.viewMode == 'xray':
1224 glEnable(GL_STENCIL_TEST)
1225 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1226 glDisable(GL_DEPTH_TEST)
1227 for i in xrange(2, 15, 2): #All even values
1228 glStencilFunc(GL_EQUAL, i, 0xFF)
1229 glColor(float(i)/10, float(i)/10, float(i)/5)
1231 glVertex3f(-1000,-1000,-10)
1232 glVertex3f( 1000,-1000,-10)
1233 glVertex3f( 1000, 1000,-10)
1234 glVertex3f(-1000, 1000,-10)
1236 for i in xrange(1, 15, 2): #All odd values
1237 glStencilFunc(GL_EQUAL, i, 0xFF)
1238 glColor(float(i)/10, 0, 0)
1240 glVertex3f(-1000,-1000,-10)
1241 glVertex3f( 1000,-1000,-10)
1242 glVertex3f( 1000, 1000,-10)
1243 glVertex3f(-1000, 1000,-10)
1246 glDisable(GL_STENCIL_TEST)
1247 glEnable(GL_DEPTH_TEST)
1249 self._objectShader.unbind()
1251 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1253 if self._objectLoadShader is not None:
1254 self._objectLoadShader.bind()
1255 #Model color during load animation
1256 glColor4ub(177, 205, 54, 255)
1257 for obj in self._scene.objects():
1258 if obj._loadAnim is None:
1260 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1261 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1262 self._renderObject(obj)
1263 self._objectLoadShader.unbind()
1268 if self.viewMode != 'gcode':
1269 #Draw the object box-shadow, so you can see where it will collide with other objects.
1270 if self._selectedObj is not None:
1272 glEnable(GL_CULL_FACE)
1273 glColor4f(0,0,0,0.16)
1275 for obj in self._scene.objects():
1277 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1278 glBegin(GL_TRIANGLE_FAN)
1279 for p in obj._boundaryHull[::-1]:
1280 glVertex3f(p[0], p[1], 0)
1283 if self._scene.isOneAtATime(): #Check print sequence mode.
1285 glColor4f(0,0,0,0.06)
1286 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1287 glBegin(GL_TRIANGLE_FAN)
1288 for p in self._selectedObj._printAreaHull[::-1]:
1289 glVertex3f(p[0], p[1], 0)
1291 glBegin(GL_TRIANGLE_FAN)
1292 for p in self._selectedObj._headAreaMinHull[::-1]:
1293 glVertex3f(p[0], p[1], 0)
1297 glDisable(GL_CULL_FACE)
1299 #Draw the outline of the selected object on top of everything else except the GUI.
1300 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1301 glDisable(GL_DEPTH_TEST)
1302 glEnable(GL_CULL_FACE)
1303 glEnable(GL_STENCIL_TEST)
1305 glStencilFunc(GL_EQUAL, 0, 255)
1307 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1309 glColor4f(1,1,1,0.5)
1310 self._renderObject(self._selectedObj)
1311 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1313 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1314 glDisable(GL_STENCIL_TEST)
1315 glDisable(GL_CULL_FACE)
1316 glEnable(GL_DEPTH_TEST)
1318 if self._selectedObj is not None:
1320 pos = self.getObjectCenterPos()
1321 glTranslate(pos[0], pos[1], pos[2])
1324 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1325 glDisable(GL_DEPTH_TEST)
1328 glTranslate(0,-4,-10)
1329 glColor4ub(60,60,60,255)
1330 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1333 def _renderObject(self, obj, brightness = 0, addSink = True):
1336 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1338 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1340 if self.tempMatrix is not None and obj == self._selectedObj:
1341 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1343 offset = obj.getDrawOffset()
1344 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1346 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1349 for m in obj._meshList:
1351 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1353 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1358 def _drawMachine(self):
1359 glEnable(GL_CULL_FACE)
1362 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1364 #Due to NC licensing of the stl files, temporarily removing platform mesh loading for Ultimaker and Witbox
1365 '''machine_type = profile.getMachineSetting('machine_type')
1366 if machine_type not in self._platformMesh:
1367 self._platformMesh[machine_type] = None
1372 texture_offset = [0,0,0]
1374 if machine_type == 'ultimaker2' or machine_type == 'ultimaker2extended':
1375 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1376 offset = [-9,-37,145]
1377 texture_name = 'Ultimaker2backplate.png'
1378 texture_offset = [9,150,-5]
1379 elif machine_type == 'ultimaker2go':
1380 filename = resources.getPathForMesh('ultimaker2go_platform.stl')
1381 offset = [0,-42,145]
1382 texture_offset = [0,105,-5]
1383 texture_name = 'Ultimaker2backplate.png'
1385 elif machine_type == 'ultimaker_plus':
1386 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1387 offset = [0,-37,145]
1388 texture_offset = [0,150,-5]
1389 texture_name = 'UltimakerPlusbackplate.png'
1390 elif machine_type == 'ultimaker':
1391 filename = resources.getPathForMesh('ultimaker_platform.stl')
1393 elif machine_type == 'Witbox':
1394 filename = resources.getPathForMesh('Witbox_platform.stl')
1395 offset = [0,-37,145]
1397 if filename is not None:
1398 meshes = meshLoader.loadMeshes(filename)
1400 self._platformMesh[machine_type] = meshes[0]
1401 self._platformMesh[machine_type]._drawOffset = numpy.array(offset, numpy.float32)
1402 self._platformMesh[machine_type].texture = None
1403 if texture_name is not None:
1404 self._platformMesh[machine_type].texture = openglHelpers.loadGLTexture(texture_name)
1405 self._platformMesh[machine_type].texture_offset = texture_offset
1406 self._platformMesh[machine_type].texture_scale = texture_scale
1407 if self._platformMesh[machine_type] is not None:
1408 mesh = self._platformMesh[machine_type]
1409 glColor4f(1,1,1,0.5)
1410 self._objectShader.bind()
1411 self._renderObject(mesh, False, False)
1412 self._objectShader.unbind()
1414 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1415 if mesh.texture is not None:
1416 glBindTexture(GL_TEXTURE_2D, mesh.texture)
1417 glEnable(GL_TEXTURE_2D)
1421 glTranslate(mesh.texture_offset[0], mesh.texture_offset[1], mesh.texture_offset[2])
1422 glScalef(mesh.texture_scale, mesh.texture_scale, mesh.texture_scale)
1427 glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA)
1428 glEnable(GL_ALPHA_TEST)
1429 glAlphaFunc(GL_GREATER, 0.0)
1432 glVertex3f( w, 0, h)
1434 glVertex3f(-w, 0, h)
1436 glVertex3f(-w, 0, 0)
1438 glVertex3f( w, 0, 0)
1441 glVertex3f(-w, d, h)
1443 glVertex3f( w, d, h)
1445 glVertex3f( w, d, 0)
1447 glVertex3f(-w, d, 0)
1449 glDisable(GL_TEXTURE_2D)
1450 glDisable(GL_ALPHA_TEST)
1451 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1454 # until glEnd() goes inside the else
1458 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1459 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1460 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1461 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1462 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1463 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1468 polys = profile.getMachineSizePolygons()
1469 height = profile.getMachineSettingFloat('machine_height')
1470 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1472 # Draw the sides of the build volume.
1473 for n in xrange(0, len(polys[0])):
1476 glColor4ub(210, 235, 103, 100)
1478 glColor4ub(223, 241, 145, 100)
1480 glColor4ub(223, 241, 145, 100)
1482 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1483 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1484 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1485 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1488 #Draw top of build volume.
1489 glColor4ub(183, 209, 90, 100)
1490 glBegin(GL_TRIANGLE_FAN)
1491 for p in polys[0][::-1]:
1492 glVertex3f(p[0], p[1], height)
1496 if self._platformTexture is None:
1497 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1498 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1499 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1500 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1501 #Dark checkerboard color
1502 glColor4f(1,1,1,0.7)
1503 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1504 glEnable(GL_TEXTURE_2D)
1505 glBegin(GL_TRIANGLE_FAN)
1507 glTexCoord2f(p[0]/20, p[1]/20)
1508 glVertex3f(p[0], p[1], 0)
1511 #Draw no-go zones. (clips in case of UM2)
1512 glDisable(GL_TEXTURE_2D)
1513 glColor4ub(127, 127, 127, 200)
1514 for poly in polys[1:]:
1515 glBegin(GL_TRIANGLE_FAN)
1517 glTexCoord2f(p[0]/20, p[1]/20)
1518 glVertex3f(p[0], p[1], 0)
1523 glDisable(GL_CULL_FACE)
1525 def getObjectCenterPos(self):
1526 if self._selectedObj is None:
1527 return [0.0, 0.0, 0.0]
1528 pos = self._selectedObj.getPosition()
1529 size = self._selectedObj.getSize()
1530 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1532 def getObjectBoundaryCircle(self):
1533 if self._selectedObj is None:
1535 return self._selectedObj.getBoundaryCircle()
1537 def getObjectSize(self):
1538 if self._selectedObj is None:
1539 return [0.0, 0.0, 0.0]
1540 return self._selectedObj.getSize()
1542 def getObjectMatrix(self):
1543 if self._selectedObj is None:
1544 return numpy.matrix(numpy.identity(3))
1545 return self._selectedObj.getMatrix()
1547 #TODO: Remove this or put it in a seperate file
1548 class shaderEditor(wx.Frame):
1549 def __init__(self, parent, callback, v, f):
1550 super(shaderEditor, self).__init__(parent, title=_("Shader editor"), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1551 self._callback = callback
1552 s = wx.BoxSizer(wx.VERTICAL)
1554 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1555 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1556 s.Add(self._vertex, 1, flag=wx.EXPAND)
1557 s.Add(self._fragment, 1, flag=wx.EXPAND)
1559 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1560 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1562 self.SetPosition(self.GetParent().GetPosition())
1563 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1566 def OnText(self, e):
1567 self._callback(self._vertex.GetValue(), self._fragment.GetValue())