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 connectionEntry = self._printerConnectionManager.getAvailableConnection()
231 if machineCom.machineIsConnected():
232 self.showPrintWindow()
233 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 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 connectionEntry is not None:
247 connection = connectionEntry.connection
248 if connectionEntry.window is None or not connectionEntry.window:
249 connectionEntry.window = printWindow2.printWindow(connection)
250 connectionEntry.window.Show()
251 connectionEntry.window.Raise()
252 if not connection.loadFile(self._gcodeFilename):
253 if connection.isPrinting():
254 self.notification.message("Cannot start print, because other print still running.")
256 self.notification.message("Failed to start print...")
261 self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
262 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
263 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
267 def showPrintWindow(self):
268 if self._gcodeFilename is None:
270 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
271 wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
273 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
274 if self._gcodeFilename == self._slicer.getGCodeFilename():
275 self._slicer.submitSliceInfoOnline()
277 def showSaveGCode(self):
278 if len(self._scene._objectList) < 1:
280 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
281 filename = self._scene._objectList[0].getName() + '.gcode'
282 dlg.SetFilename(filename)
283 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
284 if dlg.ShowModal() != wx.ID_OK:
287 filename = dlg.GetPath()
290 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
292 def _copyFile(self, fileA, fileB, allowEject = False):
294 size = float(os.stat(fileA).st_size)
295 with open(fileA, 'rb') as fsrc:
296 with open(fileB, 'wb') as fdst:
298 buf = fsrc.read(16*1024)
302 self.printButton.setProgressBar(float(fsrc.tell()) / size)
307 self.notification.message("Failed to save")
310 self.notification.message("Saved as %s" % (fileB), lambda : self._doEjectSD(allowEject), 31, 'Eject')
311 elif explorer.hasExplorer():
312 self.notification.message("Saved as %s" % (fileB), lambda : explorer.openExplorer(fileB), 4, 'Open folder')
314 self.notification.message("Saved as %s" % (fileB))
315 self.printButton.setProgressBar(None)
316 if fileA == self._slicer.getGCodeFilename():
317 self._slicer.submitSliceInfoOnline()
319 def _doEjectSD(self, drive):
320 if removableStorage.ejectDrive(drive):
321 self.notification.message('You can now eject the card.')
323 self.notification.message('Safe remove failed...')
325 def _showSliceLog(self):
326 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
330 def OnToolSelect(self, button):
331 if self.rotateToolButton.getSelected():
332 self.tool = previewTools.toolRotate(self)
333 elif self.scaleToolButton.getSelected():
334 self.tool = previewTools.toolScale(self)
335 elif self.mirrorToolButton.getSelected():
336 self.tool = previewTools.toolNone(self)
338 self.tool = previewTools.toolNone(self)
339 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
340 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
341 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
342 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
343 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
344 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
345 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
346 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
348 def updateToolButtons(self):
349 if self._selectedObj is None:
353 self.rotateToolButton.setHidden(hidden)
354 self.scaleToolButton.setHidden(hidden)
355 self.mirrorToolButton.setHidden(hidden)
357 self.rotateToolButton.setSelected(False)
358 self.scaleToolButton.setSelected(False)
359 self.mirrorToolButton.setSelected(False)
362 def OnViewChange(self):
363 if self.viewSelection.getValue() == 4:
364 self.viewMode = 'gcode'
365 if self._gcode is not None and self._gcode.layerList is not None:
366 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
367 self._selectObject(None)
368 elif self.viewSelection.getValue() == 1:
369 self.viewMode = 'overhang'
370 elif self.viewSelection.getValue() == 2:
371 self.viewMode = 'transparent'
372 elif self.viewSelection.getValue() == 3:
373 self.viewMode = 'xray'
375 self.viewMode = 'normal'
376 self.layerSelect.setHidden(self.viewMode != 'gcode')
379 def OnRotateReset(self, button):
380 if self._selectedObj is None:
382 self._selectedObj.resetRotation()
383 self._scene.pushFree()
384 self._selectObject(self._selectedObj)
387 def OnLayFlat(self, button):
388 if self._selectedObj is None:
390 self._selectedObj.layFlat()
391 self._scene.pushFree()
392 self._selectObject(self._selectedObj)
395 def OnScaleReset(self, button):
396 if self._selectedObj is None:
398 self._selectedObj.resetScale()
399 self._selectObject(self._selectedObj)
400 self.updateProfileToControls()
403 def OnScaleMax(self, button):
404 if self._selectedObj is None:
406 machine = profile.getMachineSetting('machine_type')
407 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
408 self._scene.pushFree()
410 if machine == "ultimaker2":
411 #This is bad and Jaime should feel bad!
412 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
413 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
414 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
415 self._scene.pushFree()
417 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
418 self._scene.pushFree()
419 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
420 self._scene.pushFree()
421 self._selectObject(self._selectedObj)
422 self.updateProfileToControls()
425 def OnMirror(self, axis):
426 if self._selectedObj is None:
428 self._selectedObj.mirror(axis)
431 def OnScaleEntry(self, value, axis):
432 if self._selectedObj is None:
438 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
439 self.updateProfileToControls()
440 self._scene.pushFree()
441 self._selectObject(self._selectedObj)
444 def OnScaleEntryMM(self, value, axis):
445 if self._selectedObj is None:
451 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
452 self.updateProfileToControls()
453 self._scene.pushFree()
454 self._selectObject(self._selectedObj)
457 def OnDeleteAll(self, e):
458 while len(self._scene.objects()) > 0:
459 self._deleteObject(self._scene.objects()[0])
460 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
462 def OnMultiply(self, e):
463 if self._focusObj is None:
466 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
467 if dlg.ShowModal() != wx.ID_OK:
476 self._scene.add(newObj)
477 self._scene.centerAll()
478 if not self._scene.checkPlatform(newObj):
483 self.notification.message("Could not create more then %d items" % (n - 1))
484 self._scene.remove(newObj)
485 self._scene.centerAll()
488 def OnSplitObject(self, e):
489 if self._focusObj is None:
491 self._scene.remove(self._focusObj)
492 for obj in self._focusObj.split(self._splitCallback):
493 if numpy.max(obj.getSize()) > 2.0:
495 self._scene.centerAll()
496 self._selectObject(None)
499 def OnCenter(self, e):
500 if self._focusObj is None:
502 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
503 self._scene.pushFree()
504 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
505 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
508 def _splitCallback(self, progress):
511 def OnMergeObjects(self, e):
512 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
513 if len(self._scene.objects()) == 2:
514 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
517 self._scene.merge(self._selectedObj, self._focusObj)
520 def sceneUpdated(self):
521 self._sceneUpdateTimer.Start(500, True)
522 self._slicer.abortSlicer()
523 self._scene.updateSizeOffsets()
526 def _onRunSlicer(self, e):
527 if self._isSimpleMode:
528 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
529 self._slicer.runSlicer(self._scene)
530 if self._isSimpleMode:
531 profile.resetTempOverride()
533 def _updateSliceProgress(self, progressValue, ready):
535 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
537 self.printButton.setDisabled(not ready)
538 if progressValue >= 0.0:
539 self.printButton.setProgressBar(progressValue)
541 self.printButton.setProgressBar(None)
542 if self._gcode is not None:
544 for layerVBOlist in self._gcodeVBOs:
545 for vbo in layerVBOlist:
546 self.glReleaseList.append(vbo)
549 self.printButton.setProgressBar(None)
550 text = '%s' % (self._slicer.getPrintTime())
551 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
552 amount = self._slicer.getFilamentAmount(e)
555 text += '\n%s' % (amount)
556 cost = self._slicer.getFilamentCost(e)
558 text += '\n%s' % (cost)
559 self.printButton.setBottomText(text)
560 self._gcode = gcodeInterpreter.gcode()
561 self._gcodeFilename = self._slicer.getGCodeFilename()
563 self.printButton.setBottomText('')
566 def _loadGCode(self):
567 self._gcode.progressCallback = self._gcodeLoadCallback
568 self._gcode.load(self._gcodeFilename)
570 def _gcodeLoadCallback(self, progress):
571 if not self or self._gcode is None:
573 if len(self._gcode.layerList) % 15 == 0:
575 if self._gcode is None:
577 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
578 if self.viewMode == 'gcode':
582 def loadScene(self, fileList):
583 for filename in fileList:
585 ext = os.path.splitext(filename)[1].lower()
586 if ext in imageToMesh.supportedExtensions():
587 imageToMesh.convertImageDialog(self, filename).Show()
590 objList = meshLoader.loadMeshes(filename)
592 traceback.print_exc()
595 if self._objectLoadShader is not None:
596 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
600 if not self._scene.checkPlatform(obj):
601 self._scene.centerAll()
602 self._selectObject(obj)
603 if obj.getScale()[0] < 1.0:
604 self.notification.message("Warning: Object scaled down.")
607 def _deleteObject(self, obj):
608 if obj == self._selectedObj:
609 self._selectObject(None)
610 if obj == self._focusObj:
611 self._focusObj = None
612 self._scene.remove(obj)
613 for m in obj._meshList:
614 if m.vbo is not None and m.vbo.decRef():
615 self.glReleaseList.append(m.vbo)
620 def _selectObject(self, obj, zoom = True):
621 if obj != self._selectedObj:
622 self._selectedObj = obj
623 self.updateModelSettingsToControls()
624 self.updateToolButtons()
625 if zoom and obj is not None:
626 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
627 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
628 newZoom = obj.getBoundaryCircle() * 6
629 if newZoom > numpy.max(self._machineSize) * 3:
630 newZoom = numpy.max(self._machineSize) * 3
631 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
633 def updateProfileToControls(self):
634 oldSimpleMode = self._isSimpleMode
635 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
636 if self._isSimpleMode != oldSimpleMode:
637 self._scene.arrangeAll()
639 self._scene.updateSizeOffsets(True)
640 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
641 self._objColors[0] = profile.getPreferenceColour('model_colour')
642 self._objColors[1] = profile.getPreferenceColour('model_colour2')
643 self._objColors[2] = profile.getPreferenceColour('model_colour3')
644 self._objColors[3] = profile.getPreferenceColour('model_colour4')
645 self._scene.updateMachineDimensions()
646 self.updateModelSettingsToControls()
648 def updateModelSettingsToControls(self):
649 if self._selectedObj is not None:
650 scale = self._selectedObj.getScale()
651 size = self._selectedObj.getSize()
652 self.scaleXctrl.setValue(round(scale[0], 2))
653 self.scaleYctrl.setValue(round(scale[1], 2))
654 self.scaleZctrl.setValue(round(scale[2], 2))
655 self.scaleXmmctrl.setValue(round(size[0], 2))
656 self.scaleYmmctrl.setValue(round(size[1], 2))
657 self.scaleZmmctrl.setValue(round(size[2], 2))
659 def OnKeyChar(self, keyCode):
660 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
661 if self._selectedObj is not None:
662 self._deleteObject(self._selectedObj)
664 if self.viewMode == 'gcode':
665 if keyCode == wx.WXK_UP:
666 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
668 elif keyCode == wx.WXK_DOWN:
669 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
671 elif keyCode == wx.WXK_PAGEUP:
672 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
674 elif keyCode == wx.WXK_PAGEDOWN:
675 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
678 if keyCode == wx.WXK_UP:
679 if wx.GetKeyState(wx.WXK_SHIFT):
686 elif keyCode == wx.WXK_DOWN:
687 if wx.GetKeyState(wx.WXK_SHIFT):
689 if self._zoom > numpy.max(self._machineSize) * 3:
690 self._zoom = numpy.max(self._machineSize) * 3
694 elif keyCode == wx.WXK_LEFT:
697 elif keyCode == wx.WXK_RIGHT:
700 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
705 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
707 if self._zoom > numpy.max(self._machineSize) * 3:
708 self._zoom = numpy.max(self._machineSize) * 3
710 elif keyCode == wx.WXK_HOME:
714 elif keyCode == wx.WXK_PAGEUP:
718 elif keyCode == wx.WXK_PAGEDOWN:
722 elif keyCode == wx.WXK_END:
727 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
728 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
729 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
730 from collections import defaultdict
731 from gc import get_objects
732 self._beforeLeakTest = defaultdict(int)
733 for i in get_objects():
734 self._beforeLeakTest[type(i)] += 1
735 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
736 from collections import defaultdict
737 from gc import get_objects
738 self._afterLeakTest = defaultdict(int)
739 for i in get_objects():
740 self._afterLeakTest[type(i)] += 1
741 for k in self._afterLeakTest:
742 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
743 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
745 def ShaderUpdate(self, v, f):
746 s = opengl.GLShader(v, f)
748 self._objectLoadShader.release()
749 self._objectLoadShader = s
750 for obj in self._scene.objects():
751 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
754 def OnMouseDown(self,e):
755 self._mouseX = e.GetX()
756 self._mouseY = e.GetY()
757 self._mouseClick3DPos = self._mouse3Dpos
758 self._mouseClickFocus = self._focusObj
760 self._mouseState = 'doubleClick'
762 self._mouseState = 'dragOrClick'
763 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
764 p0 -= self.getObjectCenterPos() - self._viewTarget
765 p1 -= self.getObjectCenterPos() - self._viewTarget
766 if self.tool.OnDragStart(p0, p1):
767 self._mouseState = 'tool'
768 if self._mouseState == 'dragOrClick':
769 if e.GetButton() == 1:
770 if self._focusObj is not None:
771 self._selectObject(self._focusObj, False)
774 def OnMouseUp(self, e):
775 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
777 if self._mouseState == 'dragOrClick':
778 if e.GetButton() == 1:
779 self._selectObject(self._focusObj)
780 if e.GetButton() == 3:
782 if self._focusObj is not None:
783 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
784 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
785 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
786 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
787 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:
788 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
789 if len(self._scene.objects()) > 0:
790 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
791 if menu.MenuItemCount > 0:
794 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
795 self._scene.pushFree()
797 elif self._mouseState == 'tool':
798 if self.tempMatrix is not None and self._selectedObj is not None:
799 self._selectedObj.applyMatrix(self.tempMatrix)
800 self._scene.pushFree()
801 self._selectObject(self._selectedObj)
802 self.tempMatrix = None
803 self.tool.OnDragEnd()
805 self._mouseState = None
807 def OnMouseMotion(self,e):
808 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
809 p0 -= self.getObjectCenterPos() - self._viewTarget
810 p1 -= self.getObjectCenterPos() - self._viewTarget
812 if e.Dragging() and self._mouseState is not None:
813 if self._mouseState == 'tool':
814 self.tool.OnDrag(p0, p1)
815 elif not e.LeftIsDown() and e.RightIsDown():
816 self._mouseState = 'drag'
817 if wx.GetKeyState(wx.WXK_SHIFT):
818 a = math.cos(math.radians(self._yaw)) / 3.0
819 b = math.sin(math.radians(self._yaw)) / 3.0
820 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
821 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
822 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
823 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
825 self._yaw += e.GetX() - self._mouseX
826 self._pitch -= e.GetY() - self._mouseY
827 if self._pitch > 170:
831 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
832 self._mouseState = 'drag'
833 self._zoom += e.GetY() - self._mouseY
836 if self._zoom > numpy.max(self._machineSize) * 3:
837 self._zoom = numpy.max(self._machineSize) * 3
838 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
839 self._mouseState = 'dragObject'
840 z = max(0, self._mouseClick3DPos[2])
841 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
842 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
847 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
848 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
849 diff = cursorZ1 - cursorZ0
850 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
851 if not e.Dragging() or self._mouseState != 'tool':
852 self.tool.OnMouseMove(p0, p1)
854 self._mouseX = e.GetX()
855 self._mouseY = e.GetY()
857 def OnMouseWheel(self, e):
858 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
859 delta = max(min(delta,4),-4)
860 self._zoom *= 1.0 - delta / 10.0
863 if self._zoom > numpy.max(self._machineSize) * 3:
864 self._zoom = numpy.max(self._machineSize) * 3
867 def OnMouseLeave(self, e):
871 def getMouseRay(self, x, y):
872 if self._viewport is None:
873 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
874 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
875 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
876 p0 -= self._viewTarget
877 p1 -= self._viewTarget
880 def _init3DView(self):
881 # set viewing projection
882 size = self.GetSize()
883 glViewport(0, 0, size.GetWidth(), size.GetHeight())
886 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
888 glDisable(GL_RESCALE_NORMAL)
889 glDisable(GL_LIGHTING)
891 glEnable(GL_DEPTH_TEST)
892 glDisable(GL_CULL_FACE)
894 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
896 glClearColor(0.8, 0.8, 0.8, 1.0)
900 glMatrixMode(GL_PROJECTION)
902 aspect = float(size.GetWidth()) / float(size.GetHeight())
903 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
905 glMatrixMode(GL_MODELVIEW)
907 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
910 connectionEntry = self._printerConnectionManager.getAvailableConnection()
911 if machineCom.machineIsConnected():
912 self.printButton._imageID = 6
913 self.printButton._tooltip = _("Print")
914 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
915 self.printButton._imageID = 2
916 self.printButton._tooltip = _("Toolpath to SD")
917 elif connectionEntry is not None:
918 self.printButton._imageID = connectionEntry.icon
919 self.printButton._tooltip = _("Print with %s") % (connectionEntry.name)
921 self.printButton._imageID = 3
922 self.printButton._tooltip = _("Save toolpath")
924 if self._animView is not None:
925 self._viewTarget = self._animView.getPosition()
926 if self._animView.isDone():
927 self._animView = None
928 if self._animZoom is not None:
929 self._zoom = self._animZoom.getPosition()
930 if self._animZoom.isDone():
931 self._animZoom = None
932 if self.viewMode == 'gcode' and self._gcode is not None:
934 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
937 if self._objectShader is None:
938 if opengl.hasShaderSupport():
939 self._objectShader = opengl.GLShader("""
940 varying float light_amount;
944 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
945 gl_FrontColor = gl_Color;
947 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
951 varying float light_amount;
955 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
958 self._objectOverhangShader = opengl.GLShader("""
959 uniform float cosAngle;
960 uniform mat3 rotMatrix;
961 varying float light_amount;
965 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
966 gl_FrontColor = gl_Color;
968 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
970 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
972 light_amount = -10.0;
976 varying float light_amount;
980 if (light_amount == -10.0)
982 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
984 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
988 self._objectLoadShader = opengl.GLShader("""
989 uniform float intensity;
991 varying float light_amount;
995 vec4 tmp = gl_Vertex;
996 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
997 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
998 gl_Position = gl_ModelViewProjectionMatrix * tmp;
999 gl_FrontColor = gl_Color;
1001 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1002 light_amount += 0.2;
1005 uniform float intensity;
1006 varying float light_amount;
1010 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1013 if self._objectShader is None or not self._objectShader.isValid():
1014 self._objectShader = opengl.GLFakeShader()
1015 self._objectOverhangShader = opengl.GLFakeShader()
1016 self._objectLoadShader = None
1018 glTranslate(0,0,-self._zoom)
1019 glRotate(-self._pitch, 1,0,0)
1020 glRotate(self._yaw, 0,0,1)
1021 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1023 self._viewport = glGetIntegerv(GL_VIEWPORT)
1024 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1025 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1027 glClearColor(1,1,1,1)
1028 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1030 if self.viewMode != 'gcode':
1031 for n in xrange(0, len(self._scene.objects())):
1032 obj = self._scene.objects()[n]
1033 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1034 self._renderObject(obj)
1036 if self._mouseX > -1:
1038 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1039 if n < len(self._scene.objects()):
1040 self._focusObj = self._scene.objects()[n]
1042 self._focusObj = None
1043 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1044 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1045 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1046 self._mouse3Dpos -= self._viewTarget
1049 glTranslate(0,0,-self._zoom)
1050 glRotate(-self._pitch, 1,0,0)
1051 glRotate(self._yaw, 0,0,1)
1052 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1054 if self.viewMode == 'gcode':
1055 if self._gcode is not None and self._gcode.layerList is None:
1056 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
1057 self._gcodeLoadThread.daemon = True
1058 self._gcodeLoadThread.start()
1059 if self._gcode is not None and self._gcode.layerList is not None:
1061 if profile.getMachineSetting('machine_center_is_zero') != 'True':
1062 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
1064 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
1065 for n in xrange(0, drawUpTill):
1066 c = 1.0 - float(drawUpTill - n) / 15
1068 if len(self._gcodeVBOs) < n + 1:
1069 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
1070 if time.time() - t > 0.5:
1073 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
1074 if n == drawUpTill - 1:
1075 if len(self._gcodeVBOs[n]) < 9:
1076 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
1078 self._gcodeVBOs[n][8].render(GL_QUADS)
1079 glColor3f(c/2, 0, c)
1080 self._gcodeVBOs[n][9].render(GL_QUADS)
1081 glColor3f(0, c, c/2)
1082 self._gcodeVBOs[n][10].render(GL_QUADS)
1084 self._gcodeVBOs[n][11].render(GL_QUADS)
1087 self._gcodeVBOs[n][12].render(GL_QUADS)
1088 glColor3f(c/2, c/2, 0.0)
1089 self._gcodeVBOs[n][13].render(GL_QUADS)
1091 self._gcodeVBOs[n][14].render(GL_QUADS)
1092 self._gcodeVBOs[n][15].render(GL_QUADS)
1094 self._gcodeVBOs[n][16].render(GL_LINES)
1097 self._gcodeVBOs[n][0].render(GL_LINES)
1098 glColor3f(c/2, 0, c)
1099 self._gcodeVBOs[n][1].render(GL_LINES)
1100 glColor3f(0, c, c/2)
1101 self._gcodeVBOs[n][2].render(GL_LINES)
1103 self._gcodeVBOs[n][3].render(GL_LINES)
1106 self._gcodeVBOs[n][4].render(GL_LINES)
1107 glColor3f(c/2, c/2, 0.0)
1108 self._gcodeVBOs[n][5].render(GL_LINES)
1110 self._gcodeVBOs[n][6].render(GL_LINES)
1111 self._gcodeVBOs[n][7].render(GL_LINES)
1114 glStencilFunc(GL_ALWAYS, 1, 1)
1115 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1117 if self.viewMode == 'overhang':
1118 self._objectOverhangShader.bind()
1119 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1121 self._objectShader.bind()
1122 for obj in self._scene.objects():
1123 if obj._loadAnim is not None:
1124 if obj._loadAnim.isDone():
1125 obj._loadAnim = None
1129 if self._focusObj == obj:
1131 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1134 if self._selectedObj == obj or self._selectedObj is None:
1135 #If we want transparent, then first render a solid black model to remove the printer size lines.
1136 if self.viewMode == 'transparent':
1137 glColor4f(0, 0, 0, 0)
1138 self._renderObject(obj)
1140 glBlendFunc(GL_ONE, GL_ONE)
1141 glDisable(GL_DEPTH_TEST)
1143 if self.viewMode == 'xray':
1144 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1145 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1146 glEnable(GL_STENCIL_TEST)
1148 if self.viewMode == 'overhang':
1149 if self._selectedObj == obj and self.tempMatrix is not None:
1150 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1152 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1154 if not self._scene.checkPlatform(obj):
1155 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1156 self._renderObject(obj)
1158 self._renderObject(obj, brightness)
1159 glDisable(GL_STENCIL_TEST)
1161 glEnable(GL_DEPTH_TEST)
1162 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1164 if self.viewMode == 'xray':
1167 glEnable(GL_STENCIL_TEST)
1168 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1169 glDisable(GL_DEPTH_TEST)
1170 for i in xrange(2, 15, 2):
1171 glStencilFunc(GL_EQUAL, i, 0xFF)
1172 glColor(float(i)/10, float(i)/10, float(i)/5)
1174 glVertex3f(-1000,-1000,-10)
1175 glVertex3f( 1000,-1000,-10)
1176 glVertex3f( 1000, 1000,-10)
1177 glVertex3f(-1000, 1000,-10)
1179 for i in xrange(1, 15, 2):
1180 glStencilFunc(GL_EQUAL, i, 0xFF)
1181 glColor(float(i)/10, 0, 0)
1183 glVertex3f(-1000,-1000,-10)
1184 glVertex3f( 1000,-1000,-10)
1185 glVertex3f( 1000, 1000,-10)
1186 glVertex3f(-1000, 1000,-10)
1189 glDisable(GL_STENCIL_TEST)
1190 glEnable(GL_DEPTH_TEST)
1192 self._objectShader.unbind()
1194 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1196 if self._objectLoadShader is not None:
1197 self._objectLoadShader.bind()
1198 glColor4f(0.2, 0.6, 1.0, 1.0)
1199 for obj in self._scene.objects():
1200 if obj._loadAnim is None:
1202 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1203 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1204 self._renderObject(obj)
1205 self._objectLoadShader.unbind()
1210 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1212 z = self._usbPrintMonitor.getZ()
1213 size = self._machineSize
1214 glColor4ub(255,255,0,128)
1216 glVertex3f(-size[0]/2,-size[1]/2, z)
1217 glVertex3f( size[0]/2,-size[1]/2, z)
1218 glVertex3f( size[0]/2, size[1]/2, z)
1219 glVertex3f(-size[0]/2, size[1]/2, z)
1222 if self.viewMode == 'gcode':
1223 if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1224 glDisable(GL_DEPTH_TEST)
1227 glTranslate(0,-4,-10)
1228 glColor4ub(60,60,60,255)
1229 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1232 #Draw the object box-shadow, so you can see where it will collide with other objects.
1233 if self._selectedObj is not None:
1235 glEnable(GL_CULL_FACE)
1236 glColor4f(0,0,0,0.16)
1238 for obj in self._scene.objects():
1240 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1241 glBegin(GL_TRIANGLE_FAN)
1242 for p in obj._boundaryHull[::-1]:
1243 glVertex3f(p[0], p[1], 0)
1247 glColor4f(0,0,0,0.06)
1248 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1249 glBegin(GL_TRIANGLE_FAN)
1250 for p in self._selectedObj._printAreaHull[::-1]:
1251 glVertex3f(p[0], p[1], 0)
1253 glBegin(GL_TRIANGLE_FAN)
1254 for p in self._selectedObj._headAreaMinHull[::-1]:
1255 glVertex3f(p[0], p[1], 0)
1258 glDisable(GL_CULL_FACE)
1261 #Draw the outline of the selected object, on top of everything else except the GUI.
1262 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1263 glDisable(GL_DEPTH_TEST)
1264 glEnable(GL_CULL_FACE)
1265 glEnable(GL_STENCIL_TEST)
1267 glStencilFunc(GL_EQUAL, 0, 255)
1269 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1271 glColor4f(1,1,1,0.5)
1272 self._renderObject(self._selectedObj)
1273 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1275 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1276 glDisable(GL_STENCIL_TEST)
1277 glDisable(GL_CULL_FACE)
1278 glEnable(GL_DEPTH_TEST)
1280 if self._selectedObj is not None:
1282 pos = self.getObjectCenterPos()
1283 glTranslate(pos[0], pos[1], pos[2])
1286 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1287 glDisable(GL_DEPTH_TEST)
1290 glTranslate(0,-4,-10)
1291 glColor4ub(60,60,60,255)
1292 opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1295 def _renderObject(self, obj, brightness = False, addSink = True):
1298 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1300 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1302 if self.tempMatrix is not None and obj == self._selectedObj:
1303 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1304 glMultMatrixf(tempMatrix)
1306 offset = obj.getDrawOffset()
1307 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1309 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1310 glMultMatrixf(tempMatrix)
1313 for m in obj._meshList:
1315 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1317 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1322 def _drawMachine(self):
1323 glEnable(GL_CULL_FACE)
1326 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1328 machine = profile.getMachineSetting('machine_type')
1329 if machine.startswith('ultimaker'):
1330 if machine not in self._platformMesh:
1331 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1333 self._platformMesh[machine] = meshes[0]
1335 self._platformMesh[machine] = None
1336 if machine == 'ultimaker2':
1337 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1339 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1340 glColor4f(1,1,1,0.5)
1341 self._objectShader.bind()
1342 self._renderObject(self._platformMesh[machine], False, False)
1343 self._objectShader.unbind()
1345 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1346 if machine == 'ultimaker2':
1347 if not hasattr(self._platformMesh[machine], 'texture'):
1348 self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1349 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1350 glEnable(GL_TEXTURE_2D)
1354 glTranslate(0,150,-5)
1359 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1362 glVertex3f( w, 0, h)
1364 glVertex3f(-w, 0, h)
1366 glVertex3f(-w, 0, 0)
1368 glVertex3f( w, 0, 0)
1371 glVertex3f(-w, d, h)
1373 glVertex3f( w, d, h)
1375 glVertex3f( w, d, 0)
1377 glVertex3f(-w, d, 0)
1379 glDisable(GL_TEXTURE_2D)
1380 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1386 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1387 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1388 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1389 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1390 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1391 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1396 polys = profile.getMachineSizePolygons()
1397 height = profile.getMachineSettingFloat('machine_height')
1399 for n in xrange(0, len(polys[0])):
1401 glColor4ub(5, 171, 231, 96)
1403 glColor4ub(5, 171, 231, 64)
1404 glVertex3f(polys[0][n][0], polys[0][n][1], height)
1405 glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1406 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1407 glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1409 glColor4ub(5, 171, 231, 128)
1410 glBegin(GL_TRIANGLE_FAN)
1411 for p in polys[0][::-1]:
1412 glVertex3f(p[0], p[1], height)
1416 if self._platformTexture is None:
1417 self._platformTexture = opengl.loadGLTexture('checkerboard.png')
1418 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1419 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1420 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1421 glColor4f(1,1,1,0.5)
1422 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1423 glEnable(GL_TEXTURE_2D)
1424 glBegin(GL_TRIANGLE_FAN)
1426 glTexCoord2f(p[0]/20, p[1]/20)
1427 glVertex3f(p[0], p[1], 0)
1429 glDisable(GL_TEXTURE_2D)
1430 glColor4ub(127, 127, 127, 200)
1431 for poly in polys[1:]:
1432 glBegin(GL_TRIANGLE_FAN)
1434 glTexCoord2f(p[0]/20, p[1]/20)
1435 glVertex3f(p[0], p[1], 0)
1440 glDisable(GL_CULL_FACE)
1442 def _generateGCodeVBOs(self, layer):
1444 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1445 if ':' in extrudeType:
1446 extruder = int(extrudeType[extrudeType.find(':')+1:])
1447 extrudeType = extrudeType[0:extrudeType.find(':')]
1450 pointList = numpy.zeros((0,3), numpy.float32)
1452 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1454 a = numpy.concatenate((a[:-1], a[1:]), 1)
1455 a = a.reshape((len(a) * 2, 3))
1456 pointList = numpy.concatenate((pointList, a))
1457 ret.append(opengl.GLVBO(pointList))
1460 def _generateGCodeVBOs2(self, layer):
1461 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1462 filamentArea = math.pi * filamentRadius * filamentRadius
1463 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1466 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1467 if ':' in extrudeType:
1468 extruder = int(extrudeType[extrudeType.find(':')+1:])
1469 extrudeType = extrudeType[0:extrudeType.find(':')]
1472 pointList = numpy.zeros((0,3), numpy.float32)
1474 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1476 if extrudeType == 'FILL':
1479 normal = a[1:] - a[:-1]
1480 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1481 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1484 ePerDist = path['extrusion'][1:] / lens
1486 lineWidth = ePerDist / path['layerThickness'] / 2.0
1488 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1490 normal[:,0] *= lineWidth
1491 normal[:,1] *= lineWidth
1493 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1494 b = numpy.concatenate((b, a[1:] + normal), 1)
1495 b = numpy.concatenate((b, a[1:] - normal), 1)
1496 b = numpy.concatenate((b, a[:-1] - normal), 1)
1497 b = numpy.concatenate((b, a[:-1] + normal), 1)
1498 b = b.reshape((len(b) * 4, 3))
1501 normal2 = normal[:-1] + normal[1:]
1502 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1503 normal2[:,0] /= lens2
1504 normal2[:,1] /= lens2
1505 normal2[:,0] *= lineWidth[:-1]
1506 normal2[:,1] *= lineWidth[:-1]
1508 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1509 c = numpy.concatenate((c, a[1:-1]), 1)
1510 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1511 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1512 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1514 c = numpy.concatenate((c, a[1:-1]), 1)
1515 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1516 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1517 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1519 c = c.reshape((len(c) * 8, 3))
1521 pointList = numpy.concatenate((pointList, b, c))
1523 pointList = numpy.concatenate((pointList, b))
1524 ret.append(opengl.GLVBO(pointList))
1526 pointList = numpy.zeros((0,3), numpy.float32)
1528 if path['type'] == 'move':
1529 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1530 a = numpy.concatenate((a[:-1], a[1:]), 1)
1531 a = a.reshape((len(a) * 2, 3))
1532 pointList = numpy.concatenate((pointList, a))
1533 if path['type'] == 'retract':
1534 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1535 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1536 a = a.reshape((len(a) * 2, 3))
1537 pointList = numpy.concatenate((pointList, a))
1538 ret.append(opengl.GLVBO(pointList))
1542 def getObjectCenterPos(self):
1543 if self._selectedObj is None:
1544 return [0.0, 0.0, 0.0]
1545 pos = self._selectedObj.getPosition()
1546 size = self._selectedObj.getSize()
1547 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1549 def getObjectBoundaryCircle(self):
1550 if self._selectedObj is None:
1552 return self._selectedObj.getBoundaryCircle()
1554 def getObjectSize(self):
1555 if self._selectedObj is None:
1556 return [0.0, 0.0, 0.0]
1557 return self._selectedObj.getSize()
1559 def getObjectMatrix(self):
1560 if self._selectedObj is None:
1561 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1562 return self._selectedObj.getMatrix()
1564 class shaderEditor(wx.Dialog):
1565 def __init__(self, parent, callback, v, f):
1566 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1567 self._callback = callback
1568 s = wx.BoxSizer(wx.VERTICAL)
1570 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1571 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1572 s.Add(self._vertex, 1, flag=wx.EXPAND)
1573 s.Add(self._fragment, 1, flag=wx.EXPAND)
1575 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1576 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1578 self.SetPosition(self.GetParent().GetPosition())
1579 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1582 def OnText(self, e):
1583 self._callback(self._vertex.GetValue(), self._fragment.GetValue())