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._platformMesh = {}
55 self._platformTexture = None
56 self._isSimpleMode = True
57 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
60 self._modelMatrix = None
61 self._projMatrix = None
62 self.tempMatrix = None
64 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
65 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
66 self.printButton.setDisabled(True)
69 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
70 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
71 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
73 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
74 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
76 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
77 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
79 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
80 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
81 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
83 self.rotateToolButton.setExpandArrow(True)
84 self.scaleToolButton.setExpandArrow(True)
85 self.mirrorToolButton.setExpandArrow(True)
87 self.scaleForm = openglGui.glFrame(self, (2, -2))
88 openglGui.glGuiLayoutGrid(self.scaleForm)
89 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
90 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
91 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
92 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
93 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
94 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
95 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
96 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
97 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
98 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
99 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
100 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
101 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
102 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
104 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
106 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
107 self.youMagineButton.setDisabled(True)
109 self.notification = openglGui.glNotification(self, (0, 0))
111 self._engine = sliceEngine.Engine(self._updateEngineProgress)
112 self._engineResultView = engineResultView.engineResultView(self)
113 self._sceneUpdateTimer = wx.Timer(self)
114 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
115 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
116 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
120 self.updateToolButtons()
121 self.updateProfileToControls()
123 def loadGCodeFile(self, filename):
124 self.OnDeleteAll(None)
125 #Cheat the engine results to load a GCode file into it.
126 self._engine._result = sliceEngine.EngineResult()
127 with open(filename, "r") as f:
128 self._engine._result.setGCode(f.read())
129 self.printButton.setBottomText('')
130 self.viewSelection.setValue(4)
131 self.printButton.setDisabled(False)
132 self.youMagineButton.setDisabled(True)
135 def loadSceneFiles(self, filenames):
136 self.youMagineButton.setDisabled(False)
137 #if self.viewSelection.getValue() == 4:
138 # self.viewSelection.setValue(0)
139 # self.OnViewChange()
140 self.loadScene(filenames)
142 def loadFiles(self, filenames):
143 mainWindow = self.GetParent().GetParent().GetParent()
144 # only one GCODE file can be active
145 # so if single gcode file, process this
146 # otherwise ignore all gcode files
148 if len(filenames) == 1:
149 filename = filenames[0]
150 ext = os.path.splitext(filename)[1].lower()
151 if ext == '.g' or ext == '.gcode':
152 gcodeFilename = filename
153 mainWindow.addToModelMRU(filename)
154 if gcodeFilename is not None:
155 self.loadGCodeFile(gcodeFilename)
157 # process directories and special file types
158 # and keep scene files for later processing
160 ignored_types = dict()
161 # use file list as queue
162 # pop first entry for processing and append new files at end
164 filename = filenames.pop(0)
165 if os.path.isdir(filename):
166 # directory: queue all included files and directories
167 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
169 ext = os.path.splitext(filename)[1].lower()
171 profile.loadProfile(filename)
172 mainWindow.addToProfileMRU(filename)
173 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
174 scene_filenames.append(filename)
175 mainWindow.addToModelMRU(filename)
177 ignored_types[ext] = 1
179 ignored_types = ignored_types.keys()
181 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
182 mainWindow.updateProfileToAllControls()
183 # now process all the scene files
185 self.loadSceneFiles(scene_filenames)
186 self._selectObject(None)
188 newZoom = numpy.max(self._machineSize)
189 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
190 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
192 def showLoadModel(self, button = 1):
194 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)
196 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
197 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
198 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
199 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
200 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
201 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
202 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
203 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
205 dlg.SetWildcard(wildcardFilter)
206 if dlg.ShowModal() != wx.ID_OK:
209 filenames = dlg.GetPaths()
211 if len(filenames) < 1:
213 profile.putPreference('lastFile', filenames[0])
214 self.loadFiles(filenames)
216 def showSaveModel(self):
217 if len(self._scene.objects()) < 1:
219 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
220 fileExtensions = meshLoader.saveSupportedExtensions()
221 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
222 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
223 dlg.SetWildcard(wildcardFilter)
224 if dlg.ShowModal() != wx.ID_OK:
227 filename = dlg.GetPath()
229 meshLoader.saveMeshes(filename, self._scene.objects())
231 def OnPrintButton(self, button):
233 connectionGroup = self._printerConnectionManager.getAvailableGroup()
234 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
235 drives = removableStorage.getPossibleSDcardDrives()
237 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))
238 if dlg.ShowModal() != wx.ID_OK:
241 drive = drives[dlg.GetSelection()]
245 filename = self._scene._objectList[0].getName() + '.gcode'
246 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
247 elif connectionGroup is not None:
248 connections = connectionGroup.getAvailableConnections()
249 if len(connections) < 2:
250 connection = connections[0]
252 dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
253 if dlg.ShowModal() != wx.ID_OK:
256 connection = connections[dlg.GetSelection()]
258 self._openPrintWindowForConnection(connection)
263 connections = self._printerConnectionManager.getAvailableConnections()
264 menu.connectionMap = {}
265 for connection in connections:
266 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
267 menu.connectionMap[i.GetId()] = connection
268 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
269 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
270 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
274 def _openPrintWindowForConnection(self, connection):
275 if connection.window is None or not connection.window:
276 connection.window = None
277 windowType = profile.getPreference('printing_window')
278 for p in pluginInfo.getPluginList('printwindow'):
279 if p.getName() == windowType:
280 connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
282 if connection.window is None:
283 connection.window = printWindow.printWindowBasic(self, connection)
284 connection.window.Show()
285 connection.window.Raise()
286 if not connection.loadGCodeData(StringIO.StringIO(self._engine.getResult().getGCode())):
287 if connection.isPrinting():
288 self.notification.message("Cannot start print, because other print still running.")
290 self.notification.message("Failed to start print...")
292 def showSaveGCode(self):
293 if len(self._scene._objectList) < 1:
295 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
296 filename = self._scene._objectList[0].getName() + '.gcode'
297 dlg.SetFilename(filename)
298 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
299 if dlg.ShowModal() != wx.ID_OK:
302 filename = dlg.GetPath()
305 threading.Thread(target=self._saveGCode,args=(filename,)).start()
307 def _saveGCode(self, targetFilename, ejectDrive = False):
308 data = self._engine.getResult().getGCode()
310 size = float(len(data))
311 fsrc = StringIO.StringIO(data)
312 with open(targetFilename, 'wb') as fdst:
314 buf = fsrc.read(16*1024)
318 self.printButton.setProgressBar(float(fsrc.tell()) / size)
321 import sys, traceback
322 traceback.print_exc()
323 self.notification.message("Failed to save")
326 self.notification.message("Saved as %s" % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
327 elif explorer.hasExplorer():
328 self.notification.message("Saved as %s" % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, 'Open folder')
330 self.notification.message("Saved as %s" % (targetFilename))
331 self.printButton.setProgressBar(None)
332 self._engine.getResult().submitInfoOnline()
334 def _doEjectSD(self, drive):
335 if removableStorage.ejectDrive(drive):
336 self.notification.message('You can now eject the card.')
338 self.notification.message('Safe remove failed...')
340 def _showEngineLog(self):
341 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)
345 def OnToolSelect(self, button):
346 if self.rotateToolButton.getSelected():
347 self.tool = previewTools.toolRotate(self)
348 elif self.scaleToolButton.getSelected():
349 self.tool = previewTools.toolScale(self)
350 elif self.mirrorToolButton.getSelected():
351 self.tool = previewTools.toolNone(self)
353 self.tool = previewTools.toolNone(self)
354 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
355 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
356 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
357 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
358 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
359 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
360 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
361 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
363 def updateToolButtons(self):
364 if self._selectedObj is None:
368 self.rotateToolButton.setHidden(hidden)
369 self.scaleToolButton.setHidden(hidden)
370 self.mirrorToolButton.setHidden(hidden)
372 self.rotateToolButton.setSelected(False)
373 self.scaleToolButton.setSelected(False)
374 self.mirrorToolButton.setSelected(False)
377 def OnViewChange(self):
378 if self.viewSelection.getValue() == 4:
379 self.viewMode = 'gcode'
380 elif self.viewSelection.getValue() == 1:
381 self.viewMode = 'overhang'
382 elif self.viewSelection.getValue() == 2:
383 self.viewMode = 'transparent'
384 elif self.viewSelection.getValue() == 3:
385 self.viewMode = 'xray'
387 self.viewMode = 'normal'
388 self._engineResultView.setEnabled(self.viewMode == 'gcode')
391 def OnRotateReset(self, button):
392 if self._selectedObj is None:
394 self._selectedObj.resetRotation()
395 self._scene.pushFree(self._selectedObj)
396 self._selectObject(self._selectedObj)
399 def OnLayFlat(self, button):
400 if self._selectedObj is None:
402 self._selectedObj.layFlat()
403 self._scene.pushFree(self._selectedObj)
404 self._selectObject(self._selectedObj)
407 def OnScaleReset(self, button):
408 if self._selectedObj is None:
410 self._selectedObj.resetScale()
411 self._selectObject(self._selectedObj)
412 self.updateProfileToControls()
415 def OnScaleMax(self, button):
416 if self._selectedObj is None:
418 machine = profile.getMachineSetting('machine_type')
419 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
420 self._scene.pushFree(self._selectedObj)
422 if machine == "ultimaker2":
423 #This is bad and Jaime should feel bad!
424 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
425 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
426 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
427 self._scene.pushFree(self._selectedObj)
429 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
430 self._scene.pushFree(self._selectedObj)
431 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
432 self._scene.pushFree(self._selectedObj)
433 self._selectObject(self._selectedObj)
434 self.updateProfileToControls()
437 def OnMirror(self, axis):
438 if self._selectedObj is None:
440 self._selectedObj.mirror(axis)
443 def OnScaleEntry(self, value, axis):
444 if self._selectedObj is None:
450 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
451 self.updateProfileToControls()
452 self._scene.pushFree(self._selectedObj)
453 self._selectObject(self._selectedObj)
456 def OnScaleEntryMM(self, value, axis):
457 if self._selectedObj is None:
463 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
464 self.updateProfileToControls()
465 self._scene.pushFree(self._selectedObj)
466 self._selectObject(self._selectedObj)
469 def OnDeleteAll(self, e):
470 while len(self._scene.objects()) > 0:
471 self._deleteObject(self._scene.objects()[0])
472 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
474 def OnMultiply(self, e):
475 if self._focusObj is None:
478 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
479 if dlg.ShowModal() != wx.ID_OK:
488 self._scene.add(newObj)
489 self._scene.centerAll()
490 if not self._scene.checkPlatform(newObj):
495 self.notification.message("Could not create more then %d items" % (n - 1))
496 self._scene.remove(newObj)
497 self._scene.centerAll()
500 def OnSplitObject(self, e):
501 if self._focusObj is None:
503 self._scene.remove(self._focusObj)
504 for obj in self._focusObj.split(self._splitCallback):
505 if numpy.max(obj.getSize()) > 2.0:
507 self._scene.centerAll()
508 self._selectObject(None)
511 def OnCenter(self, e):
512 if self._focusObj is None:
514 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
515 self._scene.pushFree(self._selectedObj)
516 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
517 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
520 def _splitCallback(self, progress):
523 def OnMergeObjects(self, e):
524 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
525 if len(self._scene.objects()) == 2:
526 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
529 self._scene.merge(self._selectedObj, self._focusObj)
532 def sceneUpdated(self):
533 self._sceneUpdateTimer.Start(500, True)
534 self._engine.abortEngine()
535 self._scene.updateSizeOffsets()
538 def _onRunEngine(self, e):
539 if self._isSimpleMode:
540 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
541 self._engine.runEngine(self._scene)
542 if self._isSimpleMode:
543 profile.resetTempOverride()
545 def _updateEngineProgress(self, progressValue):
546 result = self._engine.getResult()
547 finished = result is not None and result.isFinished()
549 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
551 self.printButton.setDisabled(not finished)
552 if progressValue >= 0.0:
553 self.printButton.setProgressBar(progressValue)
555 self.printButton.setProgressBar(None)
556 self._engineResultView.setResult(result)
558 self.printButton.setProgressBar(None)
559 text = '%s' % (result.getPrintTime())
560 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
561 amount = result.getFilamentAmount(e)
564 text += '\n%s' % (amount)
565 cost = result.getFilamentCost(e)
567 text += '\n%s' % (cost)
568 self.printButton.setBottomText(text)
570 self.printButton.setBottomText('')
573 def loadScene(self, fileList):
574 for filename in fileList:
576 ext = os.path.splitext(filename)[1].lower()
577 if ext in imageToMesh.supportedExtensions():
578 imageToMesh.convertImageDialog(self, filename).Show()
581 objList = meshLoader.loadMeshes(filename)
583 traceback.print_exc()
586 if self._objectLoadShader is not None:
587 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
591 if not self._scene.checkPlatform(obj):
592 self._scene.centerAll()
593 self._selectObject(obj)
594 if obj.getScale()[0] < 1.0:
595 self.notification.message("Warning: Object scaled down.")
598 def _deleteObject(self, obj):
599 if obj == self._selectedObj:
600 self._selectObject(None)
601 if obj == self._focusObj:
602 self._focusObj = None
603 self._scene.remove(obj)
604 for m in obj._meshList:
605 if m.vbo is not None and m.vbo.decRef():
606 self.glReleaseList.append(m.vbo)
611 def _selectObject(self, obj, zoom = True):
612 if obj != self._selectedObj:
613 self._selectedObj = obj
614 self.updateModelSettingsToControls()
615 self.updateToolButtons()
616 if zoom and obj is not None:
617 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
618 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
619 newZoom = obj.getBoundaryCircle() * 6
620 if newZoom > numpy.max(self._machineSize) * 3:
621 newZoom = numpy.max(self._machineSize) * 3
622 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
624 def updateProfileToControls(self):
625 oldSimpleMode = self._isSimpleMode
626 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
627 if self._isSimpleMode != oldSimpleMode:
628 self._scene.arrangeAll()
630 self._scene.updateSizeOffsets(True)
631 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
632 self._objColors[0] = profile.getPreferenceColour('model_colour')
633 self._objColors[1] = profile.getPreferenceColour('model_colour2')
634 self._objColors[2] = profile.getPreferenceColour('model_colour3')
635 self._objColors[3] = profile.getPreferenceColour('model_colour4')
636 self._scene.updateMachineDimensions()
637 self.updateModelSettingsToControls()
639 def updateModelSettingsToControls(self):
640 if self._selectedObj is not None:
641 scale = self._selectedObj.getScale()
642 size = self._selectedObj.getSize()
643 self.scaleXctrl.setValue(round(scale[0], 2))
644 self.scaleYctrl.setValue(round(scale[1], 2))
645 self.scaleZctrl.setValue(round(scale[2], 2))
646 self.scaleXmmctrl.setValue(round(size[0], 2))
647 self.scaleYmmctrl.setValue(round(size[1], 2))
648 self.scaleZmmctrl.setValue(round(size[2], 2))
650 def OnKeyChar(self, keyCode):
651 if self._engineResultView.OnKeyChar(keyCode):
653 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
654 if self._selectedObj is not None:
655 self._deleteObject(self._selectedObj)
657 if keyCode == wx.WXK_UP:
658 if wx.GetKeyState(wx.WXK_SHIFT):
665 elif keyCode == wx.WXK_DOWN:
666 if wx.GetKeyState(wx.WXK_SHIFT):
668 if self._zoom > numpy.max(self._machineSize) * 3:
669 self._zoom = numpy.max(self._machineSize) * 3
673 elif keyCode == wx.WXK_LEFT:
676 elif keyCode == wx.WXK_RIGHT:
679 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
684 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
686 if self._zoom > numpy.max(self._machineSize) * 3:
687 self._zoom = numpy.max(self._machineSize) * 3
689 elif keyCode == wx.WXK_HOME:
693 elif keyCode == wx.WXK_PAGEUP:
697 elif keyCode == wx.WXK_PAGEDOWN:
701 elif keyCode == wx.WXK_END:
706 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
707 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
708 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
709 from collections import defaultdict
710 from gc import get_objects
711 self._beforeLeakTest = defaultdict(int)
712 for i in get_objects():
713 self._beforeLeakTest[type(i)] += 1
714 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
715 from collections import defaultdict
716 from gc import get_objects
717 self._afterLeakTest = defaultdict(int)
718 for i in get_objects():
719 self._afterLeakTest[type(i)] += 1
720 for k in self._afterLeakTest:
721 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
722 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
724 def ShaderUpdate(self, v, f):
725 s = openglHelpers.GLShader(v, f)
727 self._objectLoadShader.release()
728 self._objectLoadShader = s
729 for obj in self._scene.objects():
730 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
733 def OnMouseDown(self,e):
734 self._mouseX = e.GetX()
735 self._mouseY = e.GetY()
736 self._mouseClick3DPos = self._mouse3Dpos
737 self._mouseClickFocus = self._focusObj
739 self._mouseState = 'doubleClick'
741 if self._mouseState == 'dragObject' and self._selectedObj is not None:
742 self._scene.pushFree(self._selectedObj)
744 self._mouseState = 'dragOrClick'
745 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
746 p0 -= self.getObjectCenterPos() - self._viewTarget
747 p1 -= self.getObjectCenterPos() - self._viewTarget
748 if self.tool.OnDragStart(p0, p1):
749 self._mouseState = 'tool'
750 if self._mouseState == 'dragOrClick':
751 if e.GetButton() == 1:
752 if self._focusObj is not None:
753 self._selectObject(self._focusObj, False)
756 def OnMouseUp(self, e):
757 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
759 if self._mouseState == 'dragOrClick':
760 if e.GetButton() == 1:
761 self._selectObject(self._focusObj)
762 if e.GetButton() == 3:
764 if self._focusObj is not None:
766 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
767 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
768 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
769 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
770 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:
771 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
772 if len(self._scene.objects()) > 0:
773 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
774 if menu.MenuItemCount > 0:
777 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
778 self._scene.pushFree(self._selectedObj)
780 elif self._mouseState == 'tool':
781 if self.tempMatrix is not None and self._selectedObj is not None:
782 self._selectedObj.applyMatrix(self.tempMatrix)
783 self._scene.pushFree(self._selectedObj)
784 self._selectObject(self._selectedObj)
785 self.tempMatrix = None
786 self.tool.OnDragEnd()
788 self._mouseState = None
790 def OnMouseMotion(self,e):
791 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
792 p0 -= self.getObjectCenterPos() - self._viewTarget
793 p1 -= self.getObjectCenterPos() - self._viewTarget
795 if e.Dragging() and self._mouseState is not None:
796 if self._mouseState == 'tool':
797 self.tool.OnDrag(p0, p1)
798 elif not e.LeftIsDown() and e.RightIsDown():
799 self._mouseState = 'drag'
800 if wx.GetKeyState(wx.WXK_SHIFT):
801 a = math.cos(math.radians(self._yaw)) / 3.0
802 b = math.sin(math.radians(self._yaw)) / 3.0
803 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
804 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
805 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
806 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
808 self._yaw += e.GetX() - self._mouseX
809 self._pitch -= e.GetY() - self._mouseY
810 if self._pitch > 170:
814 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
815 self._mouseState = 'drag'
816 self._zoom += e.GetY() - self._mouseY
819 if self._zoom > numpy.max(self._machineSize) * 3:
820 self._zoom = numpy.max(self._machineSize) * 3
821 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
822 self._mouseState = 'dragObject'
823 z = max(0, self._mouseClick3DPos[2])
824 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
825 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
830 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
831 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
832 diff = cursorZ1 - cursorZ0
833 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
834 if not e.Dragging() or self._mouseState != 'tool':
835 self.tool.OnMouseMove(p0, p1)
837 self._mouseX = e.GetX()
838 self._mouseY = e.GetY()
840 def OnMouseWheel(self, e):
841 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
842 delta = max(min(delta,4),-4)
843 self._zoom *= 1.0 - delta / 10.0
846 if self._zoom > numpy.max(self._machineSize) * 3:
847 self._zoom = numpy.max(self._machineSize) * 3
850 def OnMouseLeave(self, e):
854 def getMouseRay(self, x, y):
855 if self._viewport is None:
856 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
857 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
858 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
859 p0 -= self._viewTarget
860 p1 -= self._viewTarget
863 def _init3DView(self):
864 # set viewing projection
865 size = self.GetSize()
866 glViewport(0, 0, size.GetWidth(), size.GetHeight())
869 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
871 glDisable(GL_RESCALE_NORMAL)
872 glDisable(GL_LIGHTING)
874 glEnable(GL_DEPTH_TEST)
875 glDisable(GL_CULL_FACE)
877 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
879 glClearColor(0.8, 0.8, 0.8, 1.0)
883 glMatrixMode(GL_PROJECTION)
885 aspect = float(size.GetWidth()) / float(size.GetHeight())
886 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
888 glMatrixMode(GL_MODELVIEW)
890 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
893 connectionGroup = self._printerConnectionManager.getAvailableGroup()
894 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
895 self.printButton._imageID = 2
896 self.printButton._tooltip = _("Toolpath to SD")
897 elif connectionGroup is not None:
898 self.printButton._imageID = connectionGroup.getIconID()
899 self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
901 self.printButton._imageID = 3
902 self.printButton._tooltip = _("Save toolpath")
904 if self._animView is not None:
905 self._viewTarget = self._animView.getPosition()
906 if self._animView.isDone():
907 self._animView = None
908 if self._animZoom is not None:
909 self._zoom = self._animZoom.getPosition()
910 if self._animZoom.isDone():
911 self._animZoom = None
912 if self._objectShader is None: #TODO: add loading shaders from file(s)
913 if openglHelpers.hasShaderSupport():
914 self._objectShader = openglHelpers.GLShader("""
915 varying float light_amount;
919 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
920 gl_FrontColor = gl_Color;
922 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
926 varying float light_amount;
930 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
933 self._objectOverhangShader = openglHelpers.GLShader("""
934 uniform float cosAngle;
935 uniform mat3 rotMatrix;
936 varying float light_amount;
940 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
941 gl_FrontColor = gl_Color;
943 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
945 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
947 light_amount = -10.0;
951 varying float light_amount;
955 if (light_amount == -10.0)
957 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
959 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
963 self._objectLoadShader = openglHelpers.GLShader("""
964 uniform float intensity;
966 varying float light_amount;
970 vec4 tmp = gl_Vertex;
971 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
972 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
973 gl_Position = gl_ModelViewProjectionMatrix * tmp;
974 gl_FrontColor = gl_Color;
976 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
980 uniform float intensity;
981 varying float light_amount;
985 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
988 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
989 self._objectShader = openglHelpers.GLFakeShader()
990 self._objectOverhangShader = openglHelpers.GLFakeShader()
991 self._objectLoadShader = None
993 glTranslate(0,0,-self._zoom)
994 glRotate(-self._pitch, 1,0,0)
995 glRotate(self._yaw, 0,0,1)
996 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
998 self._viewport = glGetIntegerv(GL_VIEWPORT)
999 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1000 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1002 glClearColor(1,1,1,1)
1003 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1005 if self.viewMode != 'gcode':
1006 for n in xrange(0, len(self._scene.objects())):
1007 obj = self._scene.objects()[n]
1008 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1009 self._renderObject(obj)
1011 if self._mouseX > -1: # mouse has not passed over the opengl window.
1013 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1014 if n < len(self._scene.objects()):
1015 self._focusObj = self._scene.objects()[n]
1017 self._focusObj = None
1018 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1019 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1020 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1021 self._mouse3Dpos -= self._viewTarget
1024 glTranslate(0,0,-self._zoom)
1025 glRotate(-self._pitch, 1,0,0)
1026 glRotate(self._yaw, 0,0,1)
1027 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1029 self._objectShader.unbind()
1030 self._engineResultView.OnDraw()
1031 if self.viewMode != 'gcode':
1032 glStencilFunc(GL_ALWAYS, 1, 1)
1033 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1035 if self.viewMode == 'overhang':
1036 self._objectOverhangShader.bind()
1037 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1039 self._objectShader.bind()
1040 for obj in self._scene.objects():
1041 if obj._loadAnim is not None:
1042 if obj._loadAnim.isDone():
1043 obj._loadAnim = None
1047 if self._focusObj == obj:
1049 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1052 if self._selectedObj == obj or self._selectedObj is None:
1053 #If we want transparent, then first render a solid black model to remove the printer size lines.
1054 if self.viewMode == 'transparent':
1055 glColor4f(0, 0, 0, 0)
1056 self._renderObject(obj)
1058 glBlendFunc(GL_ONE, GL_ONE)
1059 glDisable(GL_DEPTH_TEST)
1061 if self.viewMode == 'xray':
1062 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1063 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1064 glEnable(GL_STENCIL_TEST)
1066 if self.viewMode == 'overhang':
1067 if self._selectedObj == obj and self.tempMatrix is not None:
1068 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1070 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1072 if not self._scene.checkPlatform(obj):
1073 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1074 self._renderObject(obj)
1076 self._renderObject(obj, brightness)
1077 glDisable(GL_STENCIL_TEST)
1079 glEnable(GL_DEPTH_TEST)
1080 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1082 if self.viewMode == 'xray':
1085 glEnable(GL_STENCIL_TEST)
1086 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1087 glDisable(GL_DEPTH_TEST)
1088 for i in xrange(2, 15, 2): #All even values
1089 glStencilFunc(GL_EQUAL, i, 0xFF)
1090 glColor(float(i)/10, float(i)/10, float(i)/5)
1092 glVertex3f(-1000,-1000,-10)
1093 glVertex3f( 1000,-1000,-10)
1094 glVertex3f( 1000, 1000,-10)
1095 glVertex3f(-1000, 1000,-10)
1097 for i in xrange(1, 15, 2): #All odd values
1098 glStencilFunc(GL_EQUAL, i, 0xFF)
1099 glColor(float(i)/10, 0, 0)
1101 glVertex3f(-1000,-1000,-10)
1102 glVertex3f( 1000,-1000,-10)
1103 glVertex3f( 1000, 1000,-10)
1104 glVertex3f(-1000, 1000,-10)
1107 glDisable(GL_STENCIL_TEST)
1108 glEnable(GL_DEPTH_TEST)
1110 self._objectShader.unbind()
1112 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1114 if self._objectLoadShader is not None:
1115 self._objectLoadShader.bind()
1116 glColor4f(0.2, 0.6, 1.0, 1.0)
1117 for obj in self._scene.objects():
1118 if obj._loadAnim is None:
1120 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1121 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1122 self._renderObject(obj)
1123 self._objectLoadShader.unbind()
1128 if self.viewMode != 'gcode':
1129 #Draw the object box-shadow, so you can see where it will collide with other objects.
1130 if self._selectedObj is not None:
1132 glEnable(GL_CULL_FACE)
1133 glColor4f(0,0,0,0.16)
1135 for obj in self._scene.objects():
1137 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1138 glBegin(GL_TRIANGLE_FAN)
1139 for p in obj._boundaryHull[::-1]:
1140 glVertex3f(p[0], p[1], 0)
1143 if self._scene.isOneAtATime(): #Check print sequence mode.
1145 glColor4f(0,0,0,0.06)
1146 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1147 glBegin(GL_TRIANGLE_FAN)
1148 for p in self._selectedObj._printAreaHull[::-1]:
1149 glVertex3f(p[0], p[1], 0)
1151 glBegin(GL_TRIANGLE_FAN)
1152 for p in self._selectedObj._headAreaMinHull[::-1]:
1153 glVertex3f(p[0], p[1], 0)
1157 glDisable(GL_CULL_FACE)
1159 #Draw the outline of the selected object on top of everything else except the GUI.
1160 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1161 glDisable(GL_DEPTH_TEST)
1162 glEnable(GL_CULL_FACE)
1163 glEnable(GL_STENCIL_TEST)
1165 glStencilFunc(GL_EQUAL, 0, 255)
1167 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1169 glColor4f(1,1,1,0.5)
1170 self._renderObject(self._selectedObj)
1171 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1173 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1174 glDisable(GL_STENCIL_TEST)
1175 glDisable(GL_CULL_FACE)
1176 glEnable(GL_DEPTH_TEST)
1178 if self._selectedObj is not None:
1180 pos = self.getObjectCenterPos()
1181 glTranslate(pos[0], pos[1], pos[2])
1184 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1185 glDisable(GL_DEPTH_TEST)
1188 glTranslate(0,-4,-10)
1189 glColor4ub(60,60,60,255)
1190 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1193 def _renderObject(self, obj, brightness = 0, addSink = True):
1196 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1198 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1200 if self.tempMatrix is not None and obj == self._selectedObj:
1201 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1203 offset = obj.getDrawOffset()
1204 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1206 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1209 for m in obj._meshList:
1211 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1213 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1218 def _drawMachine(self):
1219 glEnable(GL_CULL_FACE)
1222 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1224 machine = profile.getMachineSetting('machine_type')
1225 if machine.startswith('ultimaker'):
1226 if machine not in self._platformMesh:
1227 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1229 self._platformMesh[machine] = meshes[0]
1231 self._platformMesh[machine] = None
1232 if machine == 'ultimaker2':
1233 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1235 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1236 glColor4f(1,1,1,0.5)
1237 self._objectShader.bind()
1238 self._renderObject(self._platformMesh[machine], False, False)
1239 self._objectShader.unbind()
1241 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1242 if machine == 'ultimaker2':
1243 if not hasattr(self._platformMesh[machine], 'texture'):
1244 self._platformMesh[machine].texture = openglHelpers.loadGLTexture('Ultimaker2backplate.png')
1245 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1246 glEnable(GL_TEXTURE_2D)
1250 glTranslate(0,150,-5)
1255 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1258 glVertex3f( w, 0, h)
1260 glVertex3f(-w, 0, h)
1262 glVertex3f(-w, 0, 0)
1264 glVertex3f( w, 0, 0)
1267 glVertex3f(-w, d, h)
1269 glVertex3f( w, d, h)
1271 glVertex3f( w, d, 0)
1273 glVertex3f(-w, d, 0)
1275 glDisable(GL_TEXTURE_2D)
1276 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1282 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1283 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1284 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1285 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1286 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1287 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1292 polys = profile.getMachineSizePolygons()
1293 height = profile.getMachineSettingFloat('machine_height')
1294 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1296 # Draw the sides of the build volume.
1297 for n in xrange(0, len(polys[0])):
1300 glColor4ub(5, 171, 231, 96)
1302 glColor4ub(5, 171, 231, 64)
1304 glColor4ub(5, 171, 231, 96)
1306 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1307 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1308 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1309 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1312 #Draw top of build volume.
1313 glColor4ub(5, 171, 231, 128)
1314 glBegin(GL_TRIANGLE_FAN)
1315 for p in polys[0][::-1]:
1316 glVertex3f(p[0], p[1], height)
1320 if self._platformTexture is None:
1321 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1322 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1323 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1324 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1325 glColor4f(1,1,1,0.5)
1326 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1327 glEnable(GL_TEXTURE_2D)
1328 glBegin(GL_TRIANGLE_FAN)
1330 glTexCoord2f(p[0]/20, p[1]/20)
1331 glVertex3f(p[0], p[1], 0)
1334 #Draw no-go zones. (clips in case of UM2)
1335 glDisable(GL_TEXTURE_2D)
1336 glColor4ub(127, 127, 127, 200)
1337 for poly in polys[1:]:
1338 glBegin(GL_TRIANGLE_FAN)
1340 glTexCoord2f(p[0]/20, p[1]/20)
1341 glVertex3f(p[0], p[1], 0)
1346 glDisable(GL_CULL_FACE)
1348 def getObjectCenterPos(self):
1349 if self._selectedObj is None:
1350 return [0.0, 0.0, 0.0]
1351 pos = self._selectedObj.getPosition()
1352 size = self._selectedObj.getSize()
1353 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1355 def getObjectBoundaryCircle(self):
1356 if self._selectedObj is None:
1358 return self._selectedObj.getBoundaryCircle()
1360 def getObjectSize(self):
1361 if self._selectedObj is None:
1362 return [0.0, 0.0, 0.0]
1363 return self._selectedObj.getSize()
1365 def getObjectMatrix(self):
1366 if self._selectedObj is None:
1367 return numpy.matrix(numpy.identity(3))
1368 return self._selectedObj.getMatrix()
1370 #TODO: Remove this or put it in a seperate file
1371 class shaderEditor(wx.Dialog):
1372 def __init__(self, parent, callback, v, f):
1373 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1374 self._callback = callback
1375 s = wx.BoxSizer(wx.VERTICAL)
1377 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1378 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1379 s.Add(self._vertex, 1, flag=wx.EXPAND)
1380 s.Add(self._fragment, 1, flag=wx.EXPAND)
1382 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1383 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1385 self.SetPosition(self.GetParent().GetPosition())
1386 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1389 def OnText(self, e):
1390 self._callback(self._vertex.GetValue(), self._fragment.GetValue())