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.gui import printWindow2
20 from Cura.util import profile
21 from Cura.util import meshLoader
22 from Cura.util import objectScene
23 from Cura.util import resources
24 from Cura.util import sliceEngine
25 from Cura.util import machineCom
26 from Cura.util import removableStorage
27 from Cura.util import explorer
28 from Cura.util.printerConnection import printerConnectionManager
29 from Cura.gui.util import previewTools
30 from Cura.gui.util import openglHelpers
31 from Cura.gui.util import openglGui
32 from Cura.gui.util import engineResultView
33 from Cura.gui.tools import youmagineGui
34 from Cura.gui.tools import imageToMesh
36 class SceneView(openglGui.glGuiPanel):
37 def __init__(self, parent):
38 super(SceneView, self).__init__(parent)
43 self._scene = objectScene.Scene()
44 self._objectShader = None
45 self._objectLoadShader = None
47 self._selectedObj = None
48 self._objColors = [None,None,None,None]
51 self._mouseState = None
52 self._viewTarget = numpy.array([0,0,0], numpy.float32)
55 self._platformMesh = {}
56 self._platformTexture = None
57 self._isSimpleMode = True
58 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
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,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
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()
125 def loadGCodeFile(self, filename):
126 self.OnDeleteAll(None)
127 #TODO: Load straight GCodeFile
128 self.printButton.setBottomText('')
129 self.viewSelection.setValue(4)
130 self.printButton.setDisabled(False)
131 self.youMagineButton.setDisabled(True)
134 def loadSceneFiles(self, filenames):
135 self.youMagineButton.setDisabled(False)
136 #if self.viewSelection.getValue() == 4:
137 # self.viewSelection.setValue(0)
138 # self.OnViewChange()
139 self.loadScene(filenames)
141 def loadFiles(self, filenames):
142 mainWindow = self.GetParent().GetParent().GetParent()
143 # only one GCODE file can be active
144 # so if single gcode file, process this
145 # otherwise ignore all gcode files
147 if len(filenames) == 1:
148 filename = filenames[0]
149 ext = os.path.splitext(filename)[1].lower()
150 if ext == '.g' or ext == '.gcode':
151 gcodeFilename = filename
152 mainWindow.addToModelMRU(filename)
153 if gcodeFilename is not None:
154 self.loadGCodeFile(gcodeFilename)
156 # process directories and special file types
157 # and keep scene files for later processing
159 ignored_types = dict()
160 # use file list as queue
161 # pop first entry for processing and append new files at end
163 filename = filenames.pop(0)
164 if os.path.isdir(filename):
165 # directory: queue all included files and directories
166 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
168 ext = os.path.splitext(filename)[1].lower()
170 profile.loadProfile(filename)
171 mainWindow.addToProfileMRU(filename)
172 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
173 scene_filenames.append(filename)
174 mainWindow.addToModelMRU(filename)
176 ignored_types[ext] = 1
178 ignored_types = ignored_types.keys()
180 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
181 mainWindow.updateProfileToAllControls()
182 # now process all the scene files
184 self.loadSceneFiles(scene_filenames)
185 self._selectObject(None)
187 newZoom = numpy.max(self._machineSize)
188 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
189 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
191 def showLoadModel(self, button = 1):
193 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)
195 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
196 wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
197 wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
198 wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
199 wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
200 wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
201 wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
202 wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
204 dlg.SetWildcard(wildcardFilter)
205 if dlg.ShowModal() != wx.ID_OK:
208 filenames = dlg.GetPaths()
210 if len(filenames) < 1:
212 profile.putPreference('lastFile', filenames[0])
213 self.loadFiles(filenames)
215 def showSaveModel(self):
216 if len(self._scene.objects()) < 1:
218 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
219 fileExtensions = meshLoader.saveSupportedExtensions()
220 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
221 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
222 dlg.SetWildcard(wildcardFilter)
223 if dlg.ShowModal() != wx.ID_OK:
226 filename = dlg.GetPath()
228 meshLoader.saveMeshes(filename, self._scene.objects())
230 def OnPrintButton(self, button):
232 connectionGroup = self._printerConnectionManager.getAvailableGroup()
233 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
234 drives = removableStorage.getPossibleSDcardDrives()
236 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))
237 if dlg.ShowModal() != wx.ID_OK:
240 drive = drives[dlg.GetSelection()]
244 filename = self._scene._objectList[0].getName() + '.gcode'
245 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
246 elif connectionGroup is not None:
247 connections = connectionGroup.getAvailableConnections()
248 if len(connections) < 2:
249 connection = connections[0]
251 dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
252 if dlg.ShowModal() != wx.ID_OK:
255 connection = connections[dlg.GetSelection()]
257 self._openPrintWindowForConnection(connection)
262 connections = self._printerConnectionManager.getAvailableConnections()
263 menu.connectionMap = {}
264 for connection in connections:
265 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
266 menu.connectionMap[i.GetId()] = connection
267 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
268 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
269 self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
273 def _openPrintWindowForConnection(self, connection):
274 print '_openPrintWindowForConnection', connection.getName()
275 if connection.window is None or not connection.window:
276 connection.window = printWindow2.printWindow(connection)
277 connection.window.Show()
278 connection.window.Raise()
279 if not connection.loadGCodeData(StringIO.StringIO(self._engine.getResult().getGCode())):
280 if connection.isPrinting():
281 self.notification.message("Cannot start print, because other print still running.")
283 self.notification.message("Failed to start print...")
285 def showSaveGCode(self):
286 if len(self._scene._objectList) < 1:
288 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
289 filename = self._scene._objectList[0].getName() + '.gcode'
290 dlg.SetFilename(filename)
291 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
292 if dlg.ShowModal() != wx.ID_OK:
295 filename = dlg.GetPath()
298 threading.Thread(target=self._saveGCode,args=(filename,)).start()
300 def _saveGCode(self, targetFilename, ejectDrive = False):
301 data = self._engine.getResult().getGCode()
303 size = float(len(data))
304 fsrc = StringIO.StringIO(data)
305 with open(targetFilename, 'wb') as fdst:
307 buf = fsrc.read(16*1024)
311 self.printButton.setProgressBar(float(fsrc.tell()) / size)
314 import sys, traceback
315 traceback.print_exc()
316 self.notification.message("Failed to save")
319 self.notification.message("Saved as %s" % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
320 elif explorer.hasExplorer():
321 self.notification.message("Saved as %s" % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, 'Open folder')
323 self.notification.message("Saved as %s" % (targetFilename))
324 self.printButton.setProgressBar(None)
325 self._engine.getResult().submitInfoOnline()
327 def _doEjectSD(self, drive):
328 if removableStorage.ejectDrive(drive):
329 self.notification.message('You can now eject the card.')
331 self.notification.message('Safe remove failed...')
333 def _showEngineLog(self):
334 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)
338 def OnToolSelect(self, button):
339 if self.rotateToolButton.getSelected():
340 self.tool = previewTools.toolRotate(self)
341 elif self.scaleToolButton.getSelected():
342 self.tool = previewTools.toolScale(self)
343 elif self.mirrorToolButton.getSelected():
344 self.tool = previewTools.toolNone(self)
346 self.tool = previewTools.toolNone(self)
347 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
348 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
349 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
350 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
351 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
352 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
353 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
354 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
356 def updateToolButtons(self):
357 if self._selectedObj is None:
361 self.rotateToolButton.setHidden(hidden)
362 self.scaleToolButton.setHidden(hidden)
363 self.mirrorToolButton.setHidden(hidden)
365 self.rotateToolButton.setSelected(False)
366 self.scaleToolButton.setSelected(False)
367 self.mirrorToolButton.setSelected(False)
370 def OnViewChange(self):
371 if self.viewSelection.getValue() == 4:
372 self.viewMode = 'gcode'
373 elif self.viewSelection.getValue() == 1:
374 self.viewMode = 'overhang'
375 elif self.viewSelection.getValue() == 2:
376 self.viewMode = 'transparent'
377 elif self.viewSelection.getValue() == 3:
378 self.viewMode = 'xray'
380 self.viewMode = 'normal'
381 self._engineResultView.setEnabled(self.viewMode == 'gcode')
384 def OnRotateReset(self, button):
385 if self._selectedObj is None:
387 self._selectedObj.resetRotation()
388 self._scene.pushFree(self._selectedObj)
389 self._selectObject(self._selectedObj)
392 def OnLayFlat(self, button):
393 if self._selectedObj is None:
395 self._selectedObj.layFlat()
396 self._scene.pushFree(self._selectedObj)
397 self._selectObject(self._selectedObj)
400 def OnScaleReset(self, button):
401 if self._selectedObj is None:
403 self._selectedObj.resetScale()
404 self._selectObject(self._selectedObj)
405 self.updateProfileToControls()
408 def OnScaleMax(self, button):
409 if self._selectedObj is None:
411 machine = profile.getMachineSetting('machine_type')
412 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
413 self._scene.pushFree(self._selectedObj)
415 if machine == "ultimaker2":
416 #This is bad and Jaime should feel bad!
417 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
418 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
419 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
420 self._scene.pushFree(self._selectedObj)
422 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
423 self._scene.pushFree(self._selectedObj)
424 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
425 self._scene.pushFree(self._selectedObj)
426 self._selectObject(self._selectedObj)
427 self.updateProfileToControls()
430 def OnMirror(self, axis):
431 if self._selectedObj is None:
433 self._selectedObj.mirror(axis)
436 def OnScaleEntry(self, value, axis):
437 if self._selectedObj is None:
443 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
444 self.updateProfileToControls()
445 self._scene.pushFree(self._selectedObj)
446 self._selectObject(self._selectedObj)
449 def OnScaleEntryMM(self, value, axis):
450 if self._selectedObj is None:
456 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
457 self.updateProfileToControls()
458 self._scene.pushFree(self._selectedObj)
459 self._selectObject(self._selectedObj)
462 def OnDeleteAll(self, e):
463 while len(self._scene.objects()) > 0:
464 self._deleteObject(self._scene.objects()[0])
465 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
467 def OnMultiply(self, e):
468 if self._focusObj is None:
471 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
472 if dlg.ShowModal() != wx.ID_OK:
481 self._scene.add(newObj)
482 self._scene.centerAll()
483 if not self._scene.checkPlatform(newObj):
488 self.notification.message("Could not create more then %d items" % (n - 1))
489 self._scene.remove(newObj)
490 self._scene.centerAll()
493 def OnSplitObject(self, e):
494 if self._focusObj is None:
496 self._scene.remove(self._focusObj)
497 for obj in self._focusObj.split(self._splitCallback):
498 if numpy.max(obj.getSize()) > 2.0:
500 self._scene.centerAll()
501 self._selectObject(None)
504 def OnCenter(self, e):
505 if self._focusObj is None:
507 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
508 self._scene.pushFree(self._selectedObj)
509 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
510 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
513 def _splitCallback(self, progress):
516 def OnMergeObjects(self, e):
517 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
518 if len(self._scene.objects()) == 2:
519 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
522 self._scene.merge(self._selectedObj, self._focusObj)
525 def sceneUpdated(self):
526 self._sceneUpdateTimer.Start(500, True)
527 self._engine.abortEngine()
528 self._scene.updateSizeOffsets()
531 def _onRunEngine(self, e):
532 if self._isSimpleMode:
533 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
534 self._engine.runEngine(self._scene)
535 if self._isSimpleMode:
536 profile.resetTempOverride()
538 def _updateEngineProgress(self, progressValue):
539 result = self._engine.getResult()
540 finished = result is not None and result.isFinished()
542 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
544 self.printButton.setDisabled(not finished)
545 if progressValue >= 0.0:
546 self.printButton.setProgressBar(progressValue)
548 self.printButton.setProgressBar(None)
549 self._engineResultView.setResult(result)
551 self.printButton.setProgressBar(None)
552 text = '%s' % (result.getPrintTime())
553 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
554 amount = result.getFilamentAmount(e)
557 text += '\n%s' % (amount)
558 cost = result.getFilamentCost(e)
560 text += '\n%s' % (cost)
561 self.printButton.setBottomText(text)
563 self.printButton.setBottomText('')
566 def loadScene(self, fileList):
567 for filename in fileList:
569 ext = os.path.splitext(filename)[1].lower()
570 if ext in imageToMesh.supportedExtensions():
571 imageToMesh.convertImageDialog(self, filename).Show()
574 objList = meshLoader.loadMeshes(filename)
576 traceback.print_exc()
579 if self._objectLoadShader is not None:
580 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
584 if not self._scene.checkPlatform(obj):
585 self._scene.centerAll()
586 self._selectObject(obj)
587 if obj.getScale()[0] < 1.0:
588 self.notification.message("Warning: Object scaled down.")
591 def _deleteObject(self, obj):
592 if obj == self._selectedObj:
593 self._selectObject(None)
594 if obj == self._focusObj:
595 self._focusObj = None
596 self._scene.remove(obj)
597 for m in obj._meshList:
598 if m.vbo is not None and m.vbo.decRef():
599 self.glReleaseList.append(m.vbo)
604 def _selectObject(self, obj, zoom = True):
605 if obj != self._selectedObj:
606 self._selectedObj = obj
607 self.updateModelSettingsToControls()
608 self.updateToolButtons()
609 if zoom and obj is not None:
610 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
611 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
612 newZoom = obj.getBoundaryCircle() * 6
613 if newZoom > numpy.max(self._machineSize) * 3:
614 newZoom = numpy.max(self._machineSize) * 3
615 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
617 def updateProfileToControls(self):
618 oldSimpleMode = self._isSimpleMode
619 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
620 if self._isSimpleMode != oldSimpleMode:
621 self._scene.arrangeAll()
623 self._scene.updateSizeOffsets(True)
624 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
625 self._objColors[0] = profile.getPreferenceColour('model_colour')
626 self._objColors[1] = profile.getPreferenceColour('model_colour2')
627 self._objColors[2] = profile.getPreferenceColour('model_colour3')
628 self._objColors[3] = profile.getPreferenceColour('model_colour4')
629 self._scene.updateMachineDimensions()
630 self.updateModelSettingsToControls()
632 def updateModelSettingsToControls(self):
633 if self._selectedObj is not None:
634 scale = self._selectedObj.getScale()
635 size = self._selectedObj.getSize()
636 self.scaleXctrl.setValue(round(scale[0], 2))
637 self.scaleYctrl.setValue(round(scale[1], 2))
638 self.scaleZctrl.setValue(round(scale[2], 2))
639 self.scaleXmmctrl.setValue(round(size[0], 2))
640 self.scaleYmmctrl.setValue(round(size[1], 2))
641 self.scaleZmmctrl.setValue(round(size[2], 2))
643 def OnKeyChar(self, keyCode):
644 if self._engineResultView.OnKeyChar(keyCode):
646 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
647 if self._selectedObj is not None:
648 self._deleteObject(self._selectedObj)
650 if keyCode == wx.WXK_UP:
651 if wx.GetKeyState(wx.WXK_SHIFT):
658 elif keyCode == wx.WXK_DOWN:
659 if wx.GetKeyState(wx.WXK_SHIFT):
661 if self._zoom > numpy.max(self._machineSize) * 3:
662 self._zoom = numpy.max(self._machineSize) * 3
666 elif keyCode == wx.WXK_LEFT:
669 elif keyCode == wx.WXK_RIGHT:
672 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
677 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
679 if self._zoom > numpy.max(self._machineSize) * 3:
680 self._zoom = numpy.max(self._machineSize) * 3
682 elif keyCode == wx.WXK_HOME:
686 elif keyCode == wx.WXK_PAGEUP:
690 elif keyCode == wx.WXK_PAGEDOWN:
694 elif keyCode == wx.WXK_END:
699 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
700 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
701 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
702 from collections import defaultdict
703 from gc import get_objects
704 self._beforeLeakTest = defaultdict(int)
705 for i in get_objects():
706 self._beforeLeakTest[type(i)] += 1
707 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
708 from collections import defaultdict
709 from gc import get_objects
710 self._afterLeakTest = defaultdict(int)
711 for i in get_objects():
712 self._afterLeakTest[type(i)] += 1
713 for k in self._afterLeakTest:
714 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
715 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
717 def ShaderUpdate(self, v, f):
718 s = openglHelpers.GLShader(v, f)
720 self._objectLoadShader.release()
721 self._objectLoadShader = s
722 for obj in self._scene.objects():
723 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
726 def OnMouseDown(self,e):
727 self._mouseX = e.GetX()
728 self._mouseY = e.GetY()
729 self._mouseClick3DPos = self._mouse3Dpos
730 self._mouseClickFocus = self._focusObj
732 self._mouseState = 'doubleClick'
734 self._mouseState = 'dragOrClick'
735 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
736 p0 -= self.getObjectCenterPos() - self._viewTarget
737 p1 -= self.getObjectCenterPos() - self._viewTarget
738 if self.tool.OnDragStart(p0, p1):
739 self._mouseState = 'tool'
740 if self._mouseState == 'dragOrClick':
741 if e.GetButton() == 1:
742 if self._focusObj is not None:
743 self._selectObject(self._focusObj, False)
746 def OnMouseUp(self, e):
747 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
749 if self._mouseState == 'dragOrClick':
750 if e.GetButton() == 1:
751 self._selectObject(self._focusObj)
752 if e.GetButton() == 3:
754 if self._focusObj is not None:
755 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
756 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
757 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
758 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
759 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:
760 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
761 if len(self._scene.objects()) > 0:
762 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
763 if menu.MenuItemCount > 0:
766 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
767 self._scene.pushFree(self._selectedObj)
769 elif self._mouseState == 'tool':
770 if self.tempMatrix is not None and self._selectedObj is not None:
771 self._selectedObj.applyMatrix(self.tempMatrix)
772 self._scene.pushFree(self._selectedObj)
773 self._selectObject(self._selectedObj)
774 self.tempMatrix = None
775 self.tool.OnDragEnd()
777 self._mouseState = None
779 def OnMouseMotion(self,e):
780 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
781 p0 -= self.getObjectCenterPos() - self._viewTarget
782 p1 -= self.getObjectCenterPos() - self._viewTarget
784 if e.Dragging() and self._mouseState is not None:
785 if self._mouseState == 'tool':
786 self.tool.OnDrag(p0, p1)
787 elif not e.LeftIsDown() and e.RightIsDown():
788 if self._mouseState == 'dragObject' and self._selectedObj is not None:
789 self._scene.pushFree(self._selectedObj)
791 self._mouseState = 'drag'
792 if wx.GetKeyState(wx.WXK_SHIFT):
793 a = math.cos(math.radians(self._yaw)) / 3.0
794 b = math.sin(math.radians(self._yaw)) / 3.0
795 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
796 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
797 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
798 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
800 self._yaw += e.GetX() - self._mouseX
801 self._pitch -= e.GetY() - self._mouseY
802 if self._pitch > 170:
806 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
807 if self._mouseState == 'dragObject' and self._selectedObj is not None:
808 self._scene.pushFree(self._selectedObj)
810 self._mouseState = 'drag'
811 self._zoom += e.GetY() - self._mouseY
814 if self._zoom > numpy.max(self._machineSize) * 3:
815 self._zoom = numpy.max(self._machineSize) * 3
816 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
817 self._mouseState = 'dragObject'
818 z = max(0, self._mouseClick3DPos[2])
819 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
820 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
825 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
826 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
827 diff = cursorZ1 - cursorZ0
828 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
829 if not e.Dragging() or self._mouseState != 'tool':
830 self.tool.OnMouseMove(p0, p1)
832 self._mouseX = e.GetX()
833 self._mouseY = e.GetY()
835 def OnMouseWheel(self, e):
836 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
837 delta = max(min(delta,4),-4)
838 self._zoom *= 1.0 - delta / 10.0
841 if self._zoom > numpy.max(self._machineSize) * 3:
842 self._zoom = numpy.max(self._machineSize) * 3
845 def OnMouseLeave(self, e):
849 def getMouseRay(self, x, y):
850 if self._viewport is None:
851 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
852 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
853 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
854 p0 -= self._viewTarget
855 p1 -= self._viewTarget
858 def _init3DView(self):
859 # set viewing projection
860 size = self.GetSize()
861 glViewport(0, 0, size.GetWidth(), size.GetHeight())
864 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
866 glDisable(GL_RESCALE_NORMAL)
867 glDisable(GL_LIGHTING)
869 glEnable(GL_DEPTH_TEST)
870 glDisable(GL_CULL_FACE)
872 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
874 glClearColor(0.8, 0.8, 0.8, 1.0)
878 glMatrixMode(GL_PROJECTION)
880 aspect = float(size.GetWidth()) / float(size.GetHeight())
881 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
883 glMatrixMode(GL_MODELVIEW)
885 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
888 connectionGroup = self._printerConnectionManager.getAvailableGroup()
889 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
890 self.printButton._imageID = 2
891 self.printButton._tooltip = _("Toolpath to SD")
892 elif connectionGroup is not None:
893 self.printButton._imageID = connectionGroup.getIconID()
894 self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
896 self.printButton._imageID = 3
897 self.printButton._tooltip = _("Save toolpath")
899 if self._animView is not None:
900 self._viewTarget = self._animView.getPosition()
901 if self._animView.isDone():
902 self._animView = None
903 if self._animZoom is not None:
904 self._zoom = self._animZoom.getPosition()
905 if self._animZoom.isDone():
906 self._animZoom = None
907 if self._objectShader is None:
908 if openglHelpers.hasShaderSupport():
909 self._objectShader = openglHelpers.GLShader("""
910 varying float light_amount;
914 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
915 gl_FrontColor = gl_Color;
917 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
921 varying float light_amount;
925 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
928 self._objectOverhangShader = openglHelpers.GLShader("""
929 uniform float cosAngle;
930 uniform mat3 rotMatrix;
931 varying float light_amount;
935 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
936 gl_FrontColor = gl_Color;
938 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
940 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
942 light_amount = -10.0;
946 varying float light_amount;
950 if (light_amount == -10.0)
952 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
954 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
958 self._objectLoadShader = openglHelpers.GLShader("""
959 uniform float intensity;
961 varying float light_amount;
965 vec4 tmp = gl_Vertex;
966 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
967 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
968 gl_Position = gl_ModelViewProjectionMatrix * tmp;
969 gl_FrontColor = gl_Color;
971 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
975 uniform float intensity;
976 varying float light_amount;
980 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
983 if self._objectShader is None or not self._objectShader.isValid():
984 self._objectShader = openglHelpers.GLFakeShader()
985 self._objectOverhangShader = openglHelpers.GLFakeShader()
986 self._objectLoadShader = None
988 glTranslate(0,0,-self._zoom)
989 glRotate(-self._pitch, 1,0,0)
990 glRotate(self._yaw, 0,0,1)
991 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
993 self._viewport = glGetIntegerv(GL_VIEWPORT)
994 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
995 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
997 glClearColor(1,1,1,1)
998 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1000 if self.viewMode != 'gcode':
1001 for n in xrange(0, len(self._scene.objects())):
1002 obj = self._scene.objects()[n]
1003 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1004 self._renderObject(obj)
1006 if self._mouseX > -1:
1008 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1009 if n < len(self._scene.objects()):
1010 self._focusObj = self._scene.objects()[n]
1012 self._focusObj = None
1013 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1014 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1015 self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1016 self._mouse3Dpos -= self._viewTarget
1019 glTranslate(0,0,-self._zoom)
1020 glRotate(-self._pitch, 1,0,0)
1021 glRotate(self._yaw, 0,0,1)
1022 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1024 self._objectShader.unbind()
1025 self._engineResultView.OnDraw()
1026 if self.viewMode != 'gcode':
1027 glStencilFunc(GL_ALWAYS, 1, 1)
1028 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1030 if self.viewMode == 'overhang':
1031 self._objectOverhangShader.bind()
1032 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1034 self._objectShader.bind()
1035 for obj in self._scene.objects():
1036 if obj._loadAnim is not None:
1037 if obj._loadAnim.isDone():
1038 obj._loadAnim = None
1042 if self._focusObj == obj:
1044 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1047 if self._selectedObj == obj or self._selectedObj is None:
1048 #If we want transparent, then first render a solid black model to remove the printer size lines.
1049 if self.viewMode == 'transparent':
1050 glColor4f(0, 0, 0, 0)
1051 self._renderObject(obj)
1053 glBlendFunc(GL_ONE, GL_ONE)
1054 glDisable(GL_DEPTH_TEST)
1056 if self.viewMode == 'xray':
1057 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1058 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1059 glEnable(GL_STENCIL_TEST)
1061 if self.viewMode == 'overhang':
1062 if self._selectedObj == obj and self.tempMatrix is not None:
1063 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1065 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1067 if not self._scene.checkPlatform(obj):
1068 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1069 self._renderObject(obj)
1071 self._renderObject(obj, brightness)
1072 glDisable(GL_STENCIL_TEST)
1074 glEnable(GL_DEPTH_TEST)
1075 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1077 if self.viewMode == 'xray':
1080 glEnable(GL_STENCIL_TEST)
1081 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1082 glDisable(GL_DEPTH_TEST)
1083 for i in xrange(2, 15, 2): #All even values
1084 glStencilFunc(GL_EQUAL, i, 0xFF)
1085 glColor(float(i)/10, float(i)/10, float(i)/5)
1087 glVertex3f(-1000,-1000,-10)
1088 glVertex3f( 1000,-1000,-10)
1089 glVertex3f( 1000, 1000,-10)
1090 glVertex3f(-1000, 1000,-10)
1092 for i in xrange(1, 15, 2): #All odd values
1093 glStencilFunc(GL_EQUAL, i, 0xFF)
1094 glColor(float(i)/10, 0, 0)
1096 glVertex3f(-1000,-1000,-10)
1097 glVertex3f( 1000,-1000,-10)
1098 glVertex3f( 1000, 1000,-10)
1099 glVertex3f(-1000, 1000,-10)
1102 glDisable(GL_STENCIL_TEST)
1103 glEnable(GL_DEPTH_TEST)
1105 self._objectShader.unbind()
1107 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1109 if self._objectLoadShader is not None:
1110 self._objectLoadShader.bind()
1111 glColor4f(0.2, 0.6, 1.0, 1.0)
1112 for obj in self._scene.objects():
1113 if obj._loadAnim is None:
1115 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1116 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1117 self._renderObject(obj)
1118 self._objectLoadShader.unbind()
1123 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._engine.getID():
1124 z = self._usbPrintMonitor.getZ()
1125 if self.viewMode == 'gcode':
1126 layer_height = profile.getProfileSettingFloat('layer_height')
1127 layer1_height = profile.getProfileSettingFloat('bottom_thickness')
1128 if layer_height > 0:
1129 if layer1_height > 0:
1130 layer = int((z - layer1_height) / layer_height) + 1
1132 layer = int(z / layer_height)
1135 self.layerSelect.setValue(layer)
1137 size = self._machineSize #Typing is hard.
1139 glColor4ub(255,255,0,128)
1141 glVertex3f(-size[0]/2,-size[1]/2, z)
1142 glVertex3f( size[0]/2,-size[1]/2, z)
1143 glVertex3f( size[0]/2, size[1]/2, z)
1144 glVertex3f(-size[0]/2, size[1]/2, z)
1147 if self.viewMode != 'gcode':
1148 #Draw the object box-shadow, so you can see where it will collide with other objects.
1149 if self._selectedObj is not None:
1151 glEnable(GL_CULL_FACE)
1152 glColor4f(0,0,0,0.16)
1154 for obj in self._scene.objects():
1156 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1157 glBegin(GL_TRIANGLE_FAN)
1158 for p in obj._boundaryHull[::-1]:
1159 glVertex3f(p[0], p[1], 0)
1162 if self._scene.isOneAtATime(): #Check print sequence mode.
1164 glColor4f(0,0,0,0.06)
1165 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1166 glBegin(GL_TRIANGLE_FAN)
1167 for p in self._selectedObj._printAreaHull[::-1]:
1168 glVertex3f(p[0], p[1], 0)
1170 glBegin(GL_TRIANGLE_FAN)
1171 for p in self._selectedObj._headAreaMinHull[::-1]:
1172 glVertex3f(p[0], p[1], 0)
1176 glDisable(GL_CULL_FACE)
1178 #Draw the outline of the selected object on top of everything else except the GUI.
1179 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1180 glDisable(GL_DEPTH_TEST)
1181 glEnable(GL_CULL_FACE)
1182 glEnable(GL_STENCIL_TEST)
1184 glStencilFunc(GL_EQUAL, 0, 255)
1186 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1188 glColor4f(1,1,1,0.5)
1189 self._renderObject(self._selectedObj)
1190 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1192 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1193 glDisable(GL_STENCIL_TEST)
1194 glDisable(GL_CULL_FACE)
1195 glEnable(GL_DEPTH_TEST)
1197 if self._selectedObj is not None:
1199 pos = self.getObjectCenterPos()
1200 glTranslate(pos[0], pos[1], pos[2])
1203 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1204 glDisable(GL_DEPTH_TEST)
1207 glTranslate(0,-4,-10)
1208 glColor4ub(60,60,60,255)
1209 openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1212 def _renderObject(self, obj, brightness = 0, addSink = True):
1215 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1217 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1219 if self.tempMatrix is not None and obj == self._selectedObj:
1220 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1222 offset = obj.getDrawOffset()
1223 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1225 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1228 for m in obj._meshList:
1230 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1232 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1237 def _drawMachine(self):
1238 glEnable(GL_CULL_FACE)
1241 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1243 machine = profile.getMachineSetting('machine_type')
1244 if machine.startswith('ultimaker'):
1245 if machine not in self._platformMesh:
1246 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1248 self._platformMesh[machine] = meshes[0]
1250 self._platformMesh[machine] = None
1251 if machine == 'ultimaker2':
1252 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1254 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1255 glColor4f(1,1,1,0.5)
1256 self._objectShader.bind()
1257 self._renderObject(self._platformMesh[machine], False, False)
1258 self._objectShader.unbind()
1260 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1261 if machine == 'ultimaker2':
1262 if not hasattr(self._platformMesh[machine], 'texture'):
1263 self._platformMesh[machine].texture = openglHelpers.loadGLTexture('Ultimaker2backplate.png')
1264 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1265 glEnable(GL_TEXTURE_2D)
1269 glTranslate(0,150,-5)
1274 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1277 glVertex3f( w, 0, h)
1279 glVertex3f(-w, 0, h)
1281 glVertex3f(-w, 0, 0)
1283 glVertex3f( w, 0, 0)
1286 glVertex3f(-w, d, h)
1288 glVertex3f( w, d, h)
1290 glVertex3f( w, d, 0)
1292 glVertex3f(-w, d, 0)
1294 glDisable(GL_TEXTURE_2D)
1295 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1301 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1302 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1303 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1304 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1305 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1306 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1311 polys = profile.getMachineSizePolygons()
1312 height = profile.getMachineSettingFloat('machine_height')
1313 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1315 # Draw the sides of the build volume.
1316 for n in xrange(0, len(polys[0])):
1319 glColor4ub(5, 171, 231, 96)
1321 glColor4ub(5, 171, 231, 64)
1323 glColor4ub(5, 171, 231, 96)
1325 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1326 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1327 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1328 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1331 #Draw top of build volume.
1332 glColor4ub(5, 171, 231, 128)
1333 glBegin(GL_TRIANGLE_FAN)
1334 for p in polys[0][::-1]:
1335 glVertex3f(p[0], p[1], height)
1339 if self._platformTexture is None:
1340 self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1341 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1342 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1343 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1344 glColor4f(1,1,1,0.5)
1345 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1346 glEnable(GL_TEXTURE_2D)
1347 glBegin(GL_TRIANGLE_FAN)
1349 glTexCoord2f(p[0]/20, p[1]/20)
1350 glVertex3f(p[0], p[1], 0)
1353 #Draw no-go zones. (clips in case of UM2)
1354 glDisable(GL_TEXTURE_2D)
1355 glColor4ub(127, 127, 127, 200)
1356 for poly in polys[1:]:
1357 glBegin(GL_TRIANGLE_FAN)
1359 glTexCoord2f(p[0]/20, p[1]/20)
1360 glVertex3f(p[0], p[1], 0)
1365 glDisable(GL_CULL_FACE)
1367 def getObjectCenterPos(self):
1368 if self._selectedObj is None:
1369 return [0.0, 0.0, 0.0]
1370 pos = self._selectedObj.getPosition()
1371 size = self._selectedObj.getSize()
1372 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1374 def getObjectBoundaryCircle(self):
1375 if self._selectedObj is None:
1377 return self._selectedObj.getBoundaryCircle()
1379 def getObjectSize(self):
1380 if self._selectedObj is None:
1381 return [0.0, 0.0, 0.0]
1382 return self._selectedObj.getSize()
1384 def getObjectMatrix(self):
1385 if self._selectedObj is None:
1386 return numpy.matrix(numpy.identity(3))
1387 return self._selectedObj.getMatrix()
1389 #TODO: Remove this or put it in a seperate file
1390 class shaderEditor(wx.Dialog):
1391 def __init__(self, parent, callback, v, f):
1392 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1393 self._callback = callback
1394 s = wx.BoxSizer(wx.VERTICAL)
1396 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1397 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1398 s.Add(self._vertex, 1, flag=wx.EXPAND)
1399 s.Add(self._fragment, 1, flag=wx.EXPAND)
1401 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1402 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1404 self.SetPosition(self.GetParent().GetPosition())
1405 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1408 def OnText(self, e):
1409 self._callback(self._vertex.GetValue(), self._fragment.GetValue())