1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
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 gcodeInterpreter
28 from Cura.util import explorer
29 from Cura.util.printerConnection import printerConnectionManager
30 from Cura.gui.util import previewTools
31 from Cura.gui.util import opengl
32 from Cura.gui.util import openglGui
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()
46 self._gcodeFilename = None
47 self._gcodeLoadThread = None
48 self._objectShader = None
49 self._objectLoadShader = None
51 self._selectedObj = None
52 self._objColors = [None,None,None,None]
55 self._mouseState = None
56 self._viewTarget = numpy.array([0,0,0], numpy.float32)
59 self._platformMesh = {}
60 self._platformTexture = None
61 self._isSimpleMode = True
62 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
63 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
66 self._modelMatrix = None
67 self._projMatrix = None
68 self.tempMatrix = None
70 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
71 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
72 self.printButton.setDisabled(True)
75 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
76 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
77 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
79 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
80 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
82 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
83 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
85 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
86 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
87 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
89 self.rotateToolButton.setExpandArrow(True)
90 self.scaleToolButton.setExpandArrow(True)
91 self.mirrorToolButton.setExpandArrow(True)
93 self.scaleForm = openglGui.glFrame(self, (2, -2))
94 openglGui.glGuiLayoutGrid(self.scaleForm)
95 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
96 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
97 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
98 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
99 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
100 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
101 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
102 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
103 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
104 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
105 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
106 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
107 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
108 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
110 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
111 self.layerSelect = openglGui.glSlider(self, 10000, 0, 1, (-1,-2), lambda : self.QueueRefresh())
113 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
114 self.youMagineButton.setDisabled(True)
116 self.notification = openglGui.glNotification(self, (0, 0))
118 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
119 self._sceneUpdateTimer = wx.Timer(self)
120 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
121 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
122 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
126 self.updateToolButtons()
127 self.updateProfileToControls()
129 def loadGCodeFile(self, filename):
130 self.OnDeleteAll(None)
131 if self._gcode is not None:
133 for layerVBOlist in self._gcodeVBOs:
134 for vbo in layerVBOlist:
135 self.glReleaseList.append(vbo)
137 self._gcode = gcodeInterpreter.gcode()
138 self._gcodeFilename = filename
139 self.printButton.setBottomText('')
140 self.viewSelection.setValue(4)
141 self.printButton.setDisabled(False)
142 self.youMagineButton.setDisabled(True)
145 def loadSceneFiles(self, filenames):
146 self.youMagineButton.setDisabled(False)
147 #if self.viewSelection.getValue() == 4:
148 # self.viewSelection.setValue(0)
149 # self.OnViewChange()
150 self.loadScene(filenames)
152 def loadFiles(self, filenames):
153 mainWindow = self.GetParent().GetParent().GetParent()
154 # only one GCODE file can be active
155 # so if single gcode file, process this
156 # otherwise ignore all gcode files
158 if len(filenames) == 1:
159 filename = filenames[0]
160 ext = os.path.splitext(filename)[1].lower()
161 if ext == '.g' or ext == '.gcode':
162 gcodeFilename = filename
163 mainWindow.addToModelMRU(filename)
164 if gcodeFilename is not None:
165 self.loadGCodeFile(gcodeFilename)
167 # process directories and special file types
168 # and keep scene files for later processing
170 ignored_types = dict()
171 # use file list as queue
172 # pop first entry for processing and append new files at end
174 filename = filenames.pop(0)
175 if os.path.isdir(filename):
176 # directory: queue all included files and directories
177 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
179 ext = os.path.splitext(filename)[1].lower()
181 profile.loadProfile(filename)
182 mainWindow.addToProfileMRU(filename)
183 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
184 scene_filenames.append(filename)
185 mainWindow.addToModelMRU(filename)
187 ignored_types[ext] = 1
189 ignored_types = ignored_types.keys()
191 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
192 mainWindow.updateProfileToAllControls()
193 # now process all the scene files
195 self.loadSceneFiles(scene_filenames)
196 self._selectObject(None)
198 newZoom = numpy.max(self._machineSize)
199 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
200 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
202 def showLoadModel(self, button = 1):
204 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)
205 dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
206 if dlg.ShowModal() != wx.ID_OK:
209 filenames = dlg.GetPaths()
211 if len(filenames) < 1:
213 profile.putPreference('lastFile', filenames[0])
214 self.loadFiles(filenames)
216 def showSaveModel(self):
217 if len(self._scene.objects()) < 1:
219 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
220 dlg.SetWildcard(meshLoader.saveWildcardFilter())
221 if dlg.ShowModal() != wx.ID_OK:
224 filename = dlg.GetPath()
226 meshLoader.saveMeshes(filename, self._scene.objects())
228 def OnPrintButton(self, button):
230 connectionGroup = self._printerConnectionManager.getAvailableGroup()
231 if machineCom.machineIsConnected():
232 self.showPrintWindow()
233 elif 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._copyFile,args=(self._gcodeFilename, 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 self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
263 connections = self._printerConnectionManager.getAvailableConnections()
264 menu.connectionMap = {}
265 for connection in connections:
266 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
267 menu.connectionMap[i.GetId()] = connection
268 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
269 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
270 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
274 def _openPrintWindowForConnection(self, connection):
275 print '_openPrintWindowForConnection', connection.getName()
276 if connection.window is None or not connection.window:
277 connection.window = printWindow2.printWindow(connection)
278 connection.window.Show()
279 connection.window.Raise()
280 if not connection.loadFile(self._gcodeFilename):
281 if connection.isPrinting():
282 self.notification.message("Cannot start print, because other print still running.")
284 self.notification.message("Failed to start print...")
286 def showPrintWindow(self):
287 if self._gcodeFilename is None:
289 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
290 wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
292 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
293 if self._gcodeFilename == self._slicer.getGCodeFilename():
294 self._slicer.submitSliceInfoOnline()
296 def showSaveGCode(self):
297 if len(self._scene._objectList) < 1:
299 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
300 filename = self._scene._objectList[0].getName() + '.gcode'
301 dlg.SetFilename(filename)
302 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
303 if dlg.ShowModal() != wx.ID_OK:
306 filename = dlg.GetPath()
309 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
311 def _copyFile(self, fileA, fileB, allowEject = False):
313 size = float(os.stat(fileA).st_size)
314 with open(fileA, 'rb') as fsrc:
315 with open(fileB, 'wb') as fdst:
317 buf = fsrc.read(16*1024)
321 self.printButton.setProgressBar(float(fsrc.tell()) / size)
326 self.notification.message("Failed to save")
329 self.notification.message("Saved as %s" % (fileB), lambda : self._doEjectSD(allowEject), 31, 'Eject')
330 elif explorer.hasExplorer():
331 self.notification.message("Saved as %s" % (fileB), lambda : explorer.openExplorer(fileB), 4, 'Open folder')
333 self.notification.message("Saved as %s" % (fileB))
334 self.printButton.setProgressBar(None)
335 if fileA == self._slicer.getGCodeFilename():
336 self._slicer.submitSliceInfoOnline()
338 def _doEjectSD(self, drive):
339 if removableStorage.ejectDrive(drive):
340 self.notification.message('You can now eject the card.')
342 self.notification.message('Safe remove failed...')
344 def _showSliceLog(self):
345 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
349 def OnToolSelect(self, button):
350 if self.rotateToolButton.getSelected():
351 self.tool = previewTools.toolRotate(self)
352 elif self.scaleToolButton.getSelected():
353 self.tool = previewTools.toolScale(self)
354 elif self.mirrorToolButton.getSelected():
355 self.tool = previewTools.toolNone(self)
357 self.tool = previewTools.toolNone(self)
358 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
359 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
360 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
361 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
362 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
363 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
364 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
365 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
367 def updateToolButtons(self):
368 if self._selectedObj is None:
372 self.rotateToolButton.setHidden(hidden)
373 self.scaleToolButton.setHidden(hidden)
374 self.mirrorToolButton.setHidden(hidden)
376 self.rotateToolButton.setSelected(False)
377 self.scaleToolButton.setSelected(False)
378 self.mirrorToolButton.setSelected(False)
381 def OnViewChange(self):
382 if self.viewSelection.getValue() == 4:
383 self.viewMode = 'gcode'
384 if self._gcode is not None and self._gcode.layerList is not None:
385 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
386 self._selectObject(None)
387 elif self.viewSelection.getValue() == 1:
388 self.viewMode = 'overhang'
389 elif self.viewSelection.getValue() == 2:
390 self.viewMode = 'transparent'
391 elif self.viewSelection.getValue() == 3:
392 self.viewMode = 'xray'
394 self.viewMode = 'normal'
395 self.layerSelect.setHidden(self.viewMode != 'gcode')
398 def OnRotateReset(self, button):
399 if self._selectedObj is None:
401 self._selectedObj.resetRotation()
402 self._scene.pushFree(self._selectedObj)
403 self._selectObject(self._selectedObj)
406 def OnLayFlat(self, button):
407 if self._selectedObj is None:
409 self._selectedObj.layFlat()
410 self._scene.pushFree(self._selectedObj)
411 self._selectObject(self._selectedObj)
414 def OnScaleReset(self, button):
415 if self._selectedObj is None:
417 self._selectedObj.resetScale()
418 self._selectObject(self._selectedObj)
419 self.updateProfileToControls()
422 def OnScaleMax(self, button):
423 if self._selectedObj is None:
425 machine = profile.getMachineSetting('machine_type')
426 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
427 self._scene.pushFree(self._selectedObj)
429 if machine == "ultimaker2":
430 #This is bad and Jaime should feel bad!
431 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
432 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
433 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
434 self._scene.pushFree(self._selectedObj)
436 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
437 self._scene.pushFree(self._selectedObj)
438 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
439 self._scene.pushFree(self._selectedObj)
440 self._selectObject(self._selectedObj)
441 self.updateProfileToControls()
444 def OnMirror(self, axis):
445 if self._selectedObj is None:
447 self._selectedObj.mirror(axis)
450 def OnScaleEntry(self, value, axis):
451 if self._selectedObj is None:
457 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
458 self.updateProfileToControls()
459 self._scene.pushFree(self._selectedObj)
460 self._selectObject(self._selectedObj)
463 def OnScaleEntryMM(self, value, axis):
464 if self._selectedObj is None:
470 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
471 self.updateProfileToControls()
472 self._scene.pushFree(self._selectedObj)
473 self._selectObject(self._selectedObj)
476 def OnDeleteAll(self, e):
477 while len(self._scene.objects()) > 0:
478 self._deleteObject(self._scene.objects()[0])
479 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
481 def OnMultiply(self, e):
482 if self._focusObj is None:
485 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
486 if dlg.ShowModal() != wx.ID_OK:
495 self._scene.add(newObj)
496 self._scene.centerAll()
497 if not self._scene.checkPlatform(newObj):
502 self.notification.message("Could not create more then %d items" % (n - 1))
503 self._scene.remove(newObj)
504 self._scene.centerAll()
507 def OnSplitObject(self, e):
508 if self._focusObj is None:
510 self._scene.remove(self._focusObj)
511 for obj in self._focusObj.split(self._splitCallback):
512 if numpy.max(obj.getSize()) > 2.0:
514 self._scene.centerAll()
515 self._selectObject(None)
518 def OnCenter(self, e):
519 if self._focusObj is None:
521 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
522 self._scene.pushFree(self._selectedObj)
523 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
524 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
527 def _splitCallback(self, progress):
530 def OnMergeObjects(self, e):
531 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
532 if len(self._scene.objects()) == 2:
533 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
536 self._scene.merge(self._selectedObj, self._focusObj)
539 def sceneUpdated(self):
540 self._sceneUpdateTimer.Start(500, True)
541 self._slicer.abortSlicer()
542 self._scene.updateSizeOffsets()
545 def _onRunSlicer(self, e):
546 if self._isSimpleMode:
547 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
548 self._slicer.runSlicer(self._scene)
549 if self._isSimpleMode:
550 profile.resetTempOverride()
552 def _updateSliceProgress(self, progressValue, ready):
554 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
556 self.printButton.setDisabled(not ready)
557 if progressValue >= 0.0:
558 self.printButton.setProgressBar(progressValue)
560 self.printButton.setProgressBar(None)
561 if self._gcode is not None:
563 for layerVBOlist in self._gcodeVBOs:
564 for vbo in layerVBOlist:
565 self.glReleaseList.append(vbo)
568 self.printButton.setProgressBar(None)
569 text = '%s' % (self._slicer.getPrintTime())
570 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
571 amount = self._slicer.getFilamentAmount(e)
574 text += '\n%s' % (amount)
575 cost = self._slicer.getFilamentCost(e)
577 text += '\n%s' % (cost)
578 self.printButton.setBottomText(text)
579 self._gcode = gcodeInterpreter.gcode()
580 self._gcodeFilename = self._slicer.getGCodeFilename()
582 self.printButton.setBottomText('')
585 def _loadGCode(self):
586 self._gcode.progressCallback = self._gcodeLoadCallback
587 self._gcode.load(self._gcodeFilename)
589 def _gcodeLoadCallback(self, progress):
590 if not self or self._gcode is None:
592 if len(self._gcode.layerList) % 15 == 0:
594 if self._gcode is None:
596 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
597 if self.viewMode == 'gcode':
601 def loadScene(self, fileList):
602 for filename in fileList:
604 ext = os.path.splitext(filename)[1].lower()
605 if ext in imageToMesh.supportedExtensions():
606 imageToMesh.convertImageDialog(self, filename).Show()
609 objList = meshLoader.loadMeshes(filename)
611 traceback.print_exc()
614 if self._objectLoadShader is not None:
615 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
619 if not self._scene.checkPlatform(obj):
620 self._scene.centerAll()
621 self._selectObject(obj)
622 if obj.getScale()[0] < 1.0:
623 self.notification.message("Warning: Object scaled down.")
626 def _deleteObject(self, obj):
627 if obj == self._selectedObj:
628 self._selectObject(None)
629 if obj == self._focusObj:
630 self._focusObj = None
631 self._scene.remove(obj)
632 for m in obj._meshList:
633 if m.vbo is not None and m.vbo.decRef():
634 self.glReleaseList.append(m.vbo)
639 def _selectObject(self, obj, zoom = True):
640 if obj != self._selectedObj:
641 self._selectedObj = obj
642 self.updateModelSettingsToControls()
643 self.updateToolButtons()
644 if zoom and obj is not None:
645 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
646 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
647 newZoom = obj.getBoundaryCircle() * 6
648 if newZoom > numpy.max(self._machineSize) * 3:
649 newZoom = numpy.max(self._machineSize) * 3
650 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
652 def updateProfileToControls(self):
653 oldSimpleMode = self._isSimpleMode
654 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
655 if self._isSimpleMode != oldSimpleMode:
656 self._scene.arrangeAll()
658 self._scene.updateSizeOffsets(True)
659 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
660 self._objColors[0] = profile.getPreferenceColour('model_colour')
661 self._objColors[1] = profile.getPreferenceColour('model_colour2')
662 self._objColors[2] = profile.getPreferenceColour('model_colour3')
663 self._objColors[3] = profile.getPreferenceColour('model_colour4')
664 self._scene.updateMachineDimensions()
665 self.updateModelSettingsToControls()
667 def updateModelSettingsToControls(self):
668 if self._selectedObj is not None:
669 scale = self._selectedObj.getScale()
670 size = self._selectedObj.getSize()
671 self.scaleXctrl.setValue(round(scale[0], 2))
672 self.scaleYctrl.setValue(round(scale[1], 2))
673 self.scaleZctrl.setValue(round(scale[2], 2))
674 self.scaleXmmctrl.setValue(round(size[0], 2))
675 self.scaleYmmctrl.setValue(round(size[1], 2))
676 self.scaleZmmctrl.setValue(round(size[2], 2))
678 def OnKeyChar(self, keyCode):
679 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
680 if self._selectedObj is not None:
681 self._deleteObject(self._selectedObj)
683 if self.viewMode == 'gcode':
684 if keyCode == wx.WXK_UP:
685 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
687 elif keyCode == wx.WXK_DOWN:
688 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
690 elif keyCode == wx.WXK_PAGEUP:
691 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
693 elif keyCode == wx.WXK_PAGEDOWN:
694 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
697 if keyCode == wx.WXK_UP:
698 if wx.GetKeyState(wx.WXK_SHIFT):
705 elif keyCode == wx.WXK_DOWN:
706 if wx.GetKeyState(wx.WXK_SHIFT):
708 if self._zoom > numpy.max(self._machineSize) * 3:
709 self._zoom = numpy.max(self._machineSize) * 3
713 elif keyCode == wx.WXK_LEFT:
716 elif keyCode == wx.WXK_RIGHT:
719 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
724 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
726 if self._zoom > numpy.max(self._machineSize) * 3:
727 self._zoom = numpy.max(self._machineSize) * 3
729 elif keyCode == wx.WXK_HOME:
733 elif keyCode == wx.WXK_PAGEUP:
737 elif keyCode == wx.WXK_PAGEDOWN:
741 elif keyCode == wx.WXK_END:
746 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
747 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
748 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
749 from collections import defaultdict
750 from gc import get_objects
751 self._beforeLeakTest = defaultdict(int)
752 for i in get_objects():
753 self._beforeLeakTest[type(i)] += 1
754 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
755 from collections import defaultdict
756 from gc import get_objects
757 self._afterLeakTest = defaultdict(int)
758 for i in get_objects():
759 self._afterLeakTest[type(i)] += 1
760 for k in self._afterLeakTest:
761 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
762 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
764 def ShaderUpdate(self, v, f):
765 s = opengl.GLShader(v, f)
767 self._objectLoadShader.release()
768 self._objectLoadShader = s
769 for obj in self._scene.objects():
770 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
773 def OnMouseDown(self,e):
774 self._mouseX = e.GetX()
775 self._mouseY = e.GetY()
776 self._mouseClick3DPos = self._mouse3Dpos
777 self._mouseClickFocus = self._focusObj
779 self._mouseState = 'doubleClick'
781 self._mouseState = 'dragOrClick'
782 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
783 p0 -= self.getObjectCenterPos() - self._viewTarget
784 p1 -= self.getObjectCenterPos() - self._viewTarget
785 if self.tool.OnDragStart(p0, p1):
786 self._mouseState = 'tool'
787 if self._mouseState == 'dragOrClick':
788 if e.GetButton() == 1:
789 if self._focusObj is not None:
790 self._selectObject(self._focusObj, False)
793 def OnMouseUp(self, e):
794 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
796 if self._mouseState == 'dragOrClick':
797 if e.GetButton() == 1:
798 self._selectObject(self._focusObj)
799 if e.GetButton() == 3:
801 if self._focusObj is not None:
802 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
803 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
804 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
805 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
806 if ((self._selectedObj != self._focusObj and self._focusObj is not None and self._selectedObj is not None) or len(self._scene.objects()) == 2) and int(profile.getMachineSetting('extruder_amount')) > 1:
807 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
808 if len(self._scene.objects()) > 0:
809 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
810 if menu.MenuItemCount > 0:
813 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
814 self._scene.pushFree(self._selectedObj)
816 elif self._mouseState == 'tool':
817 if self.tempMatrix is not None and self._selectedObj is not None:
818 self._selectedObj.applyMatrix(self.tempMatrix)
819 self._scene.pushFree(self._selectedObj)
820 self._selectObject(self._selectedObj)
821 self.tempMatrix = None
822 self.tool.OnDragEnd()
824 self._mouseState = None
826 def OnMouseMotion(self,e):
827 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
828 p0 -= self.getObjectCenterPos() - self._viewTarget
829 p1 -= self.getObjectCenterPos() - self._viewTarget
831 if e.Dragging() and self._mouseState is not None:
832 if self._mouseState == 'tool':
833 self.tool.OnDrag(p0, p1)
834 elif not e.LeftIsDown() and e.RightIsDown():
835 self._mouseState = 'drag'
836 if wx.GetKeyState(wx.WXK_SHIFT):
837 a = math.cos(math.radians(self._yaw)) / 3.0
838 b = math.sin(math.radians(self._yaw)) / 3.0
839 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
840 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
841 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
842 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
844 self._yaw += e.GetX() - self._mouseX
845 self._pitch -= e.GetY() - self._mouseY
846 if self._pitch > 170:
850 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
851 self._mouseState = 'drag'
852 self._zoom += e.GetY() - self._mouseY
855 if self._zoom > numpy.max(self._machineSize) * 3:
856 self._zoom = numpy.max(self._machineSize) * 3
857 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
858 self._mouseState = 'dragObject'
859 z = max(0, self._mouseClick3DPos[2])
860 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
861 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
866 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
867 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
868 diff = cursorZ1 - cursorZ0
869 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
870 if not e.Dragging() or self._mouseState != 'tool':
871 self.tool.OnMouseMove(p0, p1)
873 self._mouseX = e.GetX()
874 self._mouseY = e.GetY()
876 def OnMouseWheel(self, e):
877 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
878 delta = max(min(delta,4),-4)
879 self._zoom *= 1.0 - delta / 10.0
882 if self._zoom > numpy.max(self._machineSize) * 3:
883 self._zoom = numpy.max(self._machineSize) * 3
886 def OnMouseLeave(self, e):
890 def getMouseRay(self, x, y):
891 if self._viewport is None:
892 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
893 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
894 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
895 p0 -= self._viewTarget
896 p1 -= self._viewTarget
899 def _init3DView(self):
900 # set viewing projection
901 size = self.GetSize()
902 glViewport(0, 0, size.GetWidth(), size.GetHeight())
905 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
907 glDisable(GL_RESCALE_NORMAL)
908 glDisable(GL_LIGHTING)
910 glEnable(GL_DEPTH_TEST)
911 glDisable(GL_CULL_FACE)
913 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
915 glClearColor(0.8, 0.8, 0.8, 1.0)
919 glMatrixMode(GL_PROJECTION)
921 aspect = float(size.GetWidth()) / float(size.GetHeight())
922 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
924 glMatrixMode(GL_MODELVIEW)
926 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
929 connectionGroup = self._printerConnectionManager.getAvailableGroup()
930 if machineCom.machineIsConnected():
931 self.printButton._imageID = 6
932 self.printButton._tooltip = _("Print")
933 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
934 self.printButton._imageID = 2
935 self.printButton._tooltip = _("Toolpath to SD")
936 elif connectionGroup is not None:
937 self.printButton._imageID = connectionGroup.getIconID()
938 self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
940 self.printButton._imageID = 3
941 self.printButton._tooltip = _("Save toolpath")
943 if self._animView is not None:
944 self._viewTarget = self._animView.getPosition()
945 if self._animView.isDone():
946 self._animView = None
947 if self._animZoom is not None:
948 self._zoom = self._animZoom.getPosition()
949 if self._animZoom.isDone():
950 self._animZoom = None
951 if self.viewMode == 'gcode' and self._gcode is not None:
953 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
956 if self._objectShader is None:
957 if opengl.hasShaderSupport():
958 self._objectShader = opengl.GLShader("""
959 varying float light_amount;
963 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
964 gl_FrontColor = gl_Color;
966 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
970 varying float light_amount;
974 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
977 self._objectOverhangShader = opengl.GLShader("""
978 uniform float cosAngle;
979 uniform mat3 rotMatrix;
980 varying float light_amount;
984 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
985 gl_FrontColor = gl_Color;
987 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
989 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
991 light_amount = -10.0;
995 varying float light_amount;
999 if (light_amount == -10.0)
1001 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1003 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1007 self._objectLoadShader = opengl.GLShader("""
1008 uniform float intensity;
1009 uniform float scale;
1010 varying float light_amount;
1014 vec4 tmp = gl_Vertex;
1015 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1016 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1017 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1018 gl_FrontColor = gl_Color;
1020 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1021 light_amount += 0.2;
1024 uniform float intensity;
1025 varying float light_amount;
1029 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1032 if self._objectShader is None or not self._objectShader.isValid():
1033 self._objectShader = opengl.GLFakeShader()
1034 self._objectOverhangShader = opengl.GLFakeShader()
1035 self._objectLoadShader = None
1037 glTranslate(0,0,-self._zoom)
1038 glRotate(-self._pitch, 1,0,0)
1039 glRotate(self._yaw, 0,0,1)
1040 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1042 self._viewport = glGetIntegerv(GL_VIEWPORT)
1043 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1044 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1046 glClearColor(1,1,1,1)
1047 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1049 if self.viewMode != 'gcode':
1050 for n in xrange(0, len(self._scene.objects())):
1051 obj = self._scene.objects()[n]
1052 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1053 self._renderObject(obj)
1055 if self._mouseX > -1:
1057 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1058 if n < len(self._scene.objects()):
1059 self._focusObj = self._scene.objects()[n]
1061 self._focusObj = None
1062 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1063 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1064 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1065 self._mouse3Dpos -= self._viewTarget
1068 glTranslate(0,0,-self._zoom)
1069 glRotate(-self._pitch, 1,0,0)
1070 glRotate(self._yaw, 0,0,1)
1071 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1073 if self.viewMode == 'gcode':
1074 if self._gcode is not None and self._gcode.layerList is None:
1075 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
1076 self._gcodeLoadThread.daemon = True
1077 self._gcodeLoadThread.start()
1078 if self._gcode is not None and self._gcode.layerList is not None:
1080 if profile.getMachineSetting('machine_center_is_zero') != 'True':
1081 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
1083 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
1084 for n in xrange(0, drawUpTill):
1085 c = 1.0 - float(drawUpTill - n) / 15
1087 if len(self._gcodeVBOs) < n + 1:
1088 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
1089 if time.time() - t > 0.5:
1092 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
1093 if n == drawUpTill - 1:
1094 if len(self._gcodeVBOs[n]) < 9:
1095 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
1097 self._gcodeVBOs[n][8].render(GL_QUADS)
1098 glColor3f(c/2, 0, c)
1099 self._gcodeVBOs[n][9].render(GL_QUADS)
1100 glColor3f(0, c, c/2)
1101 self._gcodeVBOs[n][10].render(GL_QUADS)
1103 self._gcodeVBOs[n][11].render(GL_QUADS)
1106 self._gcodeVBOs[n][12].render(GL_QUADS)
1107 glColor3f(c/2, c/2, 0.0)
1108 self._gcodeVBOs[n][13].render(GL_QUADS)
1110 self._gcodeVBOs[n][14].render(GL_QUADS)
1111 self._gcodeVBOs[n][15].render(GL_QUADS)
1113 self._gcodeVBOs[n][16].render(GL_LINES)
1116 self._gcodeVBOs[n][0].render(GL_LINES)
1117 glColor3f(c/2, 0, c)
1118 self._gcodeVBOs[n][1].render(GL_LINES)
1119 glColor3f(0, c, c/2)
1120 self._gcodeVBOs[n][2].render(GL_LINES)
1122 self._gcodeVBOs[n][3].render(GL_LINES)
1125 self._gcodeVBOs[n][4].render(GL_LINES)
1126 glColor3f(c/2, c/2, 0.0)
1127 self._gcodeVBOs[n][5].render(GL_LINES)
1129 self._gcodeVBOs[n][6].render(GL_LINES)
1130 self._gcodeVBOs[n][7].render(GL_LINES)
1133 glStencilFunc(GL_ALWAYS, 1, 1)
1134 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1136 if self.viewMode == 'overhang':
1137 self._objectOverhangShader.bind()
1138 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1140 self._objectShader.bind()
1141 for obj in self._scene.objects():
1142 if obj._loadAnim is not None:
1143 if obj._loadAnim.isDone():
1144 obj._loadAnim = None
1148 if self._focusObj == obj:
1150 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1153 if self._selectedObj == obj or self._selectedObj is None:
1154 #If we want transparent, then first render a solid black model to remove the printer size lines.
1155 if self.viewMode == 'transparent':
1156 glColor4f(0, 0, 0, 0)
1157 self._renderObject(obj)
1159 glBlendFunc(GL_ONE, GL_ONE)
1160 glDisable(GL_DEPTH_TEST)
1162 if self.viewMode == 'xray':
1163 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1164 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1165 glEnable(GL_STENCIL_TEST)
1167 if self.viewMode == 'overhang':
1168 if self._selectedObj == obj and self.tempMatrix is not None:
1169 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1171 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1173 if not self._scene.checkPlatform(obj):
1174 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1175 self._renderObject(obj)
1177 self._renderObject(obj, brightness)
1178 glDisable(GL_STENCIL_TEST)
1180 glEnable(GL_DEPTH_TEST)
1181 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1183 if self.viewMode == 'xray':
1186 glEnable(GL_STENCIL_TEST)
1187 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1188 glDisable(GL_DEPTH_TEST)
1189 for i in xrange(2, 15, 2):
1190 glStencilFunc(GL_EQUAL, i, 0xFF)
1191 glColor(float(i)/10, float(i)/10, float(i)/5)
1193 glVertex3f(-1000,-1000,-10)
1194 glVertex3f( 1000,-1000,-10)
1195 glVertex3f( 1000, 1000,-10)
1196 glVertex3f(-1000, 1000,-10)
1198 for i in xrange(1, 15, 2):
1199 glStencilFunc(GL_EQUAL, i, 0xFF)
1200 glColor(float(i)/10, 0, 0)
1202 glVertex3f(-1000,-1000,-10)
1203 glVertex3f( 1000,-1000,-10)
1204 glVertex3f( 1000, 1000,-10)
1205 glVertex3f(-1000, 1000,-10)
1208 glDisable(GL_STENCIL_TEST)
1209 glEnable(GL_DEPTH_TEST)
1211 self._objectShader.unbind()
1213 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1215 if self._objectLoadShader is not None:
1216 self._objectLoadShader.bind()
1217 glColor4f(0.2, 0.6, 1.0, 1.0)
1218 for obj in self._scene.objects():
1219 if obj._loadAnim is None:
1221 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1222 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1223 self._renderObject(obj)
1224 self._objectLoadShader.unbind()
1229 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1231 z = self._usbPrintMonitor.getZ()
1232 size = self._machineSize
1233 glColor4ub(255,255,0,128)
1235 glVertex3f(-size[0]/2,-size[1]/2, z)
1236 glVertex3f( size[0]/2,-size[1]/2, z)
1237 glVertex3f( size[0]/2, size[1]/2, z)
1238 glVertex3f(-size[0]/2, size[1]/2, z)
1241 if self.viewMode == 'gcode':
1242 if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1243 glDisable(GL_DEPTH_TEST)
1246 glTranslate(0,-4,-10)
1247 glColor4ub(60,60,60,255)
1248 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1251 #Draw the object box-shadow, so you can see where it will collide with other objects.
1252 if self._selectedObj is not None:
1254 glEnable(GL_CULL_FACE)
1255 glColor4f(0,0,0,0.16)
1257 for obj in self._scene.objects():
1259 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1260 glBegin(GL_TRIANGLE_FAN)
1261 for p in obj._boundaryHull[::-1]:
1262 glVertex3f(p[0], p[1], 0)
1266 glColor4f(0,0,0,0.06)
1267 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1268 glBegin(GL_TRIANGLE_FAN)
1269 for p in self._selectedObj._printAreaHull[::-1]:
1270 glVertex3f(p[0], p[1], 0)
1272 glBegin(GL_TRIANGLE_FAN)
1273 for p in self._selectedObj._headAreaMinHull[::-1]:
1274 glVertex3f(p[0], p[1], 0)
1277 glDisable(GL_CULL_FACE)
1280 #Draw the outline of the selected object, on top of everything else except the GUI.
1281 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1282 glDisable(GL_DEPTH_TEST)
1283 glEnable(GL_CULL_FACE)
1284 glEnable(GL_STENCIL_TEST)
1286 glStencilFunc(GL_EQUAL, 0, 255)
1288 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1290 glColor4f(1,1,1,0.5)
1291 self._renderObject(self._selectedObj)
1292 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1294 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1295 glDisable(GL_STENCIL_TEST)
1296 glDisable(GL_CULL_FACE)
1297 glEnable(GL_DEPTH_TEST)
1299 if self._selectedObj is not None:
1301 pos = self.getObjectCenterPos()
1302 glTranslate(pos[0], pos[1], pos[2])
1305 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1306 glDisable(GL_DEPTH_TEST)
1309 glTranslate(0,-4,-10)
1310 glColor4ub(60,60,60,255)
1311 opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1314 def _renderObject(self, obj, brightness = False, addSink = True):
1317 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1319 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1321 if self.tempMatrix is not None and obj == self._selectedObj:
1322 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1323 glMultMatrixf(tempMatrix)
1325 offset = obj.getDrawOffset()
1326 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1328 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1329 glMultMatrixf(tempMatrix)
1332 for m in obj._meshList:
1334 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1336 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1341 def _drawMachine(self):
1342 glEnable(GL_CULL_FACE)
1345 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1347 machine = profile.getMachineSetting('machine_type')
1348 if machine.startswith('ultimaker'):
1349 if machine not in self._platformMesh:
1350 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1352 self._platformMesh[machine] = meshes[0]
1354 self._platformMesh[machine] = None
1355 if machine == 'ultimaker2':
1356 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1358 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1359 glColor4f(1,1,1,0.5)
1360 self._objectShader.bind()
1361 self._renderObject(self._platformMesh[machine], False, False)
1362 self._objectShader.unbind()
1364 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1365 if machine == 'ultimaker2':
1366 if not hasattr(self._platformMesh[machine], 'texture'):
1367 self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1368 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1369 glEnable(GL_TEXTURE_2D)
1373 glTranslate(0,150,-5)
1378 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1381 glVertex3f( w, 0, h)
1383 glVertex3f(-w, 0, h)
1385 glVertex3f(-w, 0, 0)
1387 glVertex3f( w, 0, 0)
1390 glVertex3f(-w, d, h)
1392 glVertex3f( w, d, h)
1394 glVertex3f( w, d, 0)
1396 glVertex3f(-w, d, 0)
1398 glDisable(GL_TEXTURE_2D)
1399 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1405 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1406 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1407 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1408 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1409 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1410 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1415 polys = profile.getMachineSizePolygons()
1416 height = profile.getMachineSettingFloat('machine_height')
1418 for n in xrange(0, len(polys[0])):
1420 glColor4ub(5, 171, 231, 96)
1422 glColor4ub(5, 171, 231, 64)
1423 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1424 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1425 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1426 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1428 glColor4ub(5, 171, 231, 128)
1429 glBegin(GL_TRIANGLE_FAN)
1430 for p in polys[0][::-1]:
1431 glVertex3f(p[0], p[1], height)
1435 if self._platformTexture is None:
1436 self._platformTexture = opengl.loadGLTexture('checkerboard.png')
1437 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1438 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1439 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1440 glColor4f(1,1,1,0.5)
1441 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1442 glEnable(GL_TEXTURE_2D)
1443 glBegin(GL_TRIANGLE_FAN)
1445 glTexCoord2f(p[0]/20, p[1]/20)
1446 glVertex3f(p[0], p[1], 0)
1448 glDisable(GL_TEXTURE_2D)
1449 glColor4ub(127, 127, 127, 200)
1450 for poly in polys[1:]:
1451 glBegin(GL_TRIANGLE_FAN)
1453 glTexCoord2f(p[0]/20, p[1]/20)
1454 glVertex3f(p[0], p[1], 0)
1459 glDisable(GL_CULL_FACE)
1461 def _generateGCodeVBOs(self, layer):
1463 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1464 if ':' in extrudeType:
1465 extruder = int(extrudeType[extrudeType.find(':')+1:])
1466 extrudeType = extrudeType[0:extrudeType.find(':')]
1469 pointList = numpy.zeros((0,3), numpy.float32)
1471 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1473 a = numpy.concatenate((a[:-1], a[1:]), 1)
1474 a = a.reshape((len(a) * 2, 3))
1475 pointList = numpy.concatenate((pointList, a))
1476 ret.append(opengl.GLVBO(pointList))
1479 def _generateGCodeVBOs2(self, layer):
1480 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1481 filamentArea = math.pi * filamentRadius * filamentRadius
1482 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1485 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1486 if ':' in extrudeType:
1487 extruder = int(extrudeType[extrudeType.find(':')+1:])
1488 extrudeType = extrudeType[0:extrudeType.find(':')]
1491 pointList = numpy.zeros((0,3), numpy.float32)
1493 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1495 if extrudeType == 'FILL':
1498 normal = a[1:] - a[:-1]
1499 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1500 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1503 ePerDist = path['extrusion'][1:] / lens
1505 lineWidth = ePerDist / path['layerThickness'] / 2.0
1507 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1509 normal[:,0] *= lineWidth
1510 normal[:,1] *= lineWidth
1512 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1513 b = numpy.concatenate((b, a[1:] + normal), 1)
1514 b = numpy.concatenate((b, a[1:] - normal), 1)
1515 b = numpy.concatenate((b, a[:-1] - normal), 1)
1516 b = numpy.concatenate((b, a[:-1] + normal), 1)
1517 b = b.reshape((len(b) * 4, 3))
1520 normal2 = normal[:-1] + normal[1:]
1521 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1522 normal2[:,0] /= lens2
1523 normal2[:,1] /= lens2
1524 normal2[:,0] *= lineWidth[:-1]
1525 normal2[:,1] *= lineWidth[:-1]
1527 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1528 c = numpy.concatenate((c, a[1:-1]), 1)
1529 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1530 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1531 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1533 c = numpy.concatenate((c, a[1:-1]), 1)
1534 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1535 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1536 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1538 c = c.reshape((len(c) * 8, 3))
1540 pointList = numpy.concatenate((pointList, b, c))
1542 pointList = numpy.concatenate((pointList, b))
1543 ret.append(opengl.GLVBO(pointList))
1545 pointList = numpy.zeros((0,3), numpy.float32)
1547 if path['type'] == 'move':
1548 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1549 a = numpy.concatenate((a[:-1], a[1:]), 1)
1550 a = a.reshape((len(a) * 2, 3))
1551 pointList = numpy.concatenate((pointList, a))
1552 if path['type'] == 'retract':
1553 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1554 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1555 a = a.reshape((len(a) * 2, 3))
1556 pointList = numpy.concatenate((pointList, a))
1557 ret.append(opengl.GLVBO(pointList))
1561 def getObjectCenterPos(self):
1562 if self._selectedObj is None:
1563 return [0.0, 0.0, 0.0]
1564 pos = self._selectedObj.getPosition()
1565 size = self._selectedObj.getSize()
1566 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1568 def getObjectBoundaryCircle(self):
1569 if self._selectedObj is None:
1571 return self._selectedObj.getBoundaryCircle()
1573 def getObjectSize(self):
1574 if self._selectedObj is None:
1575 return [0.0, 0.0, 0.0]
1576 return self._selectedObj.getSize()
1578 def getObjectMatrix(self):
1579 if self._selectedObj is None:
1580 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1581 return self._selectedObj.getMatrix()
1583 class shaderEditor(wx.Dialog):
1584 def __init__(self, parent, callback, v, f):
1585 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1586 self._callback = callback
1587 s = wx.BoxSizer(wx.VERTICAL)
1589 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1590 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1591 s.Add(self._vertex, 1, flag=wx.EXPAND)
1592 s.Add(self._fragment, 1, flag=wx.EXPAND)
1594 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1595 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1597 self.SetPosition(self.GetParent().GetPosition())
1598 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1601 def OnText(self, e):
1602 self._callback(self._vertex.GetValue(), self._fragment.GetValue())