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()
43 self._objectShader = None
44 self._objectLoadShader = None
46 self._selectedObj = None
47 self._objColors = [None,None,None,None]
50 self._mouseState = None
51 self._viewTarget = numpy.array([0,0,0], numpy.float32)
54 self._lastObjectSink = None
55 self._platformMesh = {}
56 self._platformTexture = None
57 self._isSimpleMode = True
58 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
61 self._modelMatrix = None
62 self._projMatrix = None
63 self.tempMatrix = None
65 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
66 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
67 self.printButton.setDisabled(True)
70 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
71 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
72 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
74 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
75 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
77 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
78 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
80 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
81 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
82 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
84 self.rotateToolButton.setExpandArrow(True)
85 self.scaleToolButton.setExpandArrow(True)
86 self.mirrorToolButton.setExpandArrow(True)
88 self.scaleForm = openglGui.glFrame(self, (2, -2))
89 openglGui.glGuiLayoutGrid(self.scaleForm)
90 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
91 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
92 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
93 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
94 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
95 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
96 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
97 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
98 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
99 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
100 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
101 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
102 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
103 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
105 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
107 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
108 self.youMagineButton.setDisabled(True)
110 self.notification = openglGui.glNotification(self, (0, 0))
112 self._engine = sliceEngine.Engine(self._updateEngineProgress)
113 self._engineResultView = engineResultView.engineResultView(self)
114 self._sceneUpdateTimer = wx.Timer(self)
115 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
116 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
117 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
121 self.updateToolButtons()
122 self.updateProfileToControls()
124 def loadGCodeFile(self, filename):
125 self.OnDeleteAll(None)
126 #Cheat the engine results to load a GCode file into it.
127 self._engine._result = sliceEngine.EngineResult()
128 with open(filename, "r") as f:
129 self._engine._result.setGCode(f.read())
130 self._engine._result.setFinished(True)
131 self._engineResultView.setResult(self._engine._result)
132 self.printButton.setBottomText('')
133 self.viewSelection.setValue(4)
134 self.printButton.setDisabled(False)
135 self.youMagineButton.setDisabled(True)
138 def loadSceneFiles(self, filenames):
139 self.youMagineButton.setDisabled(False)
140 #if self.viewSelection.getValue() == 4:
141 # self.viewSelection.setValue(0)
142 # self.OnViewChange()
143 self.loadScene(filenames)
145 def loadFiles(self, filenames):
146 mainWindow = self.GetParent().GetParent().GetParent()
147 # only one GCODE file can be active
148 # so if single gcode file, process this
149 # otherwise ignore all gcode files
151 if len(filenames) == 1:
152 filename = filenames[0]
153 ext = os.path.splitext(filename)[1].lower()
154 if ext == '.g' or ext == '.gcode':
155 gcodeFilename = filename
156 mainWindow.addToModelMRU(filename)
157 if gcodeFilename is not None:
158 self.loadGCodeFile(gcodeFilename)
160 # process directories and special file types
161 # and keep scene files for later processing
163 ignored_types = dict()
164 # use file list as queue
165 # pop first entry for processing and append new files at end
167 filename = filenames.pop(0)
168 if os.path.isdir(filename):
169 # directory: queue all included files and directories
170 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
172 ext = os.path.splitext(filename)[1].lower()
174 profile.loadProfile(filename)
175 mainWindow.addToProfileMRU(filename)
176 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
177 scene_filenames.append(filename)
178 mainWindow.addToModelMRU(filename)
180 ignored_types[ext] = 1
182 ignored_types = ignored_types.keys()
184 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
185 mainWindow.updateProfileToAllControls()
186 # now process all the scene files
188 self.loadSceneFiles(scene_filenames)
189 self._selectObject(None)
191 newZoom = numpy.max(self._machineSize)
192 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
193 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
195 def reloadScene(self, e):
196 # Copy the list before DeleteAll clears it
198 for obj in self._scene.objects():
199 fileList.append(obj.getOriginFilename())
200 self.OnDeleteAll(None)
201 self.loadScene(fileList)
203 def showLoadModel(self, button = 1):
205 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)
207 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
208 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
209 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
210 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
211 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
212 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
213 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
214 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
216 dlg.SetWildcard(wildcardFilter)
217 if dlg.ShowModal() != wx.ID_OK:
220 filenames = dlg.GetPaths()
222 if len(filenames) < 1:
224 profile.putPreference('lastFile', filenames[0])
225 self.loadFiles(filenames)
227 def showSaveModel(self):
228 if len(self._scene.objects()) < 1:
230 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
231 fileExtensions = meshLoader.saveSupportedExtensions()
232 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
233 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
234 dlg.SetWildcard(wildcardFilter)
235 if dlg.ShowModal() != wx.ID_OK:
238 filename = dlg.GetPath()
240 meshLoader.saveMeshes(filename, self._scene.objects())
242 def OnPrintButton(self, button):
244 connectionGroup = self._printerConnectionManager.getAvailableGroup()
245 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
246 drives = removableStorage.getPossibleSDcardDrives()
248 dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
249 if dlg.ShowModal() != wx.ID_OK:
252 drive = drives[dlg.GetSelection()]
256 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
258 #check if the file is part of the root folder. If so, create folders on sd card to get the same folder hierarchy.
259 repDir = profile.getPreference("sdcard_rootfolder")
260 if os.path.exists(repDir) and os.path.isdir(repDir):
261 repDir = os.path.abspath(repDir)
262 originFilename = os.path.abspath( self._scene._objectList[0].getOriginFilename() )
263 if os.path.dirname(originFilename).startswith(repDir):
264 filename = os.path.splitext(originFilename[len(repDir):])[0] + profile.getGCodeExtension()
265 sdPath = os.path.dirname(os.path.join( drive[1], filename))
266 if not os.path.exists(sdPath):
267 print "Creating replication directory:", sdPath
270 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
271 elif connectionGroup is not None:
272 connections = connectionGroup.getAvailableConnections()
273 if len(connections) < 2:
274 connection = connections[0]
276 dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
277 if dlg.ShowModal() != wx.ID_OK:
280 connection = connections[dlg.GetSelection()]
282 self._openPrintWindowForConnection(connection)
287 connections = self._printerConnectionManager.getAvailableConnections()
288 menu.connectionMap = {}
289 for connection in connections:
290 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
291 menu.connectionMap[i.GetId()] = connection
292 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
293 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
294 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
298 def _openPrintWindowForConnection(self, connection):
299 if connection.window is None or not connection.window:
300 connection.window = None
301 windowType = profile.getPreference('printing_window')
302 for p in pluginInfo.getPluginList('printwindow'):
303 if p.getName() == windowType:
304 connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
306 if connection.window is None:
307 connection.window = printWindow.printWindowBasic(self, connection)
308 connection.window.Show()
309 connection.window.Raise()
310 if not connection.loadGCodeData(self._engine.getResult().getGCode()):
311 if connection.isPrinting():
312 self.notification.message("Cannot start print, because other print still running.")
314 self.notification.message("Failed to start print...")
316 def showSaveGCode(self):
317 if len(self._scene._objectList) < 1:
319 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
320 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
321 dlg.SetFilename(filename)
322 dlg.SetWildcard('Toolpath (*%s)|*%s;*%s' % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
323 if dlg.ShowModal() != wx.ID_OK:
326 filename = dlg.GetPath()
329 threading.Thread(target=self._saveGCode,args=(filename,)).start()
331 def _saveGCode(self, targetFilename, ejectDrive = False):
332 gcode = self._engine.getResult().getGCode()
334 size = float(len(gcode))
336 with open(targetFilename, 'wb') as fdst:
338 buf = gcode.read(16*1024)
343 self.printButton.setProgressBar(read_pos / size)
346 import sys, traceback
347 traceback.print_exc()
348 self.notification.message("Failed to save")
351 self.notification.message("Saved as %s" % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
352 elif explorer.hasExplorer():
353 self.notification.message("Saved as %s" % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, 'Open folder')
355 self.notification.message("Saved as %s" % (targetFilename))
356 self.printButton.setProgressBar(None)
357 self._engine.getResult().submitInfoOnline()
359 def _doEjectSD(self, drive):
360 if removableStorage.ejectDrive(drive):
361 self.notification.message('You can now eject the card.')
363 self.notification.message('Safe remove failed...')
365 def _showEngineLog(self):
366 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)
370 def OnToolSelect(self, button):
371 if self.rotateToolButton.getSelected():
372 self.tool = previewTools.toolRotate(self)
373 elif self.scaleToolButton.getSelected():
374 self.tool = previewTools.toolScale(self)
375 elif self.mirrorToolButton.getSelected():
376 self.tool = previewTools.toolNone(self)
378 self.tool = previewTools.toolNone(self)
379 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
380 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
381 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
382 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
383 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
384 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
385 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
386 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
388 def updateToolButtons(self):
389 if self._selectedObj is None:
393 self.rotateToolButton.setHidden(hidden)
394 self.scaleToolButton.setHidden(hidden)
395 self.mirrorToolButton.setHidden(hidden)
397 self.rotateToolButton.setSelected(False)
398 self.scaleToolButton.setSelected(False)
399 self.mirrorToolButton.setSelected(False)
402 def OnViewChange(self):
403 if self.viewSelection.getValue() == 4:
404 self.viewMode = 'gcode'
405 self.tool = previewTools.toolNone(self)
406 elif self.viewSelection.getValue() == 1:
407 self.viewMode = 'overhang'
408 elif self.viewSelection.getValue() == 2:
409 self.viewMode = 'transparent'
410 elif self.viewSelection.getValue() == 3:
411 self.viewMode = 'xray'
413 self.viewMode = 'normal'
414 self._engineResultView.setEnabled(self.viewMode == 'gcode')
417 def OnRotateReset(self, button):
418 if self._selectedObj is None:
420 self._selectedObj.resetRotation()
421 self._scene.pushFree(self._selectedObj)
422 self._selectObject(self._selectedObj)
425 def OnLayFlat(self, button):
426 if self._selectedObj is None:
428 self._selectedObj.layFlat()
429 self._scene.pushFree(self._selectedObj)
430 self._selectObject(self._selectedObj)
433 def OnScaleReset(self, button):
434 if self._selectedObj is None:
436 self._selectedObj.resetScale()
437 self._selectObject(self._selectedObj)
438 self.updateProfileToControls()
441 def OnScaleMax(self, button):
442 if self._selectedObj is None:
444 machine = profile.getMachineSetting('machine_type')
445 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
446 self._scene.pushFree(self._selectedObj)
448 if machine == "ultimaker2":
449 #This is bad and Jaime should feel bad!
450 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
451 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
452 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
453 self._scene.pushFree(self._selectedObj)
455 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
456 self._scene.pushFree(self._selectedObj)
457 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
458 self._scene.pushFree(self._selectedObj)
459 self._selectObject(self._selectedObj)
460 self.updateProfileToControls()
463 def OnMirror(self, axis):
464 if self._selectedObj is None:
466 self._selectedObj.mirror(axis)
469 def OnScaleEntry(self, value, axis):
470 if self._selectedObj is None:
476 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
477 self.updateProfileToControls()
478 self._scene.pushFree(self._selectedObj)
479 self._selectObject(self._selectedObj)
482 def OnScaleEntryMM(self, value, axis):
483 if self._selectedObj is None:
489 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
490 self.updateProfileToControls()
491 self._scene.pushFree(self._selectedObj)
492 self._selectObject(self._selectedObj)
495 def OnDeleteAll(self, e):
496 while len(self._scene.objects()) > 0:
497 self._deleteObject(self._scene.objects()[0])
498 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
499 self._engineResultView.setResult(None)
501 def OnMultiply(self, e):
502 if self._focusObj is None:
505 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
506 if dlg.ShowModal() != wx.ID_OK:
515 self._scene.add(newObj)
516 self._scene.centerAll()
517 if not self._scene.checkPlatform(newObj):
522 self.notification.message("Could not create more than %d items" % (n - 1))
523 self._scene.remove(newObj)
524 self._scene.centerAll()
527 def OnSplitObject(self, e):
528 if self._focusObj is None:
530 self._scene.remove(self._focusObj)
531 for obj in self._focusObj.split(self._splitCallback):
532 if numpy.max(obj.getSize()) > 2.0:
534 self._scene.centerAll()
535 self._selectObject(None)
538 def OnCenter(self, e):
539 if self._focusObj is None:
541 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
542 self._scene.pushFree(self._selectedObj)
543 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
544 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
547 def _splitCallback(self, progress):
550 def OnMergeObjects(self, e):
551 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
552 if len(self._scene.objects()) == 2:
553 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
556 self._scene.merge(self._selectedObj, self._focusObj)
559 def sceneUpdated(self):
561 objectSink = profile.getProfileSettingFloat("object_sink")
562 if self._lastObjectSink != objectSink:
563 self._lastObjectSink = objectSink
564 self._scene.updateHeadSize()
566 wx.CallAfter(self._sceneUpdateTimer.Start, 500, True)
567 self._engine.abortEngine()
568 self._scene.updateSizeOffsets()
571 def _onRunEngine(self, e):
572 if self._isSimpleMode:
573 self._engine.runEngine(self._scene, self.GetTopLevelParent().simpleSettingsPanel.getSettingOverrides())
575 self._engine.runEngine(self._scene)
577 def _updateEngineProgress(self, progressValue):
578 result = self._engine.getResult()
579 finished = result is not None and result.isFinished()
581 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
583 self.printButton.setDisabled(not finished)
584 if progressValue >= 0.0:
585 self.printButton.setProgressBar(progressValue)
587 self.printButton.setProgressBar(None)
588 self._engineResultView.setResult(result)
590 self.printButton.setProgressBar(None)
591 text = '%s' % (result.getPrintTime())
592 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
593 amount = result.getFilamentAmount(e)
596 text += '\n%s' % (amount)
597 cost = result.getFilamentCost(e)
599 text += '\n%s' % (cost)
600 self.printButton.setBottomText(text)
602 self.printButton.setBottomText('')
605 def loadScene(self, fileList):
606 for filename in fileList:
608 ext = os.path.splitext(filename)[1].lower()
609 if ext in imageToMesh.supportedExtensions():
610 imageToMesh.convertImageDialog(self, filename).Show()
613 objList = meshLoader.loadMeshes(filename)
615 traceback.print_exc()
618 if self._objectLoadShader is not None:
619 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
623 if not self._scene.checkPlatform(obj):
624 self._scene.centerAll()
625 self._selectObject(obj)
626 if obj.getScale()[0] < 1.0:
627 self.notification.message("Warning: Object scaled down.")
630 def _deleteObject(self, obj):
631 if obj == self._selectedObj:
632 self._selectObject(None)
633 if obj == self._focusObj:
634 self._focusObj = None
635 self._scene.remove(obj)
636 for m in obj._meshList:
637 if m.vbo is not None and m.vbo.decRef():
638 self.glReleaseList.append(m.vbo)
639 if len(self._scene.objects()) == 0:
640 self._engineResultView.setResult(None)
645 def _selectObject(self, obj, zoom = True):
646 if obj != self._selectedObj:
647 self._selectedObj = obj
648 self.updateModelSettingsToControls()
649 self.updateToolButtons()
650 if zoom and obj is not None:
651 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
652 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
653 newZoom = obj.getBoundaryCircle() * 6
654 if newZoom > numpy.max(self._machineSize) * 3:
655 newZoom = numpy.max(self._machineSize) * 3
656 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
658 def updateProfileToControls(self):
659 oldSimpleMode = self._isSimpleMode
660 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
661 if self._isSimpleMode != oldSimpleMode:
662 self._scene.arrangeAll()
664 self._scene.updateSizeOffsets(True)
665 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
666 self._objColors[0] = profile.getPreferenceColour('model_colour')
667 self._objColors[1] = profile.getPreferenceColour('model_colour2')
668 self._objColors[2] = profile.getPreferenceColour('model_colour3')
669 self._objColors[3] = profile.getPreferenceColour('model_colour4')
670 self._scene.updateMachineDimensions()
671 if self._zoom > numpy.max(self._machineSize) * 3:
672 self._animZoom = openglGui.animation(self, self._zoom, numpy.max(self._machineSize) * 3, 0.5)
673 self.updateModelSettingsToControls()
675 def updateModelSettingsToControls(self):
676 if self._selectedObj is not None:
677 scale = self._selectedObj.getScale()
678 size = self._selectedObj.getSize()
679 self.scaleXctrl.setValue(round(scale[0], 2))
680 self.scaleYctrl.setValue(round(scale[1], 2))
681 self.scaleZctrl.setValue(round(scale[2], 2))
682 self.scaleXmmctrl.setValue(round(size[0], 2))
683 self.scaleYmmctrl.setValue(round(size[1], 2))
684 self.scaleZmmctrl.setValue(round(size[2], 2))
686 def OnKeyChar(self, keyCode):
687 if self._engineResultView.OnKeyChar(keyCode):
689 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and sys.platform.startswith("darwin")):
690 if self._selectedObj is not None:
691 self._deleteObject(self._selectedObj)
693 if keyCode == wx.WXK_UP:
694 if wx.GetKeyState(wx.WXK_SHIFT):
701 elif keyCode == wx.WXK_DOWN:
702 if wx.GetKeyState(wx.WXK_SHIFT):
704 if self._zoom > numpy.max(self._machineSize) * 3:
705 self._zoom = numpy.max(self._machineSize) * 3
709 elif keyCode == wx.WXK_LEFT:
712 elif keyCode == wx.WXK_RIGHT:
715 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
720 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
722 if self._zoom > numpy.max(self._machineSize) * 3:
723 self._zoom = numpy.max(self._machineSize) * 3
725 elif keyCode == wx.WXK_HOME:
729 elif keyCode == wx.WXK_PAGEUP:
733 elif keyCode == wx.WXK_PAGEDOWN:
737 elif keyCode == wx.WXK_END:
742 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
743 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
744 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
745 from collections import defaultdict
746 from gc import get_objects
747 self._beforeLeakTest = defaultdict(int)
748 for i in get_objects():
749 self._beforeLeakTest[type(i)] += 1
750 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
751 from collections import defaultdict
752 from gc import get_objects
753 self._afterLeakTest = defaultdict(int)
754 for i in get_objects():
755 self._afterLeakTest[type(i)] += 1
756 for k in self._afterLeakTest:
757 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
758 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
760 def ShaderUpdate(self, v, f):
761 s = openglHelpers.GLShader(v, f)
763 self._objectLoadShader.release()
764 self._objectLoadShader = s
765 for obj in self._scene.objects():
766 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
769 def OnMouseDown(self,e):
770 self._mouseX = e.GetX()
771 self._mouseY = e.GetY()
772 self._mouseClick3DPos = self._mouse3Dpos
773 self._mouseClickFocus = self._focusObj
775 self._mouseState = 'doubleClick'
777 if self._mouseState == 'dragObject' and self._selectedObj is not None:
778 self._scene.pushFree(self._selectedObj)
780 self._mouseState = 'dragOrClick'
781 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
782 p0 -= self.getObjectCenterPos() - self._viewTarget
783 p1 -= self.getObjectCenterPos() - self._viewTarget
784 if self.tool.OnDragStart(p0, p1):
785 self._mouseState = 'tool'
786 if self._mouseState == 'dragOrClick':
787 if e.GetButton() == 1:
788 if self._focusObj is not None:
789 self._selectObject(self._focusObj, False)
792 def OnMouseUp(self, e):
793 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
795 if self._mouseState == 'dragOrClick':
796 if e.GetButton() == 1:
797 self._selectObject(self._focusObj)
798 if e.GetButton() == 3:
800 if self._focusObj is not None:
802 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
803 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
804 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
805 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
806 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:
807 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
808 if len(self._scene.objects()) > 0:
809 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
810 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
811 if menu.MenuItemCount > 0:
814 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
815 self._scene.pushFree(self._selectedObj)
817 elif self._mouseState == 'tool':
818 if self.tempMatrix is not None and self._selectedObj is not None:
819 self._selectedObj.applyMatrix(self.tempMatrix)
820 self._scene.pushFree(self._selectedObj)
821 self._selectObject(self._selectedObj)
822 self.tempMatrix = None
823 self.tool.OnDragEnd()
825 self._mouseState = None
827 def OnMouseMotion(self,e):
828 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
829 p0 -= self.getObjectCenterPos() - self._viewTarget
830 p1 -= self.getObjectCenterPos() - self._viewTarget
832 if e.Dragging() and self._mouseState is not None:
833 if self._mouseState == 'tool':
834 self.tool.OnDrag(p0, p1)
835 elif not e.LeftIsDown() and e.RightIsDown():
836 self._mouseState = 'drag'
837 if wx.GetKeyState(wx.WXK_SHIFT):
838 a = math.cos(math.radians(self._yaw)) / 3.0
839 b = math.sin(math.radians(self._yaw)) / 3.0
840 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
841 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
842 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
843 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
845 self._yaw += e.GetX() - self._mouseX
846 self._pitch -= e.GetY() - self._mouseY
847 if self._pitch > 170:
851 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
852 self._mouseState = 'drag'
853 self._zoom += e.GetY() - self._mouseY
856 if self._zoom > numpy.max(self._machineSize) * 3:
857 self._zoom = numpy.max(self._machineSize) * 3
858 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
859 self._mouseState = 'dragObject'
860 z = max(0, self._mouseClick3DPos[2])
861 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
862 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
867 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
868 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
869 diff = cursorZ1 - cursorZ0
870 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
871 if not e.Dragging() or self._mouseState != 'tool':
872 self.tool.OnMouseMove(p0, p1)
874 self._mouseX = e.GetX()
875 self._mouseY = e.GetY()
877 def OnMouseWheel(self, e):
878 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
879 delta = max(min(delta,4),-4)
880 self._zoom *= 1.0 - delta / 10.0
883 if self._zoom > numpy.max(self._machineSize) * 3:
884 self._zoom = numpy.max(self._machineSize) * 3
887 def OnMouseLeave(self, e):
891 def getMouseRay(self, x, y):
892 if self._viewport is None:
893 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
894 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
895 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
896 p0 -= self._viewTarget
897 p1 -= self._viewTarget
900 def _init3DView(self):
901 # set viewing projection
902 size = self.GetSize()
903 glViewport(0, 0, size.GetWidth(), size.GetHeight())
906 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
908 glDisable(GL_RESCALE_NORMAL)
909 glDisable(GL_LIGHTING)
911 glEnable(GL_DEPTH_TEST)
912 glDisable(GL_CULL_FACE)
914 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
916 glClearColor(0.8, 0.8, 0.8, 1.0)
920 glMatrixMode(GL_PROJECTION)
922 aspect = float(size.GetWidth()) / float(size.GetHeight())
923 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
925 glMatrixMode(GL_MODELVIEW)
927 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
930 connectionGroup = self._printerConnectionManager.getAvailableGroup()
931 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
932 self.printButton._imageID = 2
933 self.printButton._tooltip = _("Toolpath to SD")
934 elif connectionGroup is not None:
935 self.printButton._imageID = connectionGroup.getIconID()
936 self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
938 self.printButton._imageID = 3
939 self.printButton._tooltip = _("Save toolpath")
941 if self._animView is not None:
942 self._viewTarget = self._animView.getPosition()
943 if self._animView.isDone():
944 self._animView = None
945 if self._animZoom is not None:
946 self._zoom = self._animZoom.getPosition()
947 if self._animZoom.isDone():
948 self._animZoom = None
949 if self._objectShader is None: #TODO: add loading shaders from file(s)
950 if openglHelpers.hasShaderSupport():
951 self._objectShader = openglHelpers.GLShader("""
952 varying float light_amount;
956 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
957 gl_FrontColor = gl_Color;
959 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
963 varying float light_amount;
967 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
970 self._objectOverhangShader = openglHelpers.GLShader("""
971 uniform float cosAngle;
972 uniform mat3 rotMatrix;
973 varying float light_amount;
977 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
978 gl_FrontColor = gl_Color;
980 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
982 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
984 light_amount = -10.0;
988 varying float light_amount;
992 if (light_amount == -10.0)
994 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
996 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1000 self._objectLoadShader = openglHelpers.GLShader("""
1001 uniform float intensity;
1002 uniform float scale;
1003 varying float light_amount;
1007 vec4 tmp = gl_Vertex;
1008 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1009 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1010 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1011 gl_FrontColor = gl_Color;
1013 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1014 light_amount += 0.2;
1017 uniform float intensity;
1018 varying float light_amount;
1022 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1025 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
1026 self._objectShader = openglHelpers.GLFakeShader()
1027 self._objectOverhangShader = openglHelpers.GLFakeShader()
1028 self._objectLoadShader = None
1030 glTranslate(0,0,-self._zoom)
1031 glRotate(-self._pitch, 1,0,0)
1032 glRotate(self._yaw, 0,0,1)
1033 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1035 self._viewport = glGetIntegerv(GL_VIEWPORT)
1036 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1037 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1039 glClearColor(1,1,1,1)
1040 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1042 if self.viewMode != 'gcode':
1043 for n in xrange(0, len(self._scene.objects())):
1044 obj = self._scene.objects()[n]
1045 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1046 self._renderObject(obj)
1048 if self._mouseX > -1: # mouse has not passed over the opengl window.
1050 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1051 if n < len(self._scene.objects()):
1052 self._focusObj = self._scene.objects()[n]
1054 self._focusObj = None
1055 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1056 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1057 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1058 self._mouse3Dpos -= self._viewTarget
1061 glTranslate(0,0,-self._zoom)
1062 glRotate(-self._pitch, 1,0,0)
1063 glRotate(self._yaw, 0,0,1)
1064 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1066 self._objectShader.unbind()
1067 self._engineResultView.OnDraw()
1068 if self.viewMode != 'gcode':
1069 glStencilFunc(GL_ALWAYS, 1, 1)
1070 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1072 if self.viewMode == 'overhang':
1073 self._objectOverhangShader.bind()
1074 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1076 self._objectShader.bind()
1077 for obj in self._scene.objects():
1078 if obj._loadAnim is not None:
1079 if obj._loadAnim.isDone():
1080 obj._loadAnim = None
1084 if self._focusObj == obj:
1086 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1089 if self._selectedObj == obj or self._selectedObj is None:
1090 #If we want transparent, then first render a solid black model to remove the printer size lines.
1091 if self.viewMode == 'transparent':
1092 glColor4f(0, 0, 0, 0)
1093 self._renderObject(obj)
1095 glBlendFunc(GL_ONE, GL_ONE)
1096 glDisable(GL_DEPTH_TEST)
1098 if self.viewMode == 'xray':
1099 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1100 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1101 glEnable(GL_STENCIL_TEST)
1103 if self.viewMode == 'overhang':
1104 if self._selectedObj == obj and self.tempMatrix is not None:
1105 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1107 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1109 if not self._scene.checkPlatform(obj):
1110 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1111 self._renderObject(obj)
1113 self._renderObject(obj, brightness)
1114 glDisable(GL_STENCIL_TEST)
1116 glEnable(GL_DEPTH_TEST)
1117 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1119 if self.viewMode == 'xray':
1122 glEnable(GL_STENCIL_TEST)
1123 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1124 glDisable(GL_DEPTH_TEST)
1125 for i in xrange(2, 15, 2): #All even values
1126 glStencilFunc(GL_EQUAL, i, 0xFF)
1127 glColor(float(i)/10, float(i)/10, float(i)/5)
1129 glVertex3f(-1000,-1000,-10)
1130 glVertex3f( 1000,-1000,-10)
1131 glVertex3f( 1000, 1000,-10)
1132 glVertex3f(-1000, 1000,-10)
1134 for i in xrange(1, 15, 2): #All odd values
1135 glStencilFunc(GL_EQUAL, i, 0xFF)
1136 glColor(float(i)/10, 0, 0)
1138 glVertex3f(-1000,-1000,-10)
1139 glVertex3f( 1000,-1000,-10)
1140 glVertex3f( 1000, 1000,-10)
1141 glVertex3f(-1000, 1000,-10)
1144 glDisable(GL_STENCIL_TEST)
1145 glEnable(GL_DEPTH_TEST)
1147 self._objectShader.unbind()
1149 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1151 if self._objectLoadShader is not None:
1152 self._objectLoadShader.bind()
1153 glColor4f(0.2, 0.6, 1.0, 1.0)
1154 for obj in self._scene.objects():
1155 if obj._loadAnim is None:
1157 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1158 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1159 self._renderObject(obj)
1160 self._objectLoadShader.unbind()
1165 if self.viewMode != 'gcode':
1166 #Draw the object box-shadow, so you can see where it will collide with other objects.
1167 if self._selectedObj is not None:
1169 glEnable(GL_CULL_FACE)
1170 glColor4f(0,0,0,0.16)
1172 for obj in self._scene.objects():
1174 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1175 glBegin(GL_TRIANGLE_FAN)
1176 for p in obj._boundaryHull[::-1]:
1177 glVertex3f(p[0], p[1], 0)
1180 if self._scene.isOneAtATime(): #Check print sequence mode.
1182 glColor4f(0,0,0,0.06)
1183 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1184 glBegin(GL_TRIANGLE_FAN)
1185 for p in self._selectedObj._printAreaHull[::-1]:
1186 glVertex3f(p[0], p[1], 0)
1188 glBegin(GL_TRIANGLE_FAN)
1189 for p in self._selectedObj._headAreaMinHull[::-1]:
1190 glVertex3f(p[0], p[1], 0)
1194 glDisable(GL_CULL_FACE)
1196 #Draw the outline of the selected object on top of everything else except the GUI.
1197 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1198 glDisable(GL_DEPTH_TEST)
1199 glEnable(GL_CULL_FACE)
1200 glEnable(GL_STENCIL_TEST)
1202 glStencilFunc(GL_EQUAL, 0, 255)
1204 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1206 glColor4f(1,1,1,0.5)
1207 self._renderObject(self._selectedObj)
1208 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1210 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1211 glDisable(GL_STENCIL_TEST)
1212 glDisable(GL_CULL_FACE)
1213 glEnable(GL_DEPTH_TEST)
1215 if self._selectedObj is not None:
1217 pos = self.getObjectCenterPos()
1218 glTranslate(pos[0], pos[1], pos[2])
1221 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1222 glDisable(GL_DEPTH_TEST)
1225 glTranslate(0,-4,-10)
1226 glColor4ub(60,60,60,255)
1227 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1230 def _renderObject(self, obj, brightness = 0, addSink = True):
1233 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1235 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1237 if self.tempMatrix is not None and obj == self._selectedObj:
1238 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1240 offset = obj.getDrawOffset()
1241 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1243 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1246 for m in obj._meshList:
1248 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1250 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1255 def _drawMachine(self):
1256 glEnable(GL_CULL_FACE)
1259 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1261 machine_type = profile.getMachineSetting('machine_type')
1262 if machine_type not in self._platformMesh:
1263 self._platformMesh[machine_type] = None
1268 texture_offset = [0,0,0]
1270 if machine_type == 'ultimaker2' or machine_type == 'ultimaker2extended':
1271 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1272 offset = [-9,-37,145]
1273 texture_name = 'Ultimaker2backplate.png'
1274 texture_offset = [9,150,-5]
1275 elif machine_type == 'ultimaker2go':
1276 filename = resources.getPathForMesh('ultimaker2go_platform.stl')
1277 offset = [0,-42,145]
1278 texture_offset = [0,105,-5]
1279 texture_name = 'Ultimaker2backplate.png'
1281 elif machine_type == 'ultimaker_plus':
1282 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1283 offset = [0,-37,145]
1284 texture_offset = [0,150,-5]
1285 texture_name = 'UltimakerPlusbackplate.png'
1286 elif machine_type == 'ultimaker':
1287 filename = resources.getPathForMesh('ultimaker_platform.stl')
1289 elif machine_type == 'Witbox':
1290 filename = resources.getPathForMesh('Witbox_platform.stl')
1291 offset = [0,-37,145]
1293 if filename is not None:
1294 meshes = meshLoader.loadMeshes(filename)
1296 self._platformMesh[machine_type] = meshes[0]
1297 self._platformMesh[machine_type]._drawOffset = numpy.array(offset, numpy.float32)
1298 self._platformMesh[machine_type].texture = None
1299 if texture_name is not None:
1300 self._platformMesh[machine_type].texture = openglHelpers.loadGLTexture(texture_name)
1301 self._platformMesh[machine_type].texture_offset = texture_offset
1302 self._platformMesh[machine_type].texture_scale = texture_scale
1303 if self._platformMesh[machine_type] is not None:
1304 mesh = self._platformMesh[machine_type]
1305 glColor4f(1,1,1,0.5)
1306 self._objectShader.bind()
1307 self._renderObject(mesh, False, False)
1308 self._objectShader.unbind()
1310 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1311 if mesh.texture is not None:
1312 glBindTexture(GL_TEXTURE_2D, mesh.texture)
1313 glEnable(GL_TEXTURE_2D)
1317 glTranslate(mesh.texture_offset[0], mesh.texture_offset[1], mesh.texture_offset[2])
1318 glScalef(mesh.texture_scale, mesh.texture_scale, mesh.texture_scale)
1323 glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA)
1324 glEnable(GL_ALPHA_TEST)
1325 glAlphaFunc(GL_GREATER, 0.0)
1328 glVertex3f( w, 0, h)
1330 glVertex3f(-w, 0, h)
1332 glVertex3f(-w, 0, 0)
1334 glVertex3f( w, 0, 0)
1337 glVertex3f(-w, d, h)
1339 glVertex3f( w, d, h)
1341 glVertex3f( w, d, 0)
1343 glVertex3f(-w, d, 0)
1345 glDisable(GL_TEXTURE_2D)
1346 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1352 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1353 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1354 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1355 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1356 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1357 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1362 polys = profile.getMachineSizePolygons()
1363 height = profile.getMachineSettingFloat('machine_height')
1364 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1366 # Draw the sides of the build volume.
1367 for n in xrange(0, len(polys[0])):
1370 glColor4ub(5, 171, 231, 96)
1372 glColor4ub(5, 171, 231, 64)
1374 glColor4ub(5, 171, 231, 96)
1376 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1377 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1378 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1379 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1382 #Draw top of build volume.
1383 glColor4ub(5, 171, 231, 128)
1384 glBegin(GL_TRIANGLE_FAN)
1385 for p in polys[0][::-1]:
1386 glVertex3f(p[0], p[1], height)
1390 if self._platformTexture is None:
1391 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1392 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1393 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1394 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1395 glColor4f(1,1,1,0.5)
1396 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1397 glEnable(GL_TEXTURE_2D)
1398 glBegin(GL_TRIANGLE_FAN)
1400 glTexCoord2f(p[0]/20, p[1]/20)
1401 glVertex3f(p[0], p[1], 0)
1404 #Draw no-go zones. (clips in case of UM2)
1405 glDisable(GL_TEXTURE_2D)
1406 glColor4ub(127, 127, 127, 200)
1407 for poly in polys[1:]:
1408 glBegin(GL_TRIANGLE_FAN)
1410 glTexCoord2f(p[0]/20, p[1]/20)
1411 glVertex3f(p[0], p[1], 0)
1416 glDisable(GL_CULL_FACE)
1418 def getObjectCenterPos(self):
1419 if self._selectedObj is None:
1420 return [0.0, 0.0, 0.0]
1421 pos = self._selectedObj.getPosition()
1422 size = self._selectedObj.getSize()
1423 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1425 def getObjectBoundaryCircle(self):
1426 if self._selectedObj is None:
1428 return self._selectedObj.getBoundaryCircle()
1430 def getObjectSize(self):
1431 if self._selectedObj is None:
1432 return [0.0, 0.0, 0.0]
1433 return self._selectedObj.getSize()
1435 def getObjectMatrix(self):
1436 if self._selectedObj is None:
1437 return numpy.matrix(numpy.identity(3))
1438 return self._selectedObj.getMatrix()
1440 #TODO: Remove this or put it in a seperate file
1441 class shaderEditor(wx.Frame):
1442 def __init__(self, parent, callback, v, f):
1443 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1444 self._callback = callback
1445 s = wx.BoxSizer(wx.VERTICAL)
1447 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1448 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1449 s.Add(self._vertex, 1, flag=wx.EXPAND)
1450 s.Add(self._fragment, 1, flag=wx.EXPAND)
1452 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1453 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1455 self.SetPosition(self.GetParent().GetPosition())
1456 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1459 def OnText(self, e):
1460 self._callback(self._vertex.GetValue(), self._fragment.GetValue())