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._engine._result.setFinished(True)
130 self._engineResultView.setResult(self._engine._result)
131 self.printButton.setBottomText('')
132 self.viewSelection.setValue(4)
133 self.printButton.setDisabled(False)
134 self.youMagineButton.setDisabled(True)
137 def loadSceneFiles(self, filenames):
138 self.youMagineButton.setDisabled(False)
139 #if self.viewSelection.getValue() == 4:
140 # self.viewSelection.setValue(0)
141 # self.OnViewChange()
142 self.loadScene(filenames)
144 def loadFiles(self, filenames):
145 mainWindow = self.GetParent().GetParent().GetParent()
146 # only one GCODE file can be active
147 # so if single gcode file, process this
148 # otherwise ignore all gcode files
150 if len(filenames) == 1:
151 filename = filenames[0]
152 ext = os.path.splitext(filename)[1].lower()
153 if ext == '.g' or ext == '.gcode':
154 gcodeFilename = filename
155 mainWindow.addToModelMRU(filename)
156 if gcodeFilename is not None:
157 self.loadGCodeFile(gcodeFilename)
159 # process directories and special file types
160 # and keep scene files for later processing
162 ignored_types = dict()
163 # use file list as queue
164 # pop first entry for processing and append new files at end
166 filename = filenames.pop(0)
167 if os.path.isdir(filename):
168 # directory: queue all included files and directories
169 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
171 ext = os.path.splitext(filename)[1].lower()
173 profile.loadProfile(filename)
174 mainWindow.addToProfileMRU(filename)
175 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
176 scene_filenames.append(filename)
177 mainWindow.addToModelMRU(filename)
179 ignored_types[ext] = 1
181 ignored_types = ignored_types.keys()
183 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
184 mainWindow.updateProfileToAllControls()
185 # now process all the scene files
187 self.loadSceneFiles(scene_filenames)
188 self._selectObject(None)
190 newZoom = numpy.max(self._machineSize)
191 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
192 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
194 def showLoadModel(self, button = 1):
196 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)
198 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
199 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
200 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
201 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
202 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
203 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
204 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
205 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
207 dlg.SetWildcard(wildcardFilter)
208 if dlg.ShowModal() != wx.ID_OK:
211 filenames = dlg.GetPaths()
213 if len(filenames) < 1:
215 profile.putPreference('lastFile', filenames[0])
216 self.loadFiles(filenames)
218 def showSaveModel(self):
219 if len(self._scene.objects()) < 1:
221 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
222 fileExtensions = meshLoader.saveSupportedExtensions()
223 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
224 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
225 dlg.SetWildcard(wildcardFilter)
226 if dlg.ShowModal() != wx.ID_OK:
229 filename = dlg.GetPath()
231 meshLoader.saveMeshes(filename, self._scene.objects())
233 def OnPrintButton(self, button):
235 connectionGroup = self._printerConnectionManager.getAvailableGroup()
236 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
237 drives = removableStorage.getPossibleSDcardDrives()
239 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))
240 if dlg.ShowModal() != wx.ID_OK:
243 drive = drives[dlg.GetSelection()]
247 filename = self._scene._objectList[0].getName() + '.gcode'
248 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
249 elif connectionGroup is not None:
250 connections = connectionGroup.getAvailableConnections()
251 if len(connections) < 2:
252 connection = connections[0]
254 dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
255 if dlg.ShowModal() != wx.ID_OK:
258 connection = connections[dlg.GetSelection()]
260 self._openPrintWindowForConnection(connection)
265 connections = self._printerConnectionManager.getAvailableConnections()
266 menu.connectionMap = {}
267 for connection in connections:
268 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
269 menu.connectionMap[i.GetId()] = connection
270 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
271 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
272 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
276 def _openPrintWindowForConnection(self, connection):
277 if connection.window is None or not connection.window:
278 connection.window = None
279 windowType = profile.getPreference('printing_window')
280 for p in pluginInfo.getPluginList('printwindow'):
281 if p.getName() == windowType:
282 connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
284 if connection.window is None:
285 connection.window = printWindow.printWindowBasic(self, connection)
286 connection.window.Show()
287 connection.window.Raise()
288 if not connection.loadGCodeData(StringIO.StringIO(self._engine.getResult().getGCode())):
289 if connection.isPrinting():
290 self.notification.message("Cannot start print, because other print still running.")
292 self.notification.message("Failed to start print...")
294 def showSaveGCode(self):
295 if len(self._scene._objectList) < 1:
297 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
298 filename = self._scene._objectList[0].getName() + '.gcode'
299 dlg.SetFilename(filename)
300 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
301 if dlg.ShowModal() != wx.ID_OK:
304 filename = dlg.GetPath()
307 threading.Thread(target=self._saveGCode,args=(filename,)).start()
309 def _saveGCode(self, targetFilename, ejectDrive = False):
310 data = self._engine.getResult().getGCode()
312 size = float(len(data))
313 fsrc = StringIO.StringIO(data)
314 with open(targetFilename, 'wb') as fdst:
316 buf = fsrc.read(16*1024)
320 self.printButton.setProgressBar(float(fsrc.tell()) / size)
323 import sys, traceback
324 traceback.print_exc()
325 self.notification.message("Failed to save")
328 self.notification.message("Saved as %s" % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
329 elif explorer.hasExplorer():
330 self.notification.message("Saved as %s" % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, 'Open folder')
332 self.notification.message("Saved as %s" % (targetFilename))
333 self.printButton.setProgressBar(None)
334 self._engine.getResult().submitInfoOnline()
336 def _doEjectSD(self, drive):
337 if removableStorage.ejectDrive(drive):
338 self.notification.message('You can now eject the card.')
340 self.notification.message('Safe remove failed...')
342 def _showEngineLog(self):
343 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)
347 def OnToolSelect(self, button):
348 if self.rotateToolButton.getSelected():
349 self.tool = previewTools.toolRotate(self)
350 elif self.scaleToolButton.getSelected():
351 self.tool = previewTools.toolScale(self)
352 elif self.mirrorToolButton.getSelected():
353 self.tool = previewTools.toolNone(self)
355 self.tool = previewTools.toolNone(self)
356 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
357 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
358 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
359 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
360 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
361 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
362 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
363 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
365 def updateToolButtons(self):
366 if self._selectedObj is None:
370 self.rotateToolButton.setHidden(hidden)
371 self.scaleToolButton.setHidden(hidden)
372 self.mirrorToolButton.setHidden(hidden)
374 self.rotateToolButton.setSelected(False)
375 self.scaleToolButton.setSelected(False)
376 self.mirrorToolButton.setSelected(False)
379 def OnViewChange(self):
380 if self.viewSelection.getValue() == 4:
381 self.viewMode = 'gcode'
382 self.tool = previewTools.toolNone(self)
383 elif self.viewSelection.getValue() == 1:
384 self.viewMode = 'overhang'
385 elif self.viewSelection.getValue() == 2:
386 self.viewMode = 'transparent'
387 elif self.viewSelection.getValue() == 3:
388 self.viewMode = 'xray'
390 self.viewMode = 'normal'
391 self._engineResultView.setEnabled(self.viewMode == 'gcode')
394 def OnRotateReset(self, button):
395 if self._selectedObj is None:
397 self._selectedObj.resetRotation()
398 self._scene.pushFree(self._selectedObj)
399 self._selectObject(self._selectedObj)
402 def OnLayFlat(self, button):
403 if self._selectedObj is None:
405 self._selectedObj.layFlat()
406 self._scene.pushFree(self._selectedObj)
407 self._selectObject(self._selectedObj)
410 def OnScaleReset(self, button):
411 if self._selectedObj is None:
413 self._selectedObj.resetScale()
414 self._selectObject(self._selectedObj)
415 self.updateProfileToControls()
418 def OnScaleMax(self, button):
419 if self._selectedObj is None:
421 machine = profile.getMachineSetting('machine_type')
422 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
423 self._scene.pushFree(self._selectedObj)
425 if machine == "ultimaker2":
426 #This is bad and Jaime should feel bad!
427 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
428 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
429 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
430 self._scene.pushFree(self._selectedObj)
432 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
433 self._scene.pushFree(self._selectedObj)
434 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
435 self._scene.pushFree(self._selectedObj)
436 self._selectObject(self._selectedObj)
437 self.updateProfileToControls()
440 def OnMirror(self, axis):
441 if self._selectedObj is None:
443 self._selectedObj.mirror(axis)
446 def OnScaleEntry(self, value, axis):
447 if self._selectedObj is None:
453 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
454 self.updateProfileToControls()
455 self._scene.pushFree(self._selectedObj)
456 self._selectObject(self._selectedObj)
459 def OnScaleEntryMM(self, value, axis):
460 if self._selectedObj is None:
466 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
467 self.updateProfileToControls()
468 self._scene.pushFree(self._selectedObj)
469 self._selectObject(self._selectedObj)
472 def OnDeleteAll(self, e):
473 while len(self._scene.objects()) > 0:
474 self._deleteObject(self._scene.objects()[0])
475 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
476 self._engineResultView.setResult(None)
478 def OnMultiply(self, e):
479 if self._focusObj is None:
482 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
483 if dlg.ShowModal() != wx.ID_OK:
492 self._scene.add(newObj)
493 self._scene.centerAll()
494 if not self._scene.checkPlatform(newObj):
499 self.notification.message("Could not create more then %d items" % (n - 1))
500 self._scene.remove(newObj)
501 self._scene.centerAll()
504 def OnSplitObject(self, e):
505 if self._focusObj is None:
507 self._scene.remove(self._focusObj)
508 for obj in self._focusObj.split(self._splitCallback):
509 if numpy.max(obj.getSize()) > 2.0:
511 self._scene.centerAll()
512 self._selectObject(None)
515 def OnCenter(self, e):
516 if self._focusObj is None:
518 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
519 self._scene.pushFree(self._selectedObj)
520 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
521 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
524 def _splitCallback(self, progress):
527 def OnMergeObjects(self, e):
528 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
529 if len(self._scene.objects()) == 2:
530 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
533 self._scene.merge(self._selectedObj, self._focusObj)
536 def sceneUpdated(self):
537 self._sceneUpdateTimer.Start(500, True)
538 self._engine.abortEngine()
539 self._scene.updateSizeOffsets()
542 def _onRunEngine(self, e):
543 if self._isSimpleMode:
544 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
545 self._engine.runEngine(self._scene)
546 if self._isSimpleMode:
547 profile.resetTempOverride()
549 def _updateEngineProgress(self, progressValue):
550 result = self._engine.getResult()
551 finished = result is not None and result.isFinished()
553 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
555 self.printButton.setDisabled(not finished)
556 if progressValue >= 0.0:
557 self.printButton.setProgressBar(progressValue)
559 self.printButton.setProgressBar(None)
560 self._engineResultView.setResult(result)
562 self.printButton.setProgressBar(None)
563 text = '%s' % (result.getPrintTime())
564 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
565 amount = result.getFilamentAmount(e)
568 text += '\n%s' % (amount)
569 cost = result.getFilamentCost(e)
571 text += '\n%s' % (cost)
572 self.printButton.setBottomText(text)
574 self.printButton.setBottomText('')
577 def loadScene(self, fileList):
578 for filename in fileList:
580 ext = os.path.splitext(filename)[1].lower()
581 if ext in imageToMesh.supportedExtensions():
582 imageToMesh.convertImageDialog(self, filename).Show()
585 objList = meshLoader.loadMeshes(filename)
587 traceback.print_exc()
590 if self._objectLoadShader is not None:
591 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
595 if not self._scene.checkPlatform(obj):
596 self._scene.centerAll()
597 self._selectObject(obj)
598 if obj.getScale()[0] < 1.0:
599 self.notification.message("Warning: Object scaled down.")
602 def _deleteObject(self, obj):
603 if obj == self._selectedObj:
604 self._selectObject(None)
605 if obj == self._focusObj:
606 self._focusObj = None
607 self._scene.remove(obj)
608 for m in obj._meshList:
609 if m.vbo is not None and m.vbo.decRef():
610 self.glReleaseList.append(m.vbo)
615 def _selectObject(self, obj, zoom = True):
616 if obj != self._selectedObj:
617 self._selectedObj = obj
618 self.updateModelSettingsToControls()
619 self.updateToolButtons()
620 if zoom and obj is not None:
621 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
622 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
623 newZoom = obj.getBoundaryCircle() * 6
624 if newZoom > numpy.max(self._machineSize) * 3:
625 newZoom = numpy.max(self._machineSize) * 3
626 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
628 def updateProfileToControls(self):
629 oldSimpleMode = self._isSimpleMode
630 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
631 if self._isSimpleMode != oldSimpleMode:
632 self._scene.arrangeAll()
634 self._scene.updateSizeOffsets(True)
635 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
636 self._objColors[0] = profile.getPreferenceColour('model_colour')
637 self._objColors[1] = profile.getPreferenceColour('model_colour2')
638 self._objColors[2] = profile.getPreferenceColour('model_colour3')
639 self._objColors[3] = profile.getPreferenceColour('model_colour4')
640 self._scene.updateMachineDimensions()
641 self.updateModelSettingsToControls()
643 def updateModelSettingsToControls(self):
644 if self._selectedObj is not None:
645 scale = self._selectedObj.getScale()
646 size = self._selectedObj.getSize()
647 self.scaleXctrl.setValue(round(scale[0], 2))
648 self.scaleYctrl.setValue(round(scale[1], 2))
649 self.scaleZctrl.setValue(round(scale[2], 2))
650 self.scaleXmmctrl.setValue(round(size[0], 2))
651 self.scaleYmmctrl.setValue(round(size[1], 2))
652 self.scaleZmmctrl.setValue(round(size[2], 2))
654 def OnKeyChar(self, keyCode):
655 if self._engineResultView.OnKeyChar(keyCode):
657 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
658 if self._selectedObj is not None:
659 self._deleteObject(self._selectedObj)
661 if keyCode == wx.WXK_UP:
662 if wx.GetKeyState(wx.WXK_SHIFT):
669 elif keyCode == wx.WXK_DOWN:
670 if wx.GetKeyState(wx.WXK_SHIFT):
672 if self._zoom > numpy.max(self._machineSize) * 3:
673 self._zoom = numpy.max(self._machineSize) * 3
677 elif keyCode == wx.WXK_LEFT:
680 elif keyCode == wx.WXK_RIGHT:
683 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
688 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
690 if self._zoom > numpy.max(self._machineSize) * 3:
691 self._zoom = numpy.max(self._machineSize) * 3
693 elif keyCode == wx.WXK_HOME:
697 elif keyCode == wx.WXK_PAGEUP:
701 elif keyCode == wx.WXK_PAGEDOWN:
705 elif keyCode == wx.WXK_END:
710 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
711 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
712 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
713 from collections import defaultdict
714 from gc import get_objects
715 self._beforeLeakTest = defaultdict(int)
716 for i in get_objects():
717 self._beforeLeakTest[type(i)] += 1
718 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
719 from collections import defaultdict
720 from gc import get_objects
721 self._afterLeakTest = defaultdict(int)
722 for i in get_objects():
723 self._afterLeakTest[type(i)] += 1
724 for k in self._afterLeakTest:
725 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
726 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
728 def ShaderUpdate(self, v, f):
729 s = openglHelpers.GLShader(v, f)
731 self._objectLoadShader.release()
732 self._objectLoadShader = s
733 for obj in self._scene.objects():
734 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
737 def OnMouseDown(self,e):
738 self._mouseX = e.GetX()
739 self._mouseY = e.GetY()
740 self._mouseClick3DPos = self._mouse3Dpos
741 self._mouseClickFocus = self._focusObj
743 self._mouseState = 'doubleClick'
745 if self._mouseState == 'dragObject' and self._selectedObj is not None:
746 self._scene.pushFree(self._selectedObj)
748 self._mouseState = 'dragOrClick'
749 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
750 p0 -= self.getObjectCenterPos() - self._viewTarget
751 p1 -= self.getObjectCenterPos() - self._viewTarget
752 if self.tool.OnDragStart(p0, p1):
753 self._mouseState = 'tool'
754 if self._mouseState == 'dragOrClick':
755 if e.GetButton() == 1:
756 if self._focusObj is not None:
757 self._selectObject(self._focusObj, False)
760 def OnMouseUp(self, e):
761 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
763 if self._mouseState == 'dragOrClick':
764 if e.GetButton() == 1:
765 self._selectObject(self._focusObj)
766 if e.GetButton() == 3:
768 if self._focusObj is not None:
770 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
771 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
772 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
773 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
774 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:
775 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
776 if len(self._scene.objects()) > 0:
777 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
778 if menu.MenuItemCount > 0:
781 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
782 self._scene.pushFree(self._selectedObj)
784 elif self._mouseState == 'tool':
785 if self.tempMatrix is not None and self._selectedObj is not None:
786 self._selectedObj.applyMatrix(self.tempMatrix)
787 self._scene.pushFree(self._selectedObj)
788 self._selectObject(self._selectedObj)
789 self.tempMatrix = None
790 self.tool.OnDragEnd()
792 self._mouseState = None
794 def OnMouseMotion(self,e):
795 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
796 p0 -= self.getObjectCenterPos() - self._viewTarget
797 p1 -= self.getObjectCenterPos() - self._viewTarget
799 if e.Dragging() and self._mouseState is not None:
800 if self._mouseState == 'tool':
801 self.tool.OnDrag(p0, p1)
802 elif not e.LeftIsDown() and e.RightIsDown():
803 self._mouseState = 'drag'
804 if wx.GetKeyState(wx.WXK_SHIFT):
805 a = math.cos(math.radians(self._yaw)) / 3.0
806 b = math.sin(math.radians(self._yaw)) / 3.0
807 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
808 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
809 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
810 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
812 self._yaw += e.GetX() - self._mouseX
813 self._pitch -= e.GetY() - self._mouseY
814 if self._pitch > 170:
818 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
819 self._mouseState = 'drag'
820 self._zoom += e.GetY() - self._mouseY
823 if self._zoom > numpy.max(self._machineSize) * 3:
824 self._zoom = numpy.max(self._machineSize) * 3
825 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
826 self._mouseState = 'dragObject'
827 z = max(0, self._mouseClick3DPos[2])
828 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
829 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
834 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
835 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
836 diff = cursorZ1 - cursorZ0
837 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
838 if not e.Dragging() or self._mouseState != 'tool':
839 self.tool.OnMouseMove(p0, p1)
841 self._mouseX = e.GetX()
842 self._mouseY = e.GetY()
844 def OnMouseWheel(self, e):
845 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
846 delta = max(min(delta,4),-4)
847 self._zoom *= 1.0 - delta / 10.0
850 if self._zoom > numpy.max(self._machineSize) * 3:
851 self._zoom = numpy.max(self._machineSize) * 3
854 def OnMouseLeave(self, e):
858 def getMouseRay(self, x, y):
859 if self._viewport is None:
860 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
861 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
862 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
863 p0 -= self._viewTarget
864 p1 -= self._viewTarget
867 def _init3DView(self):
868 # set viewing projection
869 size = self.GetSize()
870 glViewport(0, 0, size.GetWidth(), size.GetHeight())
873 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
875 glDisable(GL_RESCALE_NORMAL)
876 glDisable(GL_LIGHTING)
878 glEnable(GL_DEPTH_TEST)
879 glDisable(GL_CULL_FACE)
881 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
883 glClearColor(0.8, 0.8, 0.8, 1.0)
887 glMatrixMode(GL_PROJECTION)
889 aspect = float(size.GetWidth()) / float(size.GetHeight())
890 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
892 glMatrixMode(GL_MODELVIEW)
894 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
897 connectionGroup = self._printerConnectionManager.getAvailableGroup()
898 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
899 self.printButton._imageID = 2
900 self.printButton._tooltip = _("Toolpath to SD")
901 elif connectionGroup is not None:
902 self.printButton._imageID = connectionGroup.getIconID()
903 self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
905 self.printButton._imageID = 3
906 self.printButton._tooltip = _("Save toolpath")
908 if self._animView is not None:
909 self._viewTarget = self._animView.getPosition()
910 if self._animView.isDone():
911 self._animView = None
912 if self._animZoom is not None:
913 self._zoom = self._animZoom.getPosition()
914 if self._animZoom.isDone():
915 self._animZoom = None
916 if self._objectShader is None: #TODO: add loading shaders from file(s)
917 if openglHelpers.hasShaderSupport():
918 self._objectShader = openglHelpers.GLShader("""
919 varying float light_amount;
923 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
924 gl_FrontColor = gl_Color;
926 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
930 varying float light_amount;
934 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
937 self._objectOverhangShader = openglHelpers.GLShader("""
938 uniform float cosAngle;
939 uniform mat3 rotMatrix;
940 varying float light_amount;
944 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
945 gl_FrontColor = gl_Color;
947 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
949 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
951 light_amount = -10.0;
955 varying float light_amount;
959 if (light_amount == -10.0)
961 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
963 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
967 self._objectLoadShader = openglHelpers.GLShader("""
968 uniform float intensity;
970 varying float light_amount;
974 vec4 tmp = gl_Vertex;
975 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
976 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
977 gl_Position = gl_ModelViewProjectionMatrix * tmp;
978 gl_FrontColor = gl_Color;
980 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
984 uniform float intensity;
985 varying float light_amount;
989 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
992 if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
993 self._objectShader = openglHelpers.GLFakeShader()
994 self._objectOverhangShader = openglHelpers.GLFakeShader()
995 self._objectLoadShader = None
997 glTranslate(0,0,-self._zoom)
998 glRotate(-self._pitch, 1,0,0)
999 glRotate(self._yaw, 0,0,1)
1000 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1002 self._viewport = glGetIntegerv(GL_VIEWPORT)
1003 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1004 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1006 glClearColor(1,1,1,1)
1007 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1009 if self.viewMode != 'gcode':
1010 for n in xrange(0, len(self._scene.objects())):
1011 obj = self._scene.objects()[n]
1012 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1013 self._renderObject(obj)
1015 if self._mouseX > -1: # mouse has not passed over the opengl window.
1017 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1018 if n < len(self._scene.objects()):
1019 self._focusObj = self._scene.objects()[n]
1021 self._focusObj = None
1022 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1023 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1024 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1025 self._mouse3Dpos -= self._viewTarget
1028 glTranslate(0,0,-self._zoom)
1029 glRotate(-self._pitch, 1,0,0)
1030 glRotate(self._yaw, 0,0,1)
1031 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1033 self._objectShader.unbind()
1034 self._engineResultView.OnDraw()
1035 if self.viewMode != 'gcode':
1036 glStencilFunc(GL_ALWAYS, 1, 1)
1037 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1039 if self.viewMode == 'overhang':
1040 self._objectOverhangShader.bind()
1041 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1043 self._objectShader.bind()
1044 for obj in self._scene.objects():
1045 if obj._loadAnim is not None:
1046 if obj._loadAnim.isDone():
1047 obj._loadAnim = None
1051 if self._focusObj == obj:
1053 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1056 if self._selectedObj == obj or self._selectedObj is None:
1057 #If we want transparent, then first render a solid black model to remove the printer size lines.
1058 if self.viewMode == 'transparent':
1059 glColor4f(0, 0, 0, 0)
1060 self._renderObject(obj)
1062 glBlendFunc(GL_ONE, GL_ONE)
1063 glDisable(GL_DEPTH_TEST)
1065 if self.viewMode == 'xray':
1066 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1067 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1068 glEnable(GL_STENCIL_TEST)
1070 if self.viewMode == 'overhang':
1071 if self._selectedObj == obj and self.tempMatrix is not None:
1072 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1074 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1076 if not self._scene.checkPlatform(obj):
1077 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1078 self._renderObject(obj)
1080 self._renderObject(obj, brightness)
1081 glDisable(GL_STENCIL_TEST)
1083 glEnable(GL_DEPTH_TEST)
1084 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1086 if self.viewMode == 'xray':
1089 glEnable(GL_STENCIL_TEST)
1090 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1091 glDisable(GL_DEPTH_TEST)
1092 for i in xrange(2, 15, 2): #All even values
1093 glStencilFunc(GL_EQUAL, i, 0xFF)
1094 glColor(float(i)/10, float(i)/10, float(i)/5)
1096 glVertex3f(-1000,-1000,-10)
1097 glVertex3f( 1000,-1000,-10)
1098 glVertex3f( 1000, 1000,-10)
1099 glVertex3f(-1000, 1000,-10)
1101 for i in xrange(1, 15, 2): #All odd values
1102 glStencilFunc(GL_EQUAL, i, 0xFF)
1103 glColor(float(i)/10, 0, 0)
1105 glVertex3f(-1000,-1000,-10)
1106 glVertex3f( 1000,-1000,-10)
1107 glVertex3f( 1000, 1000,-10)
1108 glVertex3f(-1000, 1000,-10)
1111 glDisable(GL_STENCIL_TEST)
1112 glEnable(GL_DEPTH_TEST)
1114 self._objectShader.unbind()
1116 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1118 if self._objectLoadShader is not None:
1119 self._objectLoadShader.bind()
1120 glColor4f(0.2, 0.6, 1.0, 1.0)
1121 for obj in self._scene.objects():
1122 if obj._loadAnim is None:
1124 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1125 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1126 self._renderObject(obj)
1127 self._objectLoadShader.unbind()
1132 if self.viewMode != 'gcode':
1133 #Draw the object box-shadow, so you can see where it will collide with other objects.
1134 if self._selectedObj is not None:
1136 glEnable(GL_CULL_FACE)
1137 glColor4f(0,0,0,0.16)
1139 for obj in self._scene.objects():
1141 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1142 glBegin(GL_TRIANGLE_FAN)
1143 for p in obj._boundaryHull[::-1]:
1144 glVertex3f(p[0], p[1], 0)
1147 if self._scene.isOneAtATime(): #Check print sequence mode.
1149 glColor4f(0,0,0,0.06)
1150 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1151 glBegin(GL_TRIANGLE_FAN)
1152 for p in self._selectedObj._printAreaHull[::-1]:
1153 glVertex3f(p[0], p[1], 0)
1155 glBegin(GL_TRIANGLE_FAN)
1156 for p in self._selectedObj._headAreaMinHull[::-1]:
1157 glVertex3f(p[0], p[1], 0)
1161 glDisable(GL_CULL_FACE)
1163 #Draw the outline of the selected object on top of everything else except the GUI.
1164 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1165 glDisable(GL_DEPTH_TEST)
1166 glEnable(GL_CULL_FACE)
1167 glEnable(GL_STENCIL_TEST)
1169 glStencilFunc(GL_EQUAL, 0, 255)
1171 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1173 glColor4f(1,1,1,0.5)
1174 self._renderObject(self._selectedObj)
1175 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1177 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1178 glDisable(GL_STENCIL_TEST)
1179 glDisable(GL_CULL_FACE)
1180 glEnable(GL_DEPTH_TEST)
1182 if self._selectedObj is not None:
1184 pos = self.getObjectCenterPos()
1185 glTranslate(pos[0], pos[1], pos[2])
1188 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1189 glDisable(GL_DEPTH_TEST)
1192 glTranslate(0,-4,-10)
1193 glColor4ub(60,60,60,255)
1194 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1197 def _renderObject(self, obj, brightness = 0, addSink = True):
1200 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1202 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1204 if self.tempMatrix is not None and obj == self._selectedObj:
1205 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1207 offset = obj.getDrawOffset()
1208 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1210 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1213 for m in obj._meshList:
1215 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1217 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1222 def _drawMachine(self):
1223 glEnable(GL_CULL_FACE)
1226 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1228 machine = profile.getMachineSetting('machine_type')
1229 if machine.startswith('ultimaker'):
1230 if machine not in self._platformMesh:
1231 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1233 self._platformMesh[machine] = meshes[0]
1235 self._platformMesh[machine] = None
1236 if machine == 'ultimaker2':
1237 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1239 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1240 glColor4f(1,1,1,0.5)
1241 self._objectShader.bind()
1242 self._renderObject(self._platformMesh[machine], False, False)
1243 self._objectShader.unbind()
1245 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1246 if machine == 'ultimaker2':
1247 if not hasattr(self._platformMesh[machine], 'texture'):
1248 self._platformMesh[machine].texture = openglHelpers.loadGLTexture('Ultimaker2backplate.png')
1249 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1250 glEnable(GL_TEXTURE_2D)
1254 glTranslate(0,150,-5)
1259 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1262 glVertex3f( w, 0, h)
1264 glVertex3f(-w, 0, h)
1266 glVertex3f(-w, 0, 0)
1268 glVertex3f( w, 0, 0)
1271 glVertex3f(-w, d, h)
1273 glVertex3f( w, d, h)
1275 glVertex3f( w, d, 0)
1277 glVertex3f(-w, d, 0)
1279 glDisable(GL_TEXTURE_2D)
1280 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1286 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1287 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1288 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1289 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1290 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1291 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1296 polys = profile.getMachineSizePolygons()
1297 height = profile.getMachineSettingFloat('machine_height')
1298 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1300 # Draw the sides of the build volume.
1301 for n in xrange(0, len(polys[0])):
1304 glColor4ub(5, 171, 231, 96)
1306 glColor4ub(5, 171, 231, 64)
1308 glColor4ub(5, 171, 231, 96)
1310 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1311 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1312 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1313 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1316 #Draw top of build volume.
1317 glColor4ub(5, 171, 231, 128)
1318 glBegin(GL_TRIANGLE_FAN)
1319 for p in polys[0][::-1]:
1320 glVertex3f(p[0], p[1], height)
1324 if self._platformTexture is None:
1325 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1326 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1327 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1328 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1329 glColor4f(1,1,1,0.5)
1330 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1331 glEnable(GL_TEXTURE_2D)
1332 glBegin(GL_TRIANGLE_FAN)
1334 glTexCoord2f(p[0]/20, p[1]/20)
1335 glVertex3f(p[0], p[1], 0)
1338 #Draw no-go zones. (clips in case of UM2)
1339 glDisable(GL_TEXTURE_2D)
1340 glColor4ub(127, 127, 127, 200)
1341 for poly in polys[1:]:
1342 glBegin(GL_TRIANGLE_FAN)
1344 glTexCoord2f(p[0]/20, p[1]/20)
1345 glVertex3f(p[0], p[1], 0)
1350 glDisable(GL_CULL_FACE)
1352 def getObjectCenterPos(self):
1353 if self._selectedObj is None:
1354 return [0.0, 0.0, 0.0]
1355 pos = self._selectedObj.getPosition()
1356 size = self._selectedObj.getSize()
1357 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1359 def getObjectBoundaryCircle(self):
1360 if self._selectedObj is None:
1362 return self._selectedObj.getBoundaryCircle()
1364 def getObjectSize(self):
1365 if self._selectedObj is None:
1366 return [0.0, 0.0, 0.0]
1367 return self._selectedObj.getSize()
1369 def getObjectMatrix(self):
1370 if self._selectedObj is None:
1371 return numpy.matrix(numpy.identity(3))
1372 return self._selectedObj.getMatrix()
1374 #TODO: Remove this or put it in a seperate file
1375 class shaderEditor(wx.Frame):
1376 def __init__(self, parent, callback, v, f):
1377 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1378 self._callback = callback
1379 s = wx.BoxSizer(wx.VERTICAL)
1381 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1382 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1383 s.Add(self._vertex, 1, flag=wx.EXPAND)
1384 s.Add(self._fragment, 1, flag=wx.EXPAND)
1386 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1387 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1389 self.SetPosition(self.GetParent().GetPosition())
1390 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1393 def OnText(self, e):
1394 self._callback(self._vertex.GetValue(), self._fragment.GetValue())