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.util import profile
20 from Cura.util import meshLoader
21 from Cura.util import objectScene
22 from Cura.util import resources
23 from Cura.util import sliceEngine
24 from Cura.util import machineCom
25 from Cura.util import removableStorage
26 from Cura.util import gcodeInterpreter
27 from Cura.util.printerConnection import printerConnectionManager
28 from Cura.gui.util import previewTools
29 from Cura.gui.util import opengl
30 from Cura.gui.util import openglGui
31 from Cura.gui.tools import youmagineGui
32 from Cura.gui.tools import imageToMesh
34 class SceneView(openglGui.glGuiPanel):
35 def __init__(self, parent):
36 super(SceneView, self).__init__(parent)
41 self._scene = objectScene.Scene()
44 self._gcodeFilename = None
45 self._gcodeLoadThread = None
46 self._objectShader = None
47 self._objectLoadShader = None
49 self._selectedObj = None
50 self._objColors = [None,None,None,None]
53 self._mouseState = None
54 self._viewTarget = numpy.array([0,0,0], numpy.float32)
57 self._platformMesh = {}
58 self._isSimpleMode = True
59 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
60 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
63 self._modelMatrix = None
64 self._projMatrix = None
65 self.tempMatrix = None
67 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
68 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
69 self.printButton.setDisabled(True)
72 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
73 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
74 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
76 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
77 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
79 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
80 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
82 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
83 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
84 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
86 self.rotateToolButton.setExpandArrow(True)
87 self.scaleToolButton.setExpandArrow(True)
88 self.mirrorToolButton.setExpandArrow(True)
90 self.scaleForm = openglGui.glFrame(self, (2, -2))
91 openglGui.glGuiLayoutGrid(self.scaleForm)
92 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
93 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
94 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
95 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
96 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
97 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
98 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
99 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
100 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
101 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
102 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
103 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
104 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
105 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
107 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
108 self.layerSelect = openglGui.glSlider(self, 10000, 0, 1, (-1,-2), lambda : self.QueueRefresh())
110 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
111 self.youMagineButton.setDisabled(True)
113 self.notification = openglGui.glNotification(self, (0, 0))
115 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
116 self._sceneUpdateTimer = wx.Timer(self)
117 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
118 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
119 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
123 self.updateToolButtons()
124 self.updateProfileToControls()
126 def loadGCodeFile(self, filename):
127 self.OnDeleteAll(None)
128 if self._gcode is not None:
130 for layerVBOlist in self._gcodeVBOs:
131 for vbo in layerVBOlist:
132 self.glReleaseList.append(vbo)
134 self._gcode = gcodeInterpreter.gcode()
135 self._gcodeFilename = filename
136 self.printButton.setBottomText('')
137 self.viewSelection.setValue(4)
138 self.printButton.setDisabled(False)
139 self.youMagineButton.setDisabled(True)
142 def loadSceneFiles(self, filenames):
143 self.youMagineButton.setDisabled(False)
144 #if self.viewSelection.getValue() == 4:
145 # self.viewSelection.setValue(0)
146 # self.OnViewChange()
147 self.loadScene(filenames)
149 def loadFiles(self, filenames):
150 mainWindow = self.GetParent().GetParent().GetParent()
151 # only one GCODE file can be active
152 # so if single gcode file, process this
153 # otherwise ignore all gcode files
155 if len(filenames) == 1:
156 filename = filenames[0]
157 ext = os.path.splitext(filename)[1].lower()
158 if ext == '.g' or ext == '.gcode':
159 gcodeFilename = filename
160 mainWindow.addToModelMRU(filename)
161 if gcodeFilename is not None:
162 self.loadGCodeFile(gcodeFilename)
164 # process directories and special file types
165 # and keep scene files for later processing
167 ignored_types = dict()
168 # use file list as queue
169 # pop first entry for processing and append new files at end
171 filename = filenames.pop(0)
172 if os.path.isdir(filename):
173 # directory: queue all included files and directories
174 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
176 ext = os.path.splitext(filename)[1].lower()
178 profile.loadProfile(filename)
179 mainWindow.addToProfileMRU(filename)
180 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
181 scene_filenames.append(filename)
182 mainWindow.addToModelMRU(filename)
184 ignored_types[ext] = 1
186 ignored_types = ignored_types.keys()
188 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
189 mainWindow.updateProfileToAllControls()
190 # now process all the scene files
192 self.loadSceneFiles(scene_filenames)
193 self._selectObject(None)
195 newZoom = numpy.max(self._machineSize)
196 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
197 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
199 def showLoadModel(self, button = 1):
201 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)
202 dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
203 if dlg.ShowModal() != wx.ID_OK:
206 filenames = dlg.GetPaths()
208 if len(filenames) < 1:
210 profile.putPreference('lastFile', filenames[0])
211 self.loadFiles(filenames)
213 def showSaveModel(self):
214 if len(self._scene.objects()) < 1:
216 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
217 dlg.SetWildcard(meshLoader.saveWildcardFilter())
218 if dlg.ShowModal() != wx.ID_OK:
221 filename = dlg.GetPath()
223 meshLoader.saveMeshes(filename, self._scene.objects())
225 def OnPrintButton(self, button):
227 if machineCom.machineIsConnected():
228 self.showPrintWindow()
229 elif len(removableStorage.getPossibleSDcardDrives()) > 0:
230 drives = removableStorage.getPossibleSDcardDrives()
232 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))
233 if dlg.ShowModal() != wx.ID_OK:
236 drive = drives[dlg.GetSelection()]
240 filename = self._scene._objectList[0].getName() + '.gcode'
241 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
246 self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
247 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
248 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
252 def showPrintWindow(self):
253 if self._gcodeFilename is None:
255 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
256 wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
258 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
259 if self._gcodeFilename == self._slicer.getGCodeFilename():
260 self._slicer.submitSliceInfoOnline()
262 def showSaveGCode(self):
263 if len(self._scene._objectList) < 1:
265 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
266 filename = self._scene._objectList[0].getName() + '.gcode'
267 dlg.SetFilename(filename)
268 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
269 if dlg.ShowModal() != wx.ID_OK:
272 filename = dlg.GetPath()
275 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
277 def _copyFile(self, fileA, fileB, allowEject = False):
279 size = float(os.stat(fileA).st_size)
280 with open(fileA, 'rb') as fsrc:
281 with open(fileB, 'wb') as fdst:
283 buf = fsrc.read(16*1024)
287 self.printButton.setProgressBar(float(fsrc.tell()) / size)
292 self.notification.message("Failed to save")
295 self.notification.message("Saved as %s" % (fileB), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(allowEject) else self.notification.message('Safe remove failed...'))
297 self.notification.message("Saved as %s" % (fileB))
298 self.printButton.setProgressBar(None)
299 if fileA == self._slicer.getGCodeFilename():
300 self._slicer.submitSliceInfoOnline()
302 def _showSliceLog(self):
303 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
307 def OnToolSelect(self, button):
308 if self.rotateToolButton.getSelected():
309 self.tool = previewTools.toolRotate(self)
310 elif self.scaleToolButton.getSelected():
311 self.tool = previewTools.toolScale(self)
312 elif self.mirrorToolButton.getSelected():
313 self.tool = previewTools.toolNone(self)
315 self.tool = previewTools.toolNone(self)
316 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
317 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
318 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
319 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
320 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
321 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
322 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
323 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
325 def updateToolButtons(self):
326 if self._selectedObj is None:
330 self.rotateToolButton.setHidden(hidden)
331 self.scaleToolButton.setHidden(hidden)
332 self.mirrorToolButton.setHidden(hidden)
334 self.rotateToolButton.setSelected(False)
335 self.scaleToolButton.setSelected(False)
336 self.mirrorToolButton.setSelected(False)
339 def OnViewChange(self):
340 if self.viewSelection.getValue() == 4:
341 self.viewMode = 'gcode'
342 if self._gcode is not None and self._gcode.layerList is not None:
343 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
344 self._selectObject(None)
345 elif self.viewSelection.getValue() == 1:
346 self.viewMode = 'overhang'
347 elif self.viewSelection.getValue() == 2:
348 self.viewMode = 'transparent'
349 elif self.viewSelection.getValue() == 3:
350 self.viewMode = 'xray'
352 self.viewMode = 'normal'
353 self.layerSelect.setHidden(self.viewMode != 'gcode')
356 def OnRotateReset(self, button):
357 if self._selectedObj is None:
359 self._selectedObj.resetRotation()
360 self._scene.pushFree()
361 self._selectObject(self._selectedObj)
364 def OnLayFlat(self, button):
365 if self._selectedObj is None:
367 self._selectedObj.layFlat()
368 self._scene.pushFree()
369 self._selectObject(self._selectedObj)
372 def OnScaleReset(self, button):
373 if self._selectedObj is None:
375 self._selectedObj.resetScale()
376 self._selectObject(self._selectedObj)
377 self.updateProfileToControls()
380 def OnScaleMax(self, button):
381 if self._selectedObj is None:
383 machine = profile.getMachineSetting('machine_type')
384 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
385 self._scene.pushFree()
387 if machine == "ultimaker2":
388 #This is bad and Jaime should feel bad!
389 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
390 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
391 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
392 self._scene.pushFree()
394 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
395 self._scene.pushFree()
396 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
397 self._scene.pushFree()
398 self._selectObject(self._selectedObj)
399 self.updateProfileToControls()
402 def OnMirror(self, axis):
403 if self._selectedObj is None:
405 self._selectedObj.mirror(axis)
408 def OnScaleEntry(self, value, axis):
409 if self._selectedObj is None:
415 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
416 self.updateProfileToControls()
417 self._scene.pushFree()
418 self._selectObject(self._selectedObj)
421 def OnScaleEntryMM(self, value, axis):
422 if self._selectedObj is None:
428 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
429 self.updateProfileToControls()
430 self._scene.pushFree()
431 self._selectObject(self._selectedObj)
434 def OnDeleteAll(self, e):
435 while len(self._scene.objects()) > 0:
436 self._deleteObject(self._scene.objects()[0])
437 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
439 def OnMultiply(self, e):
440 if self._focusObj is None:
443 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
444 if dlg.ShowModal() != wx.ID_OK:
453 self._scene.add(newObj)
454 self._scene.centerAll()
455 if not self._scene.checkPlatform(newObj):
460 self.notification.message("Could not create more then %d items" % (n - 1))
461 self._scene.remove(newObj)
462 self._scene.centerAll()
465 def OnSplitObject(self, e):
466 if self._focusObj is None:
468 self._scene.remove(self._focusObj)
469 for obj in self._focusObj.split(self._splitCallback):
470 if numpy.max(obj.getSize()) > 2.0:
472 self._scene.centerAll()
473 self._selectObject(None)
476 def OnCenter(self, e):
477 if self._focusObj is None:
479 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
480 self._scene.pushFree()
481 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
482 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
485 def _splitCallback(self, progress):
488 def OnMergeObjects(self, e):
489 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
490 if len(self._scene.objects()) == 2:
491 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
494 self._scene.merge(self._selectedObj, self._focusObj)
497 def sceneUpdated(self):
498 self._sceneUpdateTimer.Start(500, True)
499 self._slicer.abortSlicer()
500 self._scene.updateSizeOffsets()
503 def _onRunSlicer(self, e):
504 if self._isSimpleMode:
505 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
506 self._slicer.runSlicer(self._scene)
507 if self._isSimpleMode:
508 profile.resetTempOverride()
510 def _updateSliceProgress(self, progressValue, ready):
512 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
514 self.printButton.setDisabled(not ready)
515 if progressValue >= 0.0:
516 self.printButton.setProgressBar(progressValue)
518 self.printButton.setProgressBar(None)
519 if self._gcode is not None:
521 for layerVBOlist in self._gcodeVBOs:
522 for vbo in layerVBOlist:
523 self.glReleaseList.append(vbo)
526 self.printButton.setProgressBar(None)
527 text = '%s' % (self._slicer.getPrintTime())
528 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
529 amount = self._slicer.getFilamentAmount(e)
532 text += '\n%s' % (amount)
533 cost = self._slicer.getFilamentCost(e)
535 text += '\n%s' % (cost)
536 self.printButton.setBottomText(text)
537 self._gcode = gcodeInterpreter.gcode()
538 self._gcodeFilename = self._slicer.getGCodeFilename()
540 self.printButton.setBottomText('')
543 def _loadGCode(self):
544 self._gcode.progressCallback = self._gcodeLoadCallback
545 self._gcode.load(self._gcodeFilename)
547 def _gcodeLoadCallback(self, progress):
548 if not self or self._gcode is None:
550 if len(self._gcode.layerList) % 15 == 0:
552 if self._gcode is None:
554 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
555 if self.viewMode == 'gcode':
559 def loadScene(self, fileList):
560 for filename in fileList:
562 ext = os.path.splitext(filename)[1].lower()
563 if ext in imageToMesh.supportedExtensions():
564 imageToMesh.convertImageDialog(self, filename).Show()
567 objList = meshLoader.loadMeshes(filename)
569 traceback.print_exc()
572 if self._objectLoadShader is not None:
573 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
577 self._scene.centerAll()
578 self._selectObject(obj)
579 if obj.getScale()[0] < 1.0:
580 self.notification.message("Warning: Object scaled down.")
583 def _deleteObject(self, obj):
584 if obj == self._selectedObj:
585 self._selectObject(None)
586 if obj == self._focusObj:
587 self._focusObj = None
588 self._scene.remove(obj)
589 for m in obj._meshList:
590 if m.vbo is not None and m.vbo.decRef():
591 self.glReleaseList.append(m.vbo)
596 def _selectObject(self, obj, zoom = True):
597 if obj != self._selectedObj:
598 self._selectedObj = obj
599 self.updateProfileToControls()
600 self.updateToolButtons()
601 if zoom and obj is not None:
602 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
603 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
604 newZoom = obj.getBoundaryCircle() * 6
605 if newZoom > numpy.max(self._machineSize) * 3:
606 newZoom = numpy.max(self._machineSize) * 3
607 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
609 def updateProfileToControls(self):
610 oldSimpleMode = self._isSimpleMode
611 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
612 if self._isSimpleMode != oldSimpleMode:
613 self._scene.arrangeAll()
615 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
616 self._objColors[0] = profile.getPreferenceColour('model_colour')
617 self._objColors[1] = profile.getPreferenceColour('model_colour2')
618 self._objColors[2] = profile.getPreferenceColour('model_colour3')
619 self._objColors[3] = profile.getPreferenceColour('model_colour4')
620 self._scene.setMachineSize(self._machineSize)
621 self._scene.setHeadSize(profile.getMachineSettingFloat('extruder_head_size_min_x'), profile.getMachineSettingFloat('extruder_head_size_max_x'), profile.getMachineSettingFloat('extruder_head_size_min_y'), profile.getMachineSettingFloat('extruder_head_size_max_y'), profile.getMachineSettingFloat('extruder_head_size_height'))
623 if self._selectedObj is not None:
624 scale = self._selectedObj.getScale()
625 size = self._selectedObj.getSize()
626 self.scaleXctrl.setValue(round(scale[0], 2))
627 self.scaleYctrl.setValue(round(scale[1], 2))
628 self.scaleZctrl.setValue(round(scale[2], 2))
629 self.scaleXmmctrl.setValue(round(size[0], 2))
630 self.scaleYmmctrl.setValue(round(size[1], 2))
631 self.scaleZmmctrl.setValue(round(size[2], 2))
633 def OnKeyChar(self, keyCode):
634 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
635 if self._selectedObj is not None:
636 self._deleteObject(self._selectedObj)
638 if keyCode == wx.WXK_UP:
639 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
641 elif keyCode == wx.WXK_DOWN:
642 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
644 elif keyCode == wx.WXK_PAGEUP:
645 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
647 elif keyCode == wx.WXK_PAGEDOWN:
648 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
651 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
652 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
653 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
654 from collections import defaultdict
655 from gc import get_objects
656 self._beforeLeakTest = defaultdict(int)
657 for i in get_objects():
658 self._beforeLeakTest[type(i)] += 1
659 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
660 from collections import defaultdict
661 from gc import get_objects
662 self._afterLeakTest = defaultdict(int)
663 for i in get_objects():
664 self._afterLeakTest[type(i)] += 1
665 for k in self._afterLeakTest:
666 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
667 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
669 def ShaderUpdate(self, v, f):
670 s = opengl.GLShader(v, f)
672 self._objectLoadShader.release()
673 self._objectLoadShader = s
674 for obj in self._scene.objects():
675 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
678 def OnMouseDown(self,e):
679 self._mouseX = e.GetX()
680 self._mouseY = e.GetY()
681 self._mouseClick3DPos = self._mouse3Dpos
682 self._mouseClickFocus = self._focusObj
684 self._mouseState = 'doubleClick'
686 self._mouseState = 'dragOrClick'
687 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
688 p0 -= self.getObjectCenterPos() - self._viewTarget
689 p1 -= self.getObjectCenterPos() - self._viewTarget
690 if self.tool.OnDragStart(p0, p1):
691 self._mouseState = 'tool'
692 if self._mouseState == 'dragOrClick':
693 if e.GetButton() == 1:
694 if self._focusObj is not None:
695 self._selectObject(self._focusObj, False)
698 def OnMouseUp(self, e):
699 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
701 if self._mouseState == 'dragOrClick':
702 if e.GetButton() == 1:
703 self._selectObject(self._focusObj)
704 if e.GetButton() == 3:
706 if self._focusObj is not None:
707 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
708 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
709 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
710 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
711 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:
712 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
713 if len(self._scene.objects()) > 0:
714 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
715 if menu.MenuItemCount > 0:
718 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
719 self._scene.pushFree()
721 elif self._mouseState == 'tool':
722 if self.tempMatrix is not None and self._selectedObj is not None:
723 self._selectedObj.applyMatrix(self.tempMatrix)
724 self._scene.pushFree()
725 self._selectObject(self._selectedObj)
726 self.tempMatrix = None
727 self.tool.OnDragEnd()
729 self._mouseState = None
731 def OnMouseMotion(self,e):
732 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
733 p0 -= self.getObjectCenterPos() - self._viewTarget
734 p1 -= self.getObjectCenterPos() - self._viewTarget
736 if e.Dragging() and self._mouseState is not None:
737 if self._mouseState == 'tool':
738 self.tool.OnDrag(p0, p1)
739 elif not e.LeftIsDown() and e.RightIsDown():
740 self._mouseState = 'drag'
741 if wx.GetKeyState(wx.WXK_SHIFT):
742 a = math.cos(math.radians(self._yaw)) / 3.0
743 b = math.sin(math.radians(self._yaw)) / 3.0
744 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
745 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
746 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
747 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
749 self._yaw += e.GetX() - self._mouseX
750 self._pitch -= e.GetY() - self._mouseY
751 if self._pitch > 170:
755 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
756 self._mouseState = 'drag'
757 self._zoom += e.GetY() - self._mouseY
760 if self._zoom > numpy.max(self._machineSize) * 3:
761 self._zoom = numpy.max(self._machineSize) * 3
762 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
763 self._mouseState = 'dragObject'
764 z = max(0, self._mouseClick3DPos[2])
765 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
766 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
771 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
772 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
773 diff = cursorZ1 - cursorZ0
774 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
775 if not e.Dragging() or self._mouseState != 'tool':
776 self.tool.OnMouseMove(p0, p1)
778 self._mouseX = e.GetX()
779 self._mouseY = e.GetY()
781 def OnMouseWheel(self, e):
782 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
783 delta = max(min(delta,4),-4)
784 self._zoom *= 1.0 - delta / 10.0
787 if self._zoom > numpy.max(self._machineSize) * 3:
788 self._zoom = numpy.max(self._machineSize) * 3
791 def OnMouseLeave(self, e):
795 def getMouseRay(self, x, y):
796 if self._viewport is None:
797 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
798 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
799 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
800 p0 -= self._viewTarget
801 p1 -= self._viewTarget
804 def _init3DView(self):
805 # set viewing projection
806 size = self.GetSize()
807 glViewport(0, 0, size.GetWidth(), size.GetHeight())
810 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
812 glDisable(GL_RESCALE_NORMAL)
813 glDisable(GL_LIGHTING)
815 glEnable(GL_DEPTH_TEST)
816 glDisable(GL_CULL_FACE)
818 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
820 glClearColor(0.8, 0.8, 0.8, 1.0)
824 glMatrixMode(GL_PROJECTION)
826 aspect = float(size.GetWidth()) / float(size.GetHeight())
827 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
829 glMatrixMode(GL_MODELVIEW)
831 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
834 connectionEntry = self._printerConnectionManager.getAvailableConnection()
835 if machineCom.machineIsConnected():
836 self.printButton._imageID = 6
837 self.printButton._tooltip = _("Print")
838 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
839 self.printButton._imageID = 2
840 self.printButton._tooltip = _("Toolpath to SD")
841 elif connectionEntry is not None:
842 self.printButton._imageID = connectionEntry.icon
843 self.printButton._tooltip = _("Print with %s") % (connectionEntry.name)
845 self.printButton._imageID = 3
846 self.printButton._tooltip = _("Save toolpath")
848 if self._animView is not None:
849 self._viewTarget = self._animView.getPosition()
850 if self._animView.isDone():
851 self._animView = None
852 if self._animZoom is not None:
853 self._zoom = self._animZoom.getPosition()
854 if self._animZoom.isDone():
855 self._animZoom = None
856 if self.viewMode == 'gcode' and self._gcode is not None:
858 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
861 if self._objectShader is None:
862 if opengl.hasShaderSupport():
863 self._objectShader = opengl.GLShader("""
864 varying float light_amount;
868 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
869 gl_FrontColor = gl_Color;
871 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
875 varying float light_amount;
879 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
882 self._objectOverhangShader = opengl.GLShader("""
883 uniform float cosAngle;
884 uniform mat3 rotMatrix;
885 varying float light_amount;
889 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
890 gl_FrontColor = gl_Color;
892 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
894 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
896 light_amount = -10.0;
900 varying float light_amount;
904 if (light_amount == -10.0)
906 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
908 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
912 self._objectLoadShader = opengl.GLShader("""
913 uniform float intensity;
915 varying float light_amount;
919 vec4 tmp = gl_Vertex;
920 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
921 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
922 gl_Position = gl_ModelViewProjectionMatrix * tmp;
923 gl_FrontColor = gl_Color;
925 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
929 uniform float intensity;
930 varying float light_amount;
934 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
937 if self._objectShader is None or not self._objectShader.isValid():
938 self._objectShader = opengl.GLFakeShader()
939 self._objectOverhangShader = opengl.GLFakeShader()
940 self._objectLoadShader = None
942 glTranslate(0,0,-self._zoom)
943 glRotate(-self._pitch, 1,0,0)
944 glRotate(self._yaw, 0,0,1)
945 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
947 self._viewport = glGetIntegerv(GL_VIEWPORT)
948 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
949 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
951 glClearColor(1,1,1,1)
952 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
954 if self.viewMode != 'gcode':
955 for n in xrange(0, len(self._scene.objects())):
956 obj = self._scene.objects()[n]
957 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
958 self._renderObject(obj)
960 if self._mouseX > -1:
962 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
963 if n < len(self._scene.objects()):
964 self._focusObj = self._scene.objects()[n]
966 self._focusObj = None
967 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
968 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
969 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
970 self._mouse3Dpos -= self._viewTarget
973 glTranslate(0,0,-self._zoom)
974 glRotate(-self._pitch, 1,0,0)
975 glRotate(self._yaw, 0,0,1)
976 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
978 if self.viewMode == 'gcode':
979 if self._gcode is not None and self._gcode.layerList is None:
980 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
981 self._gcodeLoadThread.daemon = True
982 self._gcodeLoadThread.start()
983 if self._gcode is not None and self._gcode.layerList is not None:
985 if profile.getMachineSetting('machine_center_is_zero') != 'True':
986 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
988 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
989 for n in xrange(0, drawUpTill):
990 c = 1.0 - float(drawUpTill - n) / 15
992 if len(self._gcodeVBOs) < n + 1:
993 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
994 if time.time() - t > 0.5:
997 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
998 if n == drawUpTill - 1:
999 if len(self._gcodeVBOs[n]) < 9:
1000 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
1002 self._gcodeVBOs[n][8].render(GL_QUADS)
1003 glColor3f(c/2, 0, c)
1004 self._gcodeVBOs[n][9].render(GL_QUADS)
1005 glColor3f(0, c, c/2)
1006 self._gcodeVBOs[n][10].render(GL_QUADS)
1008 self._gcodeVBOs[n][11].render(GL_QUADS)
1011 self._gcodeVBOs[n][12].render(GL_QUADS)
1012 glColor3f(c/2, c/2, 0.0)
1013 self._gcodeVBOs[n][13].render(GL_QUADS)
1015 self._gcodeVBOs[n][14].render(GL_QUADS)
1016 self._gcodeVBOs[n][15].render(GL_QUADS)
1018 self._gcodeVBOs[n][16].render(GL_LINES)
1021 self._gcodeVBOs[n][0].render(GL_LINES)
1022 glColor3f(c/2, 0, c)
1023 self._gcodeVBOs[n][1].render(GL_LINES)
1024 glColor3f(0, c, c/2)
1025 self._gcodeVBOs[n][2].render(GL_LINES)
1027 self._gcodeVBOs[n][3].render(GL_LINES)
1030 self._gcodeVBOs[n][4].render(GL_LINES)
1031 glColor3f(c/2, c/2, 0.0)
1032 self._gcodeVBOs[n][5].render(GL_LINES)
1034 self._gcodeVBOs[n][6].render(GL_LINES)
1035 self._gcodeVBOs[n][7].render(GL_LINES)
1038 glStencilFunc(GL_ALWAYS, 1, 1)
1039 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1041 if self.viewMode == 'overhang':
1042 self._objectOverhangShader.bind()
1043 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1045 self._objectShader.bind()
1046 for obj in self._scene.objects():
1047 if obj._loadAnim is not None:
1048 if obj._loadAnim.isDone():
1049 obj._loadAnim = None
1053 if self._focusObj == obj:
1055 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1058 if self._selectedObj == obj or self._selectedObj is None:
1059 #If we want transparent, then first render a solid black model to remove the printer size lines.
1060 if self.viewMode == 'transparent':
1061 glColor4f(0, 0, 0, 0)
1062 self._renderObject(obj)
1064 glBlendFunc(GL_ONE, GL_ONE)
1065 glDisable(GL_DEPTH_TEST)
1067 if self.viewMode == 'xray':
1068 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1069 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1070 glEnable(GL_STENCIL_TEST)
1072 if self.viewMode == 'overhang':
1073 if self._selectedObj == obj and self.tempMatrix is not None:
1074 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1076 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1078 if not self._scene.checkPlatform(obj):
1079 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1080 self._renderObject(obj)
1082 self._renderObject(obj, brightness)
1083 glDisable(GL_STENCIL_TEST)
1085 glEnable(GL_DEPTH_TEST)
1086 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1088 if self.viewMode == 'xray':
1091 glEnable(GL_STENCIL_TEST)
1092 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1093 glDisable(GL_DEPTH_TEST)
1094 for i in xrange(2, 15, 2):
1095 glStencilFunc(GL_EQUAL, i, 0xFF)
1096 glColor(float(i)/10, float(i)/10, float(i)/5)
1098 glVertex3f(-1000,-1000,-10)
1099 glVertex3f( 1000,-1000,-10)
1100 glVertex3f( 1000, 1000,-10)
1101 glVertex3f(-1000, 1000,-10)
1103 for i in xrange(1, 15, 2):
1104 glStencilFunc(GL_EQUAL, i, 0xFF)
1105 glColor(float(i)/10, 0, 0)
1107 glVertex3f(-1000,-1000,-10)
1108 glVertex3f( 1000,-1000,-10)
1109 glVertex3f( 1000, 1000,-10)
1110 glVertex3f(-1000, 1000,-10)
1113 glDisable(GL_STENCIL_TEST)
1114 glEnable(GL_DEPTH_TEST)
1116 self._objectShader.unbind()
1118 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1120 if self._objectLoadShader is not None:
1121 self._objectLoadShader.bind()
1122 glColor4f(0.2, 0.6, 1.0, 1.0)
1123 for obj in self._scene.objects():
1124 if obj._loadAnim is None:
1126 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1127 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1128 self._renderObject(obj)
1129 self._objectLoadShader.unbind()
1134 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1136 z = self._usbPrintMonitor.getZ()
1137 size = self._machineSize
1138 glColor4ub(255,255,0,128)
1140 glVertex3f(-size[0]/2,-size[1]/2, z)
1141 glVertex3f( size[0]/2,-size[1]/2, z)
1142 glVertex3f( size[0]/2, size[1]/2, z)
1143 glVertex3f(-size[0]/2, size[1]/2, z)
1146 if self.viewMode == 'gcode':
1147 if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1148 glDisable(GL_DEPTH_TEST)
1151 glTranslate(0,-4,-10)
1152 glColor4ub(60,60,60,255)
1153 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1156 #Draw the object box-shadow, so you can see where it will collide with other objects.
1157 if self._selectedObj is not None:
1159 glEnable(GL_CULL_FACE)
1160 glColor4f(0,0,0,0.16)
1162 for obj in self._scene.objects():
1164 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1165 glBegin(GL_TRIANGLE_FAN)
1166 for p in obj._boundaryHull[::-1]:
1167 glVertex3f(p[0], p[1], 0)
1171 glColor4f(0,0,0,0.06)
1172 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1173 glBegin(GL_TRIANGLE_FAN)
1174 for p in self._selectedObj._printAreaHull[::-1]:
1175 glVertex3f(p[0], p[1], 0)
1177 glBegin(GL_TRIANGLE_FAN)
1178 for p in self._selectedObj._headAreaHull[::-1]:
1179 glVertex3f(p[0], p[1], 0)
1182 glDisable(GL_CULL_FACE)
1185 #Draw the outline of the selected object, on top of everything else except the GUI.
1186 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1187 glDisable(GL_DEPTH_TEST)
1188 glEnable(GL_CULL_FACE)
1189 glEnable(GL_STENCIL_TEST)
1191 glStencilFunc(GL_EQUAL, 0, 255)
1193 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1195 glColor4f(1,1,1,0.5)
1196 self._renderObject(self._selectedObj)
1197 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1199 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1200 glDisable(GL_STENCIL_TEST)
1201 glDisable(GL_CULL_FACE)
1202 glEnable(GL_DEPTH_TEST)
1204 if self._selectedObj is not None:
1206 pos = self.getObjectCenterPos()
1207 glTranslate(pos[0], pos[1], pos[2])
1210 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1211 glDisable(GL_DEPTH_TEST)
1214 glTranslate(0,-4,-10)
1215 glColor4ub(60,60,60,255)
1216 opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1219 def _renderObject(self, obj, brightness = False, addSink = True):
1222 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1224 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1226 if self.tempMatrix is not None and obj == self._selectedObj:
1227 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1228 glMultMatrixf(tempMatrix)
1230 offset = obj.getDrawOffset()
1231 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1233 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1234 glMultMatrixf(tempMatrix)
1237 for m in obj._meshList:
1239 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1241 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1246 def _drawMachine(self):
1247 glEnable(GL_CULL_FACE)
1250 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1252 machine = profile.getMachineSetting('machine_type')
1253 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
1254 if machine not in self._platformMesh:
1255 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1257 self._platformMesh[machine] = meshes[0]
1259 self._platformMesh[machine] = None
1260 if machine == 'ultimaker2':
1261 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1263 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1264 glColor4f(1,1,1,0.5)
1265 self._objectShader.bind()
1266 self._renderObject(self._platformMesh[machine], False, False)
1267 self._objectShader.unbind()
1269 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1270 if machine == 'ultimaker2':
1271 if not hasattr(self._platformMesh[machine], 'texture'):
1272 self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1273 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1274 glEnable(GL_TEXTURE_2D)
1278 glTranslate(0,150,-5)
1283 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1286 glVertex3f( w, 0, h)
1288 glVertex3f(-w, 0, h)
1290 glVertex3f(-w, 0, 0)
1292 glVertex3f( w, 0, 0)
1295 glVertex3f(-w, d, h)
1297 glVertex3f( w, d, h)
1299 glVertex3f( w, d, 0)
1301 glVertex3f(-w, d, 0)
1303 glDisable(GL_TEXTURE_2D)
1304 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1310 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1311 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1312 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1313 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1314 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1315 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1318 #Cornerpoints for big blue square
1319 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1320 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1321 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1322 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1323 v4 = [ size[0] / 2, size[1] / 2, 0]
1324 v5 = [ size[0] / 2,-size[1] / 2, 0]
1325 v6 = [-size[0] / 2, size[1] / 2, 0]
1326 v7 = [-size[0] / 2,-size[1] / 2, 0]
1328 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1329 glEnableClientState(GL_VERTEX_ARRAY)
1330 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1333 glColor4ub(5, 171, 231, 64)
1334 glDrawArrays(GL_QUADS, 0, 4)
1335 glColor4ub(5, 171, 231, 96)
1336 glDrawArrays(GL_QUADS, 4, 8)
1337 glColor4ub(5, 171, 231, 128)
1338 glDrawArrays(GL_QUADS, 12, 8)
1339 glDisableClientState(GL_VERTEX_ARRAY)
1342 sx = self._machineSize[0]
1343 sy = self._machineSize[1]
1344 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1345 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1350 x1 = max(min(x1, sx/2), -sx/2)
1351 y1 = max(min(y1, sy/2), -sy/2)
1352 x2 = max(min(x2, sx/2), -sx/2)
1353 y2 = max(min(y2, sy/2), -sy/2)
1354 #Black or "white" checker
1355 if (x & 1) == (y & 1):
1356 glColor4ub(5, 171, 231, 127)
1358 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1360 glVertex3f(x1, y1, 0)
1361 glVertex3f(x2, y1, 0)
1362 glVertex3f(x2, y2, 0)
1363 glVertex3f(x1, y2, 0)
1366 if machine == 'ultimaker2':
1368 glColor4ub(127, 127, 127, 200)
1369 #if UM2, draw bat-area zone for head. THe head can't stop there, because its bat-area.
1373 posX = sx / 2 - clipWidth
1374 posY = sy / 2 - clipHeight
1376 glVertex3f(posX, posY, 0)
1377 glVertex3f(posX+clipWidth, posY, 0)
1378 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1379 glVertex3f(posX, posY+clipHeight, 0)
1385 posY = sy / 2 - clipHeight
1387 glVertex3f(posX, posY, 0)
1388 glVertex3f(posX+clipWidth, posY, 0)
1389 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1390 glVertex3f(posX, posY+clipHeight, 0)
1395 posX = sx / 2 - clipWidth
1398 glVertex3f(posX, posY, 0)
1399 glVertex3f(posX+clipWidth, posY, 0)
1400 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1401 glVertex3f(posX, posY+clipHeight, 0)
1409 glVertex3f(posX, posY, 0)
1410 glVertex3f(posX+clipWidth, posY, 0)
1411 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1412 glVertex3f(posX, posY+clipHeight, 0)
1417 glDisable(GL_CULL_FACE)
1419 def _generateGCodeVBOs(self, layer):
1421 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1422 if ':' in extrudeType:
1423 extruder = int(extrudeType[extrudeType.find(':')+1:])
1424 extrudeType = extrudeType[0:extrudeType.find(':')]
1427 pointList = numpy.zeros((0,3), numpy.float32)
1429 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1431 a = numpy.concatenate((a[:-1], a[1:]), 1)
1432 a = a.reshape((len(a) * 2, 3))
1433 pointList = numpy.concatenate((pointList, a))
1434 ret.append(opengl.GLVBO(pointList))
1437 def _generateGCodeVBOs2(self, layer):
1438 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1439 filamentArea = math.pi * filamentRadius * filamentRadius
1440 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1443 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1444 if ':' in extrudeType:
1445 extruder = int(extrudeType[extrudeType.find(':')+1:])
1446 extrudeType = extrudeType[0:extrudeType.find(':')]
1449 pointList = numpy.zeros((0,3), numpy.float32)
1451 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1453 if extrudeType == 'FILL':
1456 normal = a[1:] - a[:-1]
1457 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1458 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1461 ePerDist = path['extrusion'][1:] / lens
1463 lineWidth = ePerDist / path['layerThickness'] / 2.0
1465 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1467 normal[:,0] *= lineWidth
1468 normal[:,1] *= lineWidth
1470 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1471 b = numpy.concatenate((b, a[1:] + normal), 1)
1472 b = numpy.concatenate((b, a[1:] - normal), 1)
1473 b = numpy.concatenate((b, a[:-1] - normal), 1)
1474 b = numpy.concatenate((b, a[:-1] + normal), 1)
1475 b = b.reshape((len(b) * 4, 3))
1478 normal2 = normal[:-1] + normal[1:]
1479 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1480 normal2[:,0] /= lens2
1481 normal2[:,1] /= lens2
1482 normal2[:,0] *= lineWidth[:-1]
1483 normal2[:,1] *= lineWidth[:-1]
1485 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1486 c = numpy.concatenate((c, a[1:-1]), 1)
1487 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1488 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1489 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1491 c = numpy.concatenate((c, a[1:-1]), 1)
1492 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1493 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1494 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1496 c = c.reshape((len(c) * 8, 3))
1498 pointList = numpy.concatenate((pointList, b, c))
1500 pointList = numpy.concatenate((pointList, b))
1501 ret.append(opengl.GLVBO(pointList))
1503 pointList = numpy.zeros((0,3), numpy.float32)
1505 if path['type'] == 'move':
1506 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1507 a = numpy.concatenate((a[:-1], a[1:]), 1)
1508 a = a.reshape((len(a) * 2, 3))
1509 pointList = numpy.concatenate((pointList, a))
1510 if path['type'] == 'retract':
1511 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1512 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1513 a = a.reshape((len(a) * 2, 3))
1514 pointList = numpy.concatenate((pointList, a))
1515 ret.append(opengl.GLVBO(pointList))
1519 def getObjectCenterPos(self):
1520 if self._selectedObj is None:
1521 return [0.0, 0.0, 0.0]
1522 pos = self._selectedObj.getPosition()
1523 size = self._selectedObj.getSize()
1524 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1526 def getObjectBoundaryCircle(self):
1527 if self._selectedObj is None:
1529 return self._selectedObj.getBoundaryCircle()
1531 def getObjectSize(self):
1532 if self._selectedObj is None:
1533 return [0.0, 0.0, 0.0]
1534 return self._selectedObj.getSize()
1536 def getObjectMatrix(self):
1537 if self._selectedObj is None:
1538 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1539 return self._selectedObj.getMatrix()
1541 class shaderEditor(wx.Dialog):
1542 def __init__(self, parent, callback, v, f):
1543 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1544 self._callback = callback
1545 s = wx.BoxSizer(wx.VERTICAL)
1547 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1548 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1549 s.Add(self._vertex, 1, flag=wx.EXPAND)
1550 s.Add(self._fragment, 1, flag=wx.EXPAND)
1552 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1553 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1555 self.SetPosition(self.GetParent().GetPosition())
1556 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1559 def OnText(self, e):
1560 self._callback(self._vertex.GetValue(), self._fragment.GetValue())