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)
52 self._mouse3Dpos = numpy.array([0,0,0], numpy.float32)
55 self._platformMesh = {}
56 self.glReleaseList = []
57 self._platformTexture = None
58 self._isSimpleMode = True
59 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
62 self._modelMatrix = None
63 self._projMatrix = None
64 self.tempMatrix = None
66 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
67 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
68 self.printButton.setDisabled(True)
71 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
72 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
73 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
75 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
76 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
78 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
79 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
81 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
82 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
83 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
85 self.rotateToolButton.setExpandArrow(True)
86 self.scaleToolButton.setExpandArrow(True)
87 self.mirrorToolButton.setExpandArrow(True)
89 self.scaleForm = openglGui.glFrame(self, (2, -2))
90 openglGui.glGuiLayoutGrid(self.scaleForm)
91 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
92 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
93 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
94 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
95 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
96 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
97 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
98 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
99 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
100 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
101 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
102 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
103 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
104 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
106 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)
107 self.viewSelection.setDisabled(True)
108 #self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
109 #self.youMagineButton.setDisabled(True)
111 self.notification = openglGui.glNotification(self, (0, 0))
113 self._engine = sliceEngine.Engine(self._updateEngineProgress)
114 self._engineResultView = engineResultView.engineResultView(self)
115 self._sceneUpdateTimer = wx.Timer(self)
116 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
117 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
118 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
122 self.updateToolButtons()
123 self.updateProfileToControls()
126 # Delete all objects first
127 self.OnDeleteAll(None)
128 self._engine.cleanup()
129 if self._objectShader is not None:
130 self._objectShader.release()
131 if self._objectLoadShader is not None:
132 self._objectLoadShader.release()
133 if self._objectOverhangShader is not None:
134 self._objectOverhangShader.release()
135 for obj in self.glReleaseList:
138 def loadGCodeFile(self, filename):
139 self.OnDeleteAll(None)
140 #Cheat the engine results to load a GCode file into it.
141 self._engine._result = sliceEngine.EngineResult()
142 with open(filename, "r") as f:
143 self._engine._result.setGCode(f.read())
144 self._engine._result.setFinished(True)
145 self._engineResultView.setResult(self._engine._result)
146 self.printButton.setBottomText('')
147 self.viewSelection.setDisabled(False)
148 self.viewSelection.setValue(4)
149 self.printButton.setDisabled(False)
150 #self.youMagineButton.setDisabled(True)
153 def loadSceneFiles(self, filenames):
154 #self.youMagineButton.setDisabled(False)
155 #if self.viewSelection.getValue() == 4:
156 # self.viewSelection.setValue(0)
157 # self.OnViewChange()
158 self.loadScene(filenames)
160 def loadFiles(self, filenames):
161 mainWindow = self.GetParent().GetParent().GetParent()
162 # only one GCODE file can be active
163 # so if single gcode file, process this
164 # otherwise ignore all gcode files
166 if len(filenames) == 1:
167 filename = filenames[0]
168 ext = os.path.splitext(filename)[1].lower()
169 if ext == '.g' or ext == '.gcode':
170 gcodeFilename = filename
171 mainWindow.addToModelMRU(filename)
172 if gcodeFilename is not None:
173 self.loadGCodeFile(gcodeFilename)
175 # process directories and special file types
176 # and keep scene files for later processing
178 ignored_types = dict()
179 # use file list as queue
180 # pop first entry for processing and append new files at end
182 filename = filenames.pop(0)
183 if os.path.isdir(filename):
184 # directory: queue all included files and directories
185 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
187 ext = os.path.splitext(filename)[1].lower()
189 profile.loadProfile(filename)
190 mainWindow.addToProfileMRU(filename)
191 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
192 scene_filenames.append(filename)
193 mainWindow.addToModelMRU(filename)
195 ignored_types[ext] = 1
197 ignored_types = ignored_types.keys()
199 self.notification.message(_("ignored: ") + " ".join("*" + type for type in ignored_types))
200 mainWindow.updateProfileToAllControls()
201 # now process all the scene files
203 self.loadSceneFiles(scene_filenames)
204 self._selectObject(None)
206 newZoom = numpy.max(self._machineSize)
207 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
208 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
210 def reloadScene(self, e):
211 # Copy the list before DeleteAll clears it
213 for obj in self._scene.objects():
214 fileList.append(obj.getOriginFilename())
215 self.OnDeleteAll(None)
216 self.loadScene(fileList)
218 def showLoadModel(self, button = 1):
220 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)
222 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
223 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
224 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
225 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
226 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
227 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
228 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
229 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
231 dlg.SetWildcard(wildcardFilter)
232 if dlg.ShowModal() != wx.ID_OK:
235 filenames = dlg.GetPaths()
237 if len(filenames) < 1:
239 profile.putPreference('lastFile', filenames[0])
240 self.loadFiles(filenames)
242 def showSaveModel(self):
243 if len(self._scene.objects()) < 1:
245 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
246 fileExtensions = meshLoader.saveSupportedExtensions()
247 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
248 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
249 dlg.SetWildcard(wildcardFilter)
250 if dlg.ShowModal() != wx.ID_OK:
253 filename = dlg.GetPath()
255 meshLoader.saveMeshes(filename, self._scene.objects())
257 def OnPrintButton(self, button):
259 connectionGroup = self._printerConnectionManager.getAvailableGroup()
260 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
261 drives = removableStorage.getPossibleSDcardDrives()
263 choices = map(lambda n: n[0], drives)
264 choices += (_("Custom file destination"), )
265 title = _("Multiple removable drives have been found")
267 choices = [drives[0][0], _("Custom file destination")]
268 title = _("A removable drive has been found")
270 dlg = wx.SingleChoiceDialog(self, _("Select destination SD card drive\nYou can also select a custom file to save to"), title, choices)
271 if dlg.ShowModal() != wx.ID_OK:
275 drive = drives[dlg.GetSelection()]
283 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
284 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
285 elif connectionGroup is not None:
286 connections = connectionGroup.getAvailableConnections()
287 if len(connections) < 2:
288 connection = connections[0]
290 dlg = wx.SingleChoiceDialog(self, _("Select the %s connection to use") % (connectionGroup.getName()), _("Multiple %s connections found") % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
291 if dlg.ShowModal() != wx.ID_OK:
294 connection = connections[dlg.GetSelection()]
296 self._openPrintWindowForConnection(connection)
301 connections = self._printerConnectionManager.getAvailableConnections()
302 menu.connectionMap = {}
303 for connection in connections:
304 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
305 menu.connectionMap[i.GetId()] = connection
306 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
307 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
308 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
312 def _openPrintWindowForConnection(self, connection):
313 if connection.window is None or not connection.window:
314 connection.window = None
315 windowType = profile.getPreference('printing_window')
316 for p in pluginInfo.getPluginList('printwindow'):
317 if p.getName() == windowType:
318 connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
320 if connection.window is None:
321 connection.window = printWindow.printWindowBasic(self, connection)
322 connection.window.Show()
323 connection.window.Raise()
324 if not connection.loadGCodeData(self._engine.getResult().getGCode()):
325 if connection.isPrinting():
326 self.notification.message(_("Cannot start print, because other print still running."))
328 self.notification.message(_("Failed to start print..."))
330 def showSaveGCode(self):
331 if len(self._scene._objectList) < 1:
333 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
334 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
335 dlg.SetFilename(filename)
336 dlg.SetWildcard('Toolpath (*%s)|*%s;*%s' % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
337 if dlg.ShowModal() != wx.ID_OK:
340 filename = dlg.GetPath()
343 threading.Thread(target=self._saveGCode,args=(filename,)).start()
345 def _saveGCode(self, targetFilename, ejectDrive = False):
346 gcode = self._engine.getResult().getGCode()
348 size = float(len(gcode))
350 with open(targetFilename, 'wb') as fdst:
352 buf = gcode.read(16*1024)
357 self.printButton.setProgressBar(read_pos / size)
360 import sys, traceback
361 traceback.print_exc()
362 self.notification.message(_("Failed to save"))
365 self.notification.message(_("Saved as %s") % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
366 elif explorer.hasExplorer():
367 self.notification.message(_("Saved as %s") % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, _('Open folder'))
369 self.notification.message(_("Saved as %s") % (targetFilename))
370 self.printButton.setProgressBar(None)
372 def _doEjectSD(self, drive):
373 if removableStorage.ejectDrive(drive):
374 self.notification.message(_('You can now eject the card.'))
376 self.notification.message(_('Safe remove failed...'))
378 def _showEngineLog(self):
379 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)
383 def OnToolSelect(self, button):
384 if self.rotateToolButton.getSelected():
385 self.tool = previewTools.toolRotate(self)
386 elif self.scaleToolButton.getSelected():
387 self.tool = previewTools.toolScale(self)
388 elif self.mirrorToolButton.getSelected():
389 self.tool = previewTools.toolNone(self)
391 self.tool = previewTools.toolNone(self)
392 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
393 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
394 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
395 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
396 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
397 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
398 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
399 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
401 def updateToolButtons(self):
402 if self._selectedObj is None:
406 self.rotateToolButton.setHidden(hidden)
407 self.scaleToolButton.setHidden(hidden)
408 self.mirrorToolButton.setHidden(hidden)
410 self.rotateToolButton.setSelected(False)
411 self.scaleToolButton.setSelected(False)
412 self.mirrorToolButton.setSelected(False)
415 def OnViewChange(self):
416 if self.viewSelection.getValue() == 4:
417 self.viewMode = 'gcode'
418 self.tool = previewTools.toolNone(self)
419 elif self.viewSelection.getValue() == 1:
420 self.viewMode = 'overhang'
421 elif self.viewSelection.getValue() == 2:
422 self.viewMode = 'transparent'
423 elif self.viewSelection.getValue() == 3:
424 self.viewMode = 'xray'
426 self.viewMode = 'normal'
427 self._engineResultView.setEnabled(self.viewMode == 'gcode')
430 def OnViewStateChange(self, state):
431 self._engineResultView.layerSelect.setHidden(self.viewMode != 'gcode' or state)
433 def OnRotateReset(self, button):
434 if self._selectedObj is None:
436 self._selectedObj.resetRotation()
437 self._scene.pushFree(self._selectedObj)
438 self._selectObject(self._selectedObj)
441 def OnLayFlat(self, button):
442 if self._selectedObj is None:
444 self._selectedObj.layFlat()
445 self._scene.pushFree(self._selectedObj)
446 self._selectObject(self._selectedObj)
449 def OnScaleReset(self, button):
450 if self._selectedObj is None:
452 self._selectedObj.resetScale()
453 self._selectObject(self._selectedObj)
454 self.updateProfileToControls()
457 def OnScaleMax(self, button):
458 if self._selectedObj is None:
460 machine = profile.getMachineSetting('machine_type')
461 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
462 self._scene.pushFree(self._selectedObj)
464 if machine == "ultimaker2":
465 #This is bad and Jaime should feel bad!
466 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
467 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
468 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
469 self._scene.pushFree(self._selectedObj)
471 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
472 self._scene.pushFree(self._selectedObj)
473 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
474 self._scene.pushFree(self._selectedObj)
475 self._selectObject(self._selectedObj)
476 self.updateProfileToControls()
479 def OnMirror(self, axis):
480 if self._selectedObj is None:
482 self._selectedObj.mirror(axis)
485 def OnScaleEntry(self, value, axis):
486 if self._selectedObj is None:
492 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
493 self.updateProfileToControls()
494 self._scene.pushFree(self._selectedObj)
495 self._selectObject(self._selectedObj)
498 def OnScaleEntryMM(self, value, axis):
499 if self._selectedObj is None:
505 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
506 self.updateProfileToControls()
507 self._scene.pushFree(self._selectedObj)
508 self._selectObject(self._selectedObj)
511 def OnDeleteAll(self, e):
512 while len(self._scene.objects()) > 0:
513 self._deleteObject(self._scene.objects()[0])
514 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
515 self._engineResultView.setResult(None)
516 self.viewSelection.setDisabled(True)
517 self.printButton.setDisabled(True)
519 def OnMultiply(self, e):
520 if self._focusObj is None:
523 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
524 if dlg.ShowModal() != wx.ID_OK:
533 self._scene.add(newObj)
534 self._scene.centerAll()
535 if not self._scene.checkPlatform(newObj):
540 self.notification.message(_("Could not create more than %d items") % (n - 1))
541 self.notification.message(_("Could not create more than %d items") % (n - 1))
542 self._scene.remove(newObj)
543 self._scene.centerAll()
546 def OnSplitObject(self, e):
547 if self._focusObj is None:
549 self._scene.remove(self._focusObj)
550 for obj in self._focusObj.split(self._splitCallback):
551 if numpy.max(obj.getSize()) > 2.0:
553 self._scene.centerAll()
554 self._selectObject(None)
557 def OnCenter(self, e):
558 if self._focusObj is None:
560 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
561 self._scene.pushFree(self._selectedObj)
562 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
563 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
566 def _splitCallback(self, progress):
569 def OnMergeObjects(self, e):
570 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
571 if len(self._scene.objects()) == 2:
572 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
575 self._scene.merge(self._selectedObj, self._focusObj)
578 def sceneUpdated(self):
579 wx.CallAfter(self._sceneUpdateTimer.Start, 500, True)
580 self._engine.abortEngine()
581 self._scene.updateSizeOffsets()
584 def _onRunEngine(self, e):
585 if self._isSimpleMode:
586 self._engine.runEngine(self._scene, self.GetTopLevelParent().simpleSettingsPanel.getSettingOverrides())
588 self._engine.runEngine(self._scene)
590 def _updateEngineProgress(self, progressValue):
591 result = self._engine.getResult()
592 finished = result is not None and result.isFinished()
594 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
596 self.printButton.setDisabled(not finished)
597 self.viewSelection.setDisabled(not finished)
598 if progressValue >= 0.0:
599 self.printButton.setProgressBar(progressValue)
601 self.printButton.setProgressBar(None)
602 self._engineResultView.setResult(result)
604 self.printButton.setProgressBar(None)
605 text = '%s' % (result.getPrintTime())
606 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
607 amount = result.getFilamentAmount(e)
610 text += '\n%s' % (amount)
611 cost = result.getFilamentCost(e)
613 text += '\n%s' % (cost)
614 self.printButton.setBottomText(text)
616 self.printButton.setBottomText('')
619 def loadScene(self, fileList):
620 for filename in fileList:
622 ext = os.path.splitext(filename)[1].lower()
623 if ext in imageToMesh.supportedExtensions():
624 imageToMesh.convertImageDialog(self, filename).Show()
627 objList = meshLoader.loadMeshes(filename)
629 traceback.print_exc()
632 if self._objectLoadShader is not None:
633 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
637 if not self._scene.checkPlatform(obj):
638 self._scene.centerAll()
639 self._selectObject(obj)
640 if obj.getScale()[0] < 1.0:
641 self.notification.message(_("Warning: Object scaled down."))
644 def _deleteObject(self, obj):
645 if obj == self._selectedObj:
646 self._selectObject(None)
647 if obj == self._focusObj:
648 self._focusObj = None
649 self._scene.remove(obj)
650 for m in obj._meshList:
651 if m.vbo is not None and m.vbo.decRef():
652 self.glReleaseList.append(m.vbo)
653 if len(self._scene.objects()) == 0:
654 self._engineResultView.setResult(None)
655 self.printButton.setDisabled(True)
656 self.viewSelection.setDisabled(True)
657 self.printButton.setBottomText('')
662 def _selectObject(self, obj, zoom = True):
663 if obj != self._selectedObj:
664 self._selectedObj = obj
665 self.updateModelSettingsToControls()
666 self.updateToolButtons()
667 if zoom and obj is not None:
668 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
669 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
670 newZoom = obj.getBoundaryCircle() * 6
671 if newZoom > numpy.max(self._machineSize) * 3:
672 newZoom = numpy.max(self._machineSize) * 3
673 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
675 def updateProfileToControls(self):
676 oldSimpleMode = self._isSimpleMode
677 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
678 if self._isSimpleMode != oldSimpleMode:
679 self._scene.arrangeAll()
681 self._scene.updateSizeOffsets(True)
682 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
683 self._objColors[0] = profile.getPreferenceColour('model_colour')
684 self._objColors[1] = profile.getPreferenceColour('model_colour2')
685 self._objColors[2] = profile.getPreferenceColour('model_colour3')
686 self._objColors[3] = profile.getPreferenceColour('model_colour4')
687 self._scene.updateMachineDimensions()
688 self.updateModelSettingsToControls()
690 def updateModelSettingsToControls(self):
691 if self._selectedObj is not None:
692 scale = self._selectedObj.getScale()
693 size = self._selectedObj.getSize()
694 self.scaleXctrl.setValue(round(scale[0], 2))
695 self.scaleYctrl.setValue(round(scale[1], 2))
696 self.scaleZctrl.setValue(round(scale[2], 2))
697 self.scaleXmmctrl.setValue(round(size[0], 2))
698 self.scaleYmmctrl.setValue(round(size[1], 2))
699 self.scaleZmmctrl.setValue(round(size[2], 2))
701 def OnKeyChar(self, keyCode):
702 if self._engineResultView.OnKeyChar(keyCode):
704 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and sys.platform.startswith("darwin")):
705 if self._selectedObj is not None:
706 self._deleteObject(self._selectedObj)
708 if keyCode == wx.WXK_UP:
709 if wx.GetKeyState(wx.WXK_SHIFT):
716 elif keyCode == wx.WXK_DOWN:
717 if wx.GetKeyState(wx.WXK_SHIFT):
719 if self._zoom > numpy.max(self._machineSize) * 3:
720 self._zoom = numpy.max(self._machineSize) * 3
724 elif keyCode == wx.WXK_LEFT:
727 elif keyCode == wx.WXK_RIGHT:
730 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
735 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
737 if self._zoom > numpy.max(self._machineSize) * 3:
738 self._zoom = numpy.max(self._machineSize) * 3
740 elif keyCode == wx.WXK_HOME:
744 elif keyCode == wx.WXK_PAGEUP:
748 elif keyCode == wx.WXK_PAGEDOWN:
752 elif keyCode == wx.WXK_END:
757 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
758 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
759 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
760 from collections import defaultdict
761 from gc import get_objects
762 self._beforeLeakTest = defaultdict(int)
763 for i in get_objects():
764 self._beforeLeakTest[type(i)] += 1
765 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
766 from collections import defaultdict
767 from gc import get_objects
768 self._afterLeakTest = defaultdict(int)
769 for i in get_objects():
770 self._afterLeakTest[type(i)] += 1
771 for k in self._afterLeakTest:
772 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
773 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
775 def ShaderUpdate(self, v, f):
776 s = openglHelpers.GLShader(v, f)
778 self._objectLoadShader.release()
779 self._objectLoadShader = s
780 for obj in self._scene.objects():
781 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
784 def OnMouseDown(self,e):
785 self._mouseX = e.GetX()
786 self._mouseY = e.GetY()
787 self._mouseClick3DPos = self._mouse3Dpos
788 self._mouseClickFocus = self._focusObj
790 self._mouseState = 'doubleClick'
792 if self._mouseState == 'dragObject' and self._selectedObj is not None:
793 self._scene.pushFree(self._selectedObj)
795 self._mouseState = 'dragOrClick'
796 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
797 p0 -= self.getObjectCenterPos() - self._viewTarget
798 p1 -= self.getObjectCenterPos() - self._viewTarget
799 if self.tool.OnDragStart(p0, p1):
800 self._mouseState = 'tool'
801 if self._mouseState == 'dragOrClick':
802 if e.GetButton() == 1:
803 if self._focusObj is not None:
804 self._selectObject(self._focusObj, False)
807 def OnMouseUp(self, e):
808 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
810 if self._mouseState == 'dragOrClick':
811 if e.GetButton() == 1:
812 self._selectObject(self._focusObj)
813 if e.GetButton() == 3:
815 if self._focusObj is not None:
817 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
818 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
819 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
820 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
821 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:
822 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
823 if len(self._scene.objects()) > 0:
824 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
825 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
826 if menu.MenuItemCount > 0:
829 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
830 self._scene.pushFree(self._selectedObj)
832 elif self._mouseState == 'tool':
833 if self.tempMatrix is not None and self._selectedObj is not None:
834 self._selectedObj.applyMatrix(self.tempMatrix)
835 self._scene.pushFree(self._selectedObj)
836 self._selectObject(self._selectedObj)
837 self.tempMatrix = None
838 self.tool.OnDragEnd()
840 self._mouseState = None
842 def OnMouseMotion(self,e):
843 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
844 p0 -= self.getObjectCenterPos() - self._viewTarget
845 p1 -= self.getObjectCenterPos() - self._viewTarget
847 if e.Dragging() and self._mouseState is not None:
848 if self._mouseState == 'tool':
849 self.tool.OnDrag(p0, p1)
850 elif not e.LeftIsDown() and e.RightIsDown():
851 self._mouseState = 'drag'
852 if wx.GetKeyState(wx.WXK_SHIFT):
853 a = math.cos(math.radians(self._yaw)) / 3.0
854 b = math.sin(math.radians(self._yaw)) / 3.0
855 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
856 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
857 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
858 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
860 self._yaw += e.GetX() - self._mouseX
861 self._pitch -= e.GetY() - self._mouseY
862 if self._pitch > 170:
866 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
867 self._mouseState = 'drag'
868 self._zoom += e.GetY() - self._mouseY
871 if self._zoom > numpy.max(self._machineSize) * 3:
872 self._zoom = numpy.max(self._machineSize) * 3
873 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
874 self._mouseState = 'dragObject'
875 z = max(0, self._mouseClick3DPos[2])
876 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
877 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
882 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
883 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
884 diff = cursorZ1 - cursorZ0
885 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
886 if not e.Dragging() or self._mouseState != 'tool':
887 self.tool.OnMouseMove(p0, p1)
889 self._mouseX = e.GetX()
890 self._mouseY = e.GetY()
892 def OnMouseWheel(self, e):
893 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
894 delta = max(min(delta,4),-4)
895 self._zoom *= 1.0 - delta / 10.0
898 if self._zoom > numpy.max(self._machineSize) * 3:
899 self._zoom = numpy.max(self._machineSize) * 3
902 def OnMouseLeave(self, e):
906 def getMouseRay(self, x, y):
907 if self._viewport is None:
908 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
909 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
910 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
911 p0 -= self._viewTarget
912 p1 -= self._viewTarget
915 def _init3DView(self):
916 # set viewing projection
917 size = self.GetSize()
918 glViewport(0, 0, size.GetWidth(), size.GetHeight())
921 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
923 glDisable(GL_RESCALE_NORMAL)
924 glDisable(GL_LIGHTING)
926 glEnable(GL_DEPTH_TEST)
927 glDisable(GL_CULL_FACE)
929 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
931 #scene background color
932 glClearColor(0.85, 0.85, 0.85, 1.0)
936 glMatrixMode(GL_PROJECTION)
938 aspect = float(size.GetWidth()) / float(size.GetHeight())
939 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
941 glMatrixMode(GL_MODELVIEW)
943 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
946 connectionGroup = self._printerConnectionManager.getAvailableGroup()
947 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
948 self.printButton._imageID = 2
949 self.printButton._tooltip = _("Toolpath to SD")
950 elif connectionGroup is not None:
951 self.printButton._imageID = connectionGroup.getIconID()
952 #self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
953 self.printButton._tooltip = _("Print/Control")
955 self.printButton._imageID = 3
956 self.printButton._tooltip = _("Save toolpath")
958 if self._animView is not None:
959 self._viewTarget = self._animView.getPosition()
960 if self._animView.isDone():
961 self._animView = None
962 if self._animZoom is not None:
963 self._zoom = self._animZoom.getPosition()
964 if self._animZoom.isDone():
965 self._animZoom = None
966 if self._objectShader is None: #TODO: add loading shaders from file(s)
967 if openglHelpers.hasShaderSupport():
968 self._objectShader = openglHelpers.GLShader("""
969 varying float light_amount;
973 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
974 gl_FrontColor = gl_Color;
976 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
980 varying float light_amount;
984 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
987 self._objectOverhangShader = openglHelpers.GLShader("""
988 uniform float cosAngle;
989 uniform mat3 rotMatrix;
990 varying float light_amount;
994 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
995 gl_FrontColor = gl_Color;
997 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
999 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
1001 light_amount = -10.0;
1005 varying float light_amount;
1009 if (light_amount == -10.0)
1011 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1013 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1017 self._objectLoadShader = openglHelpers.GLShader("""
1018 uniform float intensity;
1019 uniform float scale;
1020 varying float light_amount;
1024 vec4 tmp = gl_Vertex;
1025 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1026 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1027 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1028 gl_FrontColor = gl_Color;
1030 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1031 light_amount += 0.2;
1034 uniform float intensity;
1035 varying float light_amount;
1039 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1042 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
1043 self._objectShader = openglHelpers.GLFakeShader()
1044 self._objectOverhangShader = openglHelpers.GLFakeShader()
1045 self._objectLoadShader = None
1047 glTranslate(0,0,-self._zoom)
1048 glRotate(-self._pitch, 1,0,0)
1049 glRotate(self._yaw, 0,0,1)
1050 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1052 self._viewport = glGetIntegerv(GL_VIEWPORT)
1053 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1054 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1056 glClearColor(1,1,1,1)
1057 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1059 if self.viewMode != 'gcode':
1060 for n in xrange(0, len(self._scene.objects())):
1061 obj = self._scene.objects()[n]
1062 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1063 self._renderObject(obj)
1065 if self._mouseX > -1: # mouse has not passed over the opengl window.
1067 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1068 if n < len(self._scene.objects()):
1069 self._focusObj = self._scene.objects()[n]
1071 self._focusObj = None
1072 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1073 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1074 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1075 self._mouse3Dpos -= self._viewTarget
1078 glTranslate(0,0,-self._zoom)
1079 glRotate(-self._pitch, 1,0,0)
1080 glRotate(self._yaw, 0,0,1)
1081 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1083 self._objectShader.unbind()
1084 self._engineResultView.OnDraw()
1085 if self.viewMode != 'gcode':
1086 glStencilFunc(GL_ALWAYS, 1, 1)
1087 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1089 if self.viewMode == 'overhang':
1090 self._objectOverhangShader.bind()
1091 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1093 self._objectShader.bind()
1094 for obj in self._scene.objects():
1095 if obj._loadAnim is not None:
1096 if obj._loadAnim.isDone():
1097 obj._loadAnim = None
1101 if self._focusObj == obj:
1103 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1106 if self._selectedObj == obj or self._selectedObj is None:
1107 #If we want transparent, then first render a solid black model to remove the printer size lines.
1108 if self.viewMode == 'transparent':
1109 glColor4f(0, 0, 0, 0)
1110 self._renderObject(obj)
1112 glBlendFunc(GL_ONE, GL_ONE)
1113 glDisable(GL_DEPTH_TEST)
1115 if self.viewMode == 'xray':
1116 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1117 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1118 glEnable(GL_STENCIL_TEST)
1120 if self.viewMode == 'overhang':
1121 if self._selectedObj == obj and self.tempMatrix is not None:
1122 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1124 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1126 if not self._scene.checkPlatform(obj):
1127 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1128 self._renderObject(obj)
1130 self._renderObject(obj, brightness)
1131 glDisable(GL_STENCIL_TEST)
1133 glEnable(GL_DEPTH_TEST)
1134 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1136 if self.viewMode == 'xray':
1139 glEnable(GL_STENCIL_TEST)
1140 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1141 glDisable(GL_DEPTH_TEST)
1142 for i in xrange(2, 15, 2): #All even values
1143 glStencilFunc(GL_EQUAL, i, 0xFF)
1144 glColor(float(i)/10, float(i)/10, float(i)/5)
1146 glVertex3f(-1000,-1000,-10)
1147 glVertex3f( 1000,-1000,-10)
1148 glVertex3f( 1000, 1000,-10)
1149 glVertex3f(-1000, 1000,-10)
1151 for i in xrange(1, 15, 2): #All odd values
1152 glStencilFunc(GL_EQUAL, i, 0xFF)
1153 glColor(float(i)/10, 0, 0)
1155 glVertex3f(-1000,-1000,-10)
1156 glVertex3f( 1000,-1000,-10)
1157 glVertex3f( 1000, 1000,-10)
1158 glVertex3f(-1000, 1000,-10)
1161 glDisable(GL_STENCIL_TEST)
1162 glEnable(GL_DEPTH_TEST)
1164 self._objectShader.unbind()
1166 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1168 if self._objectLoadShader is not None:
1169 self._objectLoadShader.bind()
1170 #Model color during load animation
1171 glColor4ub(177, 205, 54, 255)
1172 for obj in self._scene.objects():
1173 if obj._loadAnim is None:
1175 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1176 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1177 self._renderObject(obj)
1178 self._objectLoadShader.unbind()
1183 if self.viewMode != 'gcode':
1184 #Draw the object box-shadow, so you can see where it will collide with other objects.
1185 if self._selectedObj is not None:
1187 glEnable(GL_CULL_FACE)
1188 glColor4f(0,0,0,0.16)
1190 for obj in self._scene.objects():
1192 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1193 glBegin(GL_TRIANGLE_FAN)
1194 for p in obj._boundaryHull[::-1]:
1195 glVertex3f(p[0], p[1], 0)
1198 if self._scene.isOneAtATime(): #Check print sequence mode.
1200 glColor4f(0,0,0,0.06)
1201 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1202 glBegin(GL_TRIANGLE_FAN)
1203 for p in self._selectedObj._printAreaHull[::-1]:
1204 glVertex3f(p[0], p[1], 0)
1206 glBegin(GL_TRIANGLE_FAN)
1207 for p in self._selectedObj._headAreaMinHull[::-1]:
1208 glVertex3f(p[0], p[1], 0)
1212 glDisable(GL_CULL_FACE)
1214 #Draw the outline of the selected object on top of everything else except the GUI.
1215 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1216 glDisable(GL_DEPTH_TEST)
1217 glEnable(GL_CULL_FACE)
1218 glEnable(GL_STENCIL_TEST)
1220 glStencilFunc(GL_EQUAL, 0, 255)
1222 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1224 glColor4f(1,1,1,0.5)
1225 self._renderObject(self._selectedObj)
1226 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1228 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1229 glDisable(GL_STENCIL_TEST)
1230 glDisable(GL_CULL_FACE)
1231 glEnable(GL_DEPTH_TEST)
1233 if self._selectedObj is not None:
1235 pos = self.getObjectCenterPos()
1236 glTranslate(pos[0], pos[1], pos[2])
1239 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1240 glDisable(GL_DEPTH_TEST)
1243 glTranslate(0,-4,-10)
1244 glColor4ub(60,60,60,255)
1245 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1248 def _renderObject(self, obj, brightness = 0, addSink = True):
1251 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1253 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1255 if self.tempMatrix is not None and obj == self._selectedObj:
1256 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1258 offset = obj.getDrawOffset()
1259 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1261 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1264 for m in obj._meshList:
1266 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1268 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1273 def _drawMachine(self):
1274 glEnable(GL_CULL_FACE)
1277 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1279 machine = profile.getMachineSetting('machine_type')
1281 #Due to NC licensing of the stl files, temporarily removing platform mesh loading for Ultimaker and Witbox
1282 '''if machine.startswith('ultimaker'):
1283 if machine not in self._platformMesh:
1284 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1286 self._platformMesh[machine] = meshes[0]
1288 self._platformMesh[machine] = None
1289 if machine == 'ultimaker2' or machine == 'ultimaker_plus':
1290 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1292 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1293 glColor4f(1,1,1,0.5)
1294 self._objectShader.bind()
1295 self._renderObject(self._platformMesh[machine], False, False)
1296 self._objectShader.unbind()
1298 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1299 if machine == 'ultimaker2' or machine == 'ultimaker_plus':
1300 if not hasattr(self._platformMesh[machine], 'texture'):
1301 if machine == 'ultimaker2':
1302 self._platformMesh[machine].texture = openglHelpers.loadGLTexture('Ultimaker2backplate.png')
1304 self._platformMesh[machine].texture = openglHelpers.loadGLTexture('UltimakerPlusbackplate.png')
1305 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1306 glEnable(GL_TEXTURE_2D)
1310 glTranslate(0,150,-5)
1315 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1318 glVertex3f( w, 0, h)
1320 glVertex3f(-w, 0, h)
1322 glVertex3f(-w, 0, 0)
1324 glVertex3f( w, 0, 0)
1327 glVertex3f(-w, d, h)
1329 glVertex3f( w, d, h)
1331 glVertex3f( w, d, 0)
1333 glVertex3f(-w, d, 0)
1335 glDisable(GL_TEXTURE_2D)
1336 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1339 elif machine.startswith('Witbox'):
1340 if machine not in self._platformMesh:
1341 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1343 self._platformMesh[machine] = meshes[0]
1345 self._platformMesh[machine] = None
1346 if machine == 'Witbox':
1347 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1348 glColor4f(1,1,1,0.5)
1349 self._objectShader.bind()
1350 self._renderObject(self._platformMesh[machine], False, False)
1351 self._objectShader.unbind()
1357 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1358 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1359 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1360 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1361 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1362 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1367 polys = profile.getMachineSizePolygons()
1368 height = profile.getMachineSettingFloat('machine_height')
1369 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1371 # Draw the sides of the build volume.
1372 for n in xrange(0, len(polys[0])):
1375 glColor4ub(210, 235, 103, 100)
1377 glColor4ub(223, 241, 145, 100)
1379 glColor4ub(223, 241, 145, 100)
1381 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1382 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1383 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1384 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1387 #Draw top of build volume.
1388 glColor4ub(183, 209, 90, 100)
1389 glBegin(GL_TRIANGLE_FAN)
1390 for p in polys[0][::-1]:
1391 glVertex3f(p[0], p[1], height)
1395 if self._platformTexture is None:
1396 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1397 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1398 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1399 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1400 #Dark checkerboard color
1401 glColor4f(1,1,1,0.7)
1402 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1403 glEnable(GL_TEXTURE_2D)
1404 glBegin(GL_TRIANGLE_FAN)
1406 glTexCoord2f(p[0]/20, p[1]/20)
1407 glVertex3f(p[0], p[1], 0)
1410 #Draw no-go zones. (clips in case of UM2)
1411 glDisable(GL_TEXTURE_2D)
1412 glColor4ub(127, 127, 127, 200)
1413 for poly in polys[1:]:
1414 glBegin(GL_TRIANGLE_FAN)
1416 glTexCoord2f(p[0]/20, p[1]/20)
1417 glVertex3f(p[0], p[1], 0)
1422 glDisable(GL_CULL_FACE)
1424 def getObjectCenterPos(self):
1425 if self._selectedObj is None:
1426 return [0.0, 0.0, 0.0]
1427 pos = self._selectedObj.getPosition()
1428 size = self._selectedObj.getSize()
1429 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1431 def getObjectBoundaryCircle(self):
1432 if self._selectedObj is None:
1434 return self._selectedObj.getBoundaryCircle()
1436 def getObjectSize(self):
1437 if self._selectedObj is None:
1438 return [0.0, 0.0, 0.0]
1439 return self._selectedObj.getSize()
1441 def getObjectMatrix(self):
1442 if self._selectedObj is None:
1443 return numpy.matrix(numpy.identity(3))
1444 return self._selectedObj.getMatrix()
1446 #TODO: Remove this or put it in a seperate file
1447 class shaderEditor(wx.Frame):
1448 def __init__(self, parent, callback, v, f):
1449 super(shaderEditor, self).__init__(parent, title=_("Shader editor"), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1450 self._callback = callback
1451 s = wx.BoxSizer(wx.VERTICAL)
1453 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1454 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1455 s.Add(self._vertex, 1, flag=wx.EXPAND)
1456 s.Add(self._fragment, 1, flag=wx.EXPAND)
1458 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1459 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1461 self.SetPosition(self.GetParent().GetPosition())
1462 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1465 def OnText(self, e):
1466 self._callback(self._vertex.GetValue(), self._fragment.GetValue())