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.printerConnection import printerConnectionManager
29 from Cura.gui.util import previewTools
30 from Cura.gui.util import opengl
31 from Cura.gui.util import openglGui
32 from Cura.gui.tools import youmagineGui
33 from Cura.gui.tools import imageToMesh
35 class SceneView(openglGui.glGuiPanel):
36 def __init__(self, parent):
37 super(SceneView, self).__init__(parent)
42 self._scene = objectScene.Scene()
45 self._gcodeFilename = None
46 self._gcodeLoadThread = None
47 self._objectShader = None
48 self._objectLoadShader = None
50 self._selectedObj = None
51 self._objColors = [None,None,None,None]
54 self._mouseState = None
55 self._viewTarget = numpy.array([0,0,0], numpy.float32)
58 self._platformMesh = {}
59 self._isSimpleMode = True
60 self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
61 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
64 self._modelMatrix = None
65 self._projMatrix = None
66 self.tempMatrix = None
68 self.openFileButton = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
69 self.printButton = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
70 self.printButton.setDisabled(True)
73 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
74 self.scaleToolButton = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
75 self.mirrorToolButton = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
77 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
78 self.layFlatButton = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
80 self.resetScaleButton = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
81 self.scaleMaxButton = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
83 self.mirrorXButton = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
84 self.mirrorYButton = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
85 self.mirrorZButton = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
87 self.rotateToolButton.setExpandArrow(True)
88 self.scaleToolButton.setExpandArrow(True)
89 self.mirrorToolButton.setExpandArrow(True)
91 self.scaleForm = openglGui.glFrame(self, (2, -2))
92 openglGui.glGuiLayoutGrid(self.scaleForm)
93 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
94 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
95 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
96 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
97 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
98 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
99 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
100 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
101 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
102 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
103 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
104 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
105 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
106 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
108 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
109 self.layerSelect = openglGui.glSlider(self, 10000, 0, 1, (-1,-2), lambda : self.QueueRefresh())
111 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
112 self.youMagineButton.setDisabled(True)
114 self.notification = openglGui.glNotification(self, (0, 0))
116 self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
117 self._sceneUpdateTimer = wx.Timer(self)
118 self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
119 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
120 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
124 self.updateToolButtons()
125 self.updateProfileToControls()
127 def loadGCodeFile(self, filename):
128 self.OnDeleteAll(None)
129 if self._gcode is not None:
131 for layerVBOlist in self._gcodeVBOs:
132 for vbo in layerVBOlist:
133 self.glReleaseList.append(vbo)
135 self._gcode = gcodeInterpreter.gcode()
136 self._gcodeFilename = filename
137 self.printButton.setBottomText('')
138 self.viewSelection.setValue(4)
139 self.printButton.setDisabled(False)
140 self.youMagineButton.setDisabled(True)
143 def loadSceneFiles(self, filenames):
144 self.youMagineButton.setDisabled(False)
145 #if self.viewSelection.getValue() == 4:
146 # self.viewSelection.setValue(0)
147 # self.OnViewChange()
148 self.loadScene(filenames)
150 def loadFiles(self, filenames):
151 mainWindow = self.GetParent().GetParent().GetParent()
152 # only one GCODE file can be active
153 # so if single gcode file, process this
154 # otherwise ignore all gcode files
156 if len(filenames) == 1:
157 filename = filenames[0]
158 ext = os.path.splitext(filename)[1].lower()
159 if ext == '.g' or ext == '.gcode':
160 gcodeFilename = filename
161 mainWindow.addToModelMRU(filename)
162 if gcodeFilename is not None:
163 self.loadGCodeFile(gcodeFilename)
165 # process directories and special file types
166 # and keep scene files for later processing
168 ignored_types = dict()
169 # use file list as queue
170 # pop first entry for processing and append new files at end
172 filename = filenames.pop(0)
173 if os.path.isdir(filename):
174 # directory: queue all included files and directories
175 filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
177 ext = os.path.splitext(filename)[1].lower()
179 profile.loadProfile(filename)
180 mainWindow.addToProfileMRU(filename)
181 elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
182 scene_filenames.append(filename)
183 mainWindow.addToModelMRU(filename)
185 ignored_types[ext] = 1
187 ignored_types = ignored_types.keys()
189 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
190 mainWindow.updateProfileToAllControls()
191 # now process all the scene files
193 self.loadSceneFiles(scene_filenames)
194 self._selectObject(None)
196 newZoom = numpy.max(self._machineSize)
197 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
198 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
200 def showLoadModel(self, button = 1):
202 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)
203 dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
204 if dlg.ShowModal() != wx.ID_OK:
207 filenames = dlg.GetPaths()
209 if len(filenames) < 1:
211 profile.putPreference('lastFile', filenames[0])
212 self.loadFiles(filenames)
214 def showSaveModel(self):
215 if len(self._scene.objects()) < 1:
217 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
218 dlg.SetWildcard(meshLoader.saveWildcardFilter())
219 if dlg.ShowModal() != wx.ID_OK:
222 filename = dlg.GetPath()
224 meshLoader.saveMeshes(filename, self._scene.objects())
226 def OnPrintButton(self, button):
228 connectionEntry = self._printerConnectionManager.getAvailableConnection()
229 if machineCom.machineIsConnected():
230 self.showPrintWindow()
231 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
232 drives = removableStorage.getPossibleSDcardDrives()
234 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))
235 if dlg.ShowModal() != wx.ID_OK:
238 drive = drives[dlg.GetSelection()]
242 filename = self._scene._objectList[0].getName() + '.gcode'
243 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
244 elif connectionEntry is not None:
245 connection = connectionEntry.connection
246 if connectionEntry.window is None or not connectionEntry.window:
247 connectionEntry.window = printWindow2.printWindow(connection)
248 connectionEntry.window.Show()
249 connectionEntry.window.Raise()
250 if not connection.loadFile(self._gcodeFilename):
251 if connection.isPrinting():
252 self.notification.message("Cannot start print, because other print still running.")
254 self.notification.message("Failed to start print...")
259 self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
260 self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
261 self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
265 def showPrintWindow(self):
266 if self._gcodeFilename is None:
268 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
269 wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
271 self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
272 if self._gcodeFilename == self._slicer.getGCodeFilename():
273 self._slicer.submitSliceInfoOnline()
275 def showSaveGCode(self):
276 if len(self._scene._objectList) < 1:
278 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
279 filename = self._scene._objectList[0].getName() + '.gcode'
280 dlg.SetFilename(filename)
281 dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
282 if dlg.ShowModal() != wx.ID_OK:
285 filename = dlg.GetPath()
288 threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
290 def _copyFile(self, fileA, fileB, allowEject = False):
292 size = float(os.stat(fileA).st_size)
293 with open(fileA, 'rb') as fsrc:
294 with open(fileB, 'wb') as fdst:
296 buf = fsrc.read(16*1024)
300 self.printButton.setProgressBar(float(fsrc.tell()) / size)
305 self.notification.message("Failed to save")
308 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...'))
310 self.notification.message("Saved as %s" % (fileB))
311 self.printButton.setProgressBar(None)
312 if fileA == self._slicer.getGCodeFilename():
313 self._slicer.submitSliceInfoOnline()
315 def _showSliceLog(self):
316 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
320 def OnToolSelect(self, button):
321 if self.rotateToolButton.getSelected():
322 self.tool = previewTools.toolRotate(self)
323 elif self.scaleToolButton.getSelected():
324 self.tool = previewTools.toolScale(self)
325 elif self.mirrorToolButton.getSelected():
326 self.tool = previewTools.toolNone(self)
328 self.tool = previewTools.toolNone(self)
329 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
330 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
331 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
332 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
333 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
334 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
335 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
336 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
338 def updateToolButtons(self):
339 if self._selectedObj is None:
343 self.rotateToolButton.setHidden(hidden)
344 self.scaleToolButton.setHidden(hidden)
345 self.mirrorToolButton.setHidden(hidden)
347 self.rotateToolButton.setSelected(False)
348 self.scaleToolButton.setSelected(False)
349 self.mirrorToolButton.setSelected(False)
352 def OnViewChange(self):
353 if self.viewSelection.getValue() == 4:
354 self.viewMode = 'gcode'
355 if self._gcode is not None and self._gcode.layerList is not None:
356 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
357 self._selectObject(None)
358 elif self.viewSelection.getValue() == 1:
359 self.viewMode = 'overhang'
360 elif self.viewSelection.getValue() == 2:
361 self.viewMode = 'transparent'
362 elif self.viewSelection.getValue() == 3:
363 self.viewMode = 'xray'
365 self.viewMode = 'normal'
366 self.layerSelect.setHidden(self.viewMode != 'gcode')
369 def OnRotateReset(self, button):
370 if self._selectedObj is None:
372 self._selectedObj.resetRotation()
373 self._scene.pushFree()
374 self._selectObject(self._selectedObj)
377 def OnLayFlat(self, button):
378 if self._selectedObj is None:
380 self._selectedObj.layFlat()
381 self._scene.pushFree()
382 self._selectObject(self._selectedObj)
385 def OnScaleReset(self, button):
386 if self._selectedObj is None:
388 self._selectedObj.resetScale()
389 self._selectObject(self._selectedObj)
390 self.updateProfileToControls()
393 def OnScaleMax(self, button):
394 if self._selectedObj is None:
396 machine = profile.getMachineSetting('machine_type')
397 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
398 self._scene.pushFree()
400 if machine == "ultimaker2":
401 #This is bad and Jaime should feel bad!
402 self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
403 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
404 self._selectedObj.setPosition(numpy.array([0.0,0.0]))
405 self._scene.pushFree()
407 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
408 self._scene.pushFree()
409 self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
410 self._scene.pushFree()
411 self._selectObject(self._selectedObj)
412 self.updateProfileToControls()
415 def OnMirror(self, axis):
416 if self._selectedObj is None:
418 self._selectedObj.mirror(axis)
421 def OnScaleEntry(self, value, axis):
422 if self._selectedObj is None:
428 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
429 self.updateProfileToControls()
430 self._scene.pushFree()
431 self._selectObject(self._selectedObj)
434 def OnScaleEntryMM(self, value, axis):
435 if self._selectedObj is None:
441 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
442 self.updateProfileToControls()
443 self._scene.pushFree()
444 self._selectObject(self._selectedObj)
447 def OnDeleteAll(self, e):
448 while len(self._scene.objects()) > 0:
449 self._deleteObject(self._scene.objects()[0])
450 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
452 def OnMultiply(self, e):
453 if self._focusObj is None:
456 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
457 if dlg.ShowModal() != wx.ID_OK:
466 self._scene.add(newObj)
467 self._scene.centerAll()
468 if not self._scene.checkPlatform(newObj):
473 self.notification.message("Could not create more then %d items" % (n - 1))
474 self._scene.remove(newObj)
475 self._scene.centerAll()
478 def OnSplitObject(self, e):
479 if self._focusObj is None:
481 self._scene.remove(self._focusObj)
482 for obj in self._focusObj.split(self._splitCallback):
483 if numpy.max(obj.getSize()) > 2.0:
485 self._scene.centerAll()
486 self._selectObject(None)
489 def OnCenter(self, e):
490 if self._focusObj is None:
492 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
493 self._scene.pushFree()
494 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
495 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
498 def _splitCallback(self, progress):
501 def OnMergeObjects(self, e):
502 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
503 if len(self._scene.objects()) == 2:
504 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
507 self._scene.merge(self._selectedObj, self._focusObj)
510 def sceneUpdated(self):
511 self._sceneUpdateTimer.Start(500, True)
512 self._slicer.abortSlicer()
513 self._scene.updateSizeOffsets()
516 def _onRunSlicer(self, e):
517 if self._isSimpleMode:
518 self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
519 self._slicer.runSlicer(self._scene)
520 if self._isSimpleMode:
521 profile.resetTempOverride()
523 def _updateSliceProgress(self, progressValue, ready):
525 if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
527 self.printButton.setDisabled(not ready)
528 if progressValue >= 0.0:
529 self.printButton.setProgressBar(progressValue)
531 self.printButton.setProgressBar(None)
532 if self._gcode is not None:
534 for layerVBOlist in self._gcodeVBOs:
535 for vbo in layerVBOlist:
536 self.glReleaseList.append(vbo)
539 self.printButton.setProgressBar(None)
540 text = '%s' % (self._slicer.getPrintTime())
541 for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
542 amount = self._slicer.getFilamentAmount(e)
545 text += '\n%s' % (amount)
546 cost = self._slicer.getFilamentCost(e)
548 text += '\n%s' % (cost)
549 self.printButton.setBottomText(text)
550 self._gcode = gcodeInterpreter.gcode()
551 self._gcodeFilename = self._slicer.getGCodeFilename()
553 self.printButton.setBottomText('')
556 def _loadGCode(self):
557 self._gcode.progressCallback = self._gcodeLoadCallback
558 self._gcode.load(self._gcodeFilename)
560 def _gcodeLoadCallback(self, progress):
561 if not self or self._gcode is None:
563 if len(self._gcode.layerList) % 15 == 0:
565 if self._gcode is None:
567 self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
568 if self.viewMode == 'gcode':
572 def loadScene(self, fileList):
573 for filename in fileList:
575 ext = os.path.splitext(filename)[1].lower()
576 if ext in imageToMesh.supportedExtensions():
577 imageToMesh.convertImageDialog(self, filename).Show()
580 objList = meshLoader.loadMeshes(filename)
582 traceback.print_exc()
585 if self._objectLoadShader is not None:
586 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
590 self._scene.centerAll()
591 self._selectObject(obj)
592 if obj.getScale()[0] < 1.0:
593 self.notification.message("Warning: Object scaled down.")
596 def _deleteObject(self, obj):
597 if obj == self._selectedObj:
598 self._selectObject(None)
599 if obj == self._focusObj:
600 self._focusObj = None
601 self._scene.remove(obj)
602 for m in obj._meshList:
603 if m.vbo is not None and m.vbo.decRef():
604 self.glReleaseList.append(m.vbo)
609 def _selectObject(self, obj, zoom = True):
610 if obj != self._selectedObj:
611 self._selectedObj = obj
612 self.updateProfileToControls()
613 self.updateToolButtons()
614 if zoom and obj is not None:
615 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
616 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
617 newZoom = obj.getBoundaryCircle() * 6
618 if newZoom > numpy.max(self._machineSize) * 3:
619 newZoom = numpy.max(self._machineSize) * 3
620 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
622 def updateProfileToControls(self):
623 oldSimpleMode = self._isSimpleMode
624 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
625 if self._isSimpleMode != oldSimpleMode:
626 self._scene.arrangeAll()
628 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
629 self._objColors[0] = profile.getPreferenceColour('model_colour')
630 self._objColors[1] = profile.getPreferenceColour('model_colour2')
631 self._objColors[2] = profile.getPreferenceColour('model_colour3')
632 self._objColors[3] = profile.getPreferenceColour('model_colour4')
633 self._scene.setMachineSize(self._machineSize)
634 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'))
636 if self._selectedObj is not None:
637 scale = self._selectedObj.getScale()
638 size = self._selectedObj.getSize()
639 self.scaleXctrl.setValue(round(scale[0], 2))
640 self.scaleYctrl.setValue(round(scale[1], 2))
641 self.scaleZctrl.setValue(round(scale[2], 2))
642 self.scaleXmmctrl.setValue(round(size[0], 2))
643 self.scaleYmmctrl.setValue(round(size[1], 2))
644 self.scaleZmmctrl.setValue(round(size[2], 2))
646 def OnKeyChar(self, keyCode):
647 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
648 if self._selectedObj is not None:
649 self._deleteObject(self._selectedObj)
651 if keyCode == wx.WXK_UP:
652 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
654 elif keyCode == wx.WXK_DOWN:
655 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
657 elif keyCode == wx.WXK_PAGEUP:
658 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
660 elif keyCode == wx.WXK_PAGEDOWN:
661 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
664 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
665 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
666 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
667 from collections import defaultdict
668 from gc import get_objects
669 self._beforeLeakTest = defaultdict(int)
670 for i in get_objects():
671 self._beforeLeakTest[type(i)] += 1
672 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
673 from collections import defaultdict
674 from gc import get_objects
675 self._afterLeakTest = defaultdict(int)
676 for i in get_objects():
677 self._afterLeakTest[type(i)] += 1
678 for k in self._afterLeakTest:
679 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
680 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
682 def ShaderUpdate(self, v, f):
683 s = opengl.GLShader(v, f)
685 self._objectLoadShader.release()
686 self._objectLoadShader = s
687 for obj in self._scene.objects():
688 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
691 def OnMouseDown(self,e):
692 self._mouseX = e.GetX()
693 self._mouseY = e.GetY()
694 self._mouseClick3DPos = self._mouse3Dpos
695 self._mouseClickFocus = self._focusObj
697 self._mouseState = 'doubleClick'
699 self._mouseState = 'dragOrClick'
700 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
701 p0 -= self.getObjectCenterPos() - self._viewTarget
702 p1 -= self.getObjectCenterPos() - self._viewTarget
703 if self.tool.OnDragStart(p0, p1):
704 self._mouseState = 'tool'
705 if self._mouseState == 'dragOrClick':
706 if e.GetButton() == 1:
707 if self._focusObj is not None:
708 self._selectObject(self._focusObj, False)
711 def OnMouseUp(self, e):
712 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
714 if self._mouseState == 'dragOrClick':
715 if e.GetButton() == 1:
716 self._selectObject(self._focusObj)
717 if e.GetButton() == 3:
719 if self._focusObj is not None:
720 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
721 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
722 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
723 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
724 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:
725 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
726 if len(self._scene.objects()) > 0:
727 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
728 if menu.MenuItemCount > 0:
731 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
732 self._scene.pushFree()
734 elif self._mouseState == 'tool':
735 if self.tempMatrix is not None and self._selectedObj is not None:
736 self._selectedObj.applyMatrix(self.tempMatrix)
737 self._scene.pushFree()
738 self._selectObject(self._selectedObj)
739 self.tempMatrix = None
740 self.tool.OnDragEnd()
742 self._mouseState = None
744 def OnMouseMotion(self,e):
745 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
746 p0 -= self.getObjectCenterPos() - self._viewTarget
747 p1 -= self.getObjectCenterPos() - self._viewTarget
749 if e.Dragging() and self._mouseState is not None:
750 if self._mouseState == 'tool':
751 self.tool.OnDrag(p0, p1)
752 elif not e.LeftIsDown() and e.RightIsDown():
753 self._mouseState = 'drag'
754 if wx.GetKeyState(wx.WXK_SHIFT):
755 a = math.cos(math.radians(self._yaw)) / 3.0
756 b = math.sin(math.radians(self._yaw)) / 3.0
757 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
758 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
759 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
760 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
762 self._yaw += e.GetX() - self._mouseX
763 self._pitch -= e.GetY() - self._mouseY
764 if self._pitch > 170:
768 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
769 self._mouseState = 'drag'
770 self._zoom += e.GetY() - self._mouseY
773 if self._zoom > numpy.max(self._machineSize) * 3:
774 self._zoom = numpy.max(self._machineSize) * 3
775 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
776 self._mouseState = 'dragObject'
777 z = max(0, self._mouseClick3DPos[2])
778 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
779 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
784 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
785 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
786 diff = cursorZ1 - cursorZ0
787 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
788 if not e.Dragging() or self._mouseState != 'tool':
789 self.tool.OnMouseMove(p0, p1)
791 self._mouseX = e.GetX()
792 self._mouseY = e.GetY()
794 def OnMouseWheel(self, e):
795 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
796 delta = max(min(delta,4),-4)
797 self._zoom *= 1.0 - delta / 10.0
800 if self._zoom > numpy.max(self._machineSize) * 3:
801 self._zoom = numpy.max(self._machineSize) * 3
804 def OnMouseLeave(self, e):
808 def getMouseRay(self, x, y):
809 if self._viewport is None:
810 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
811 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
812 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
813 p0 -= self._viewTarget
814 p1 -= self._viewTarget
817 def _init3DView(self):
818 # set viewing projection
819 size = self.GetSize()
820 glViewport(0, 0, size.GetWidth(), size.GetHeight())
823 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
825 glDisable(GL_RESCALE_NORMAL)
826 glDisable(GL_LIGHTING)
828 glEnable(GL_DEPTH_TEST)
829 glDisable(GL_CULL_FACE)
831 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
833 glClearColor(0.8, 0.8, 0.8, 1.0)
837 glMatrixMode(GL_PROJECTION)
839 aspect = float(size.GetWidth()) / float(size.GetHeight())
840 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
842 glMatrixMode(GL_MODELVIEW)
844 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
847 connectionEntry = self._printerConnectionManager.getAvailableConnection()
848 if machineCom.machineIsConnected():
849 self.printButton._imageID = 6
850 self.printButton._tooltip = _("Print")
851 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
852 self.printButton._imageID = 2
853 self.printButton._tooltip = _("Toolpath to SD")
854 elif connectionEntry is not None:
855 self.printButton._imageID = connectionEntry.icon
856 self.printButton._tooltip = _("Print with %s") % (connectionEntry.name)
858 self.printButton._imageID = 3
859 self.printButton._tooltip = _("Save toolpath")
861 if self._animView is not None:
862 self._viewTarget = self._animView.getPosition()
863 if self._animView.isDone():
864 self._animView = None
865 if self._animZoom is not None:
866 self._zoom = self._animZoom.getPosition()
867 if self._animZoom.isDone():
868 self._animZoom = None
869 if self.viewMode == 'gcode' and self._gcode is not None:
871 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
874 if self._objectShader is None:
875 if opengl.hasShaderSupport():
876 self._objectShader = opengl.GLShader("""
877 varying float light_amount;
881 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
882 gl_FrontColor = gl_Color;
884 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
888 varying float light_amount;
892 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
895 self._objectOverhangShader = opengl.GLShader("""
896 uniform float cosAngle;
897 uniform mat3 rotMatrix;
898 varying float light_amount;
902 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
903 gl_FrontColor = gl_Color;
905 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
907 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
909 light_amount = -10.0;
913 varying float light_amount;
917 if (light_amount == -10.0)
919 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
921 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
925 self._objectLoadShader = opengl.GLShader("""
926 uniform float intensity;
928 varying float light_amount;
932 vec4 tmp = gl_Vertex;
933 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
934 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
935 gl_Position = gl_ModelViewProjectionMatrix * tmp;
936 gl_FrontColor = gl_Color;
938 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
942 uniform float intensity;
943 varying float light_amount;
947 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
950 if self._objectShader is None or not self._objectShader.isValid():
951 self._objectShader = opengl.GLFakeShader()
952 self._objectOverhangShader = opengl.GLFakeShader()
953 self._objectLoadShader = None
955 glTranslate(0,0,-self._zoom)
956 glRotate(-self._pitch, 1,0,0)
957 glRotate(self._yaw, 0,0,1)
958 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
960 self._viewport = glGetIntegerv(GL_VIEWPORT)
961 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
962 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
964 glClearColor(1,1,1,1)
965 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
967 if self.viewMode != 'gcode':
968 for n in xrange(0, len(self._scene.objects())):
969 obj = self._scene.objects()[n]
970 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
971 self._renderObject(obj)
973 if self._mouseX > -1:
975 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
976 if n < len(self._scene.objects()):
977 self._focusObj = self._scene.objects()[n]
979 self._focusObj = None
980 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
981 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
982 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
983 self._mouse3Dpos -= self._viewTarget
986 glTranslate(0,0,-self._zoom)
987 glRotate(-self._pitch, 1,0,0)
988 glRotate(self._yaw, 0,0,1)
989 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
991 if self.viewMode == 'gcode':
992 if self._gcode is not None and self._gcode.layerList is None:
993 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
994 self._gcodeLoadThread.daemon = True
995 self._gcodeLoadThread.start()
996 if self._gcode is not None and self._gcode.layerList is not None:
998 if profile.getMachineSetting('machine_center_is_zero') != 'True':
999 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
1001 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
1002 for n in xrange(0, drawUpTill):
1003 c = 1.0 - float(drawUpTill - n) / 15
1005 if len(self._gcodeVBOs) < n + 1:
1006 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
1007 if time.time() - t > 0.5:
1010 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
1011 if n == drawUpTill - 1:
1012 if len(self._gcodeVBOs[n]) < 9:
1013 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
1015 self._gcodeVBOs[n][8].render(GL_QUADS)
1016 glColor3f(c/2, 0, c)
1017 self._gcodeVBOs[n][9].render(GL_QUADS)
1018 glColor3f(0, c, c/2)
1019 self._gcodeVBOs[n][10].render(GL_QUADS)
1021 self._gcodeVBOs[n][11].render(GL_QUADS)
1024 self._gcodeVBOs[n][12].render(GL_QUADS)
1025 glColor3f(c/2, c/2, 0.0)
1026 self._gcodeVBOs[n][13].render(GL_QUADS)
1028 self._gcodeVBOs[n][14].render(GL_QUADS)
1029 self._gcodeVBOs[n][15].render(GL_QUADS)
1031 self._gcodeVBOs[n][16].render(GL_LINES)
1034 self._gcodeVBOs[n][0].render(GL_LINES)
1035 glColor3f(c/2, 0, c)
1036 self._gcodeVBOs[n][1].render(GL_LINES)
1037 glColor3f(0, c, c/2)
1038 self._gcodeVBOs[n][2].render(GL_LINES)
1040 self._gcodeVBOs[n][3].render(GL_LINES)
1043 self._gcodeVBOs[n][4].render(GL_LINES)
1044 glColor3f(c/2, c/2, 0.0)
1045 self._gcodeVBOs[n][5].render(GL_LINES)
1047 self._gcodeVBOs[n][6].render(GL_LINES)
1048 self._gcodeVBOs[n][7].render(GL_LINES)
1051 glStencilFunc(GL_ALWAYS, 1, 1)
1052 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1054 if self.viewMode == 'overhang':
1055 self._objectOverhangShader.bind()
1056 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1058 self._objectShader.bind()
1059 for obj in self._scene.objects():
1060 if obj._loadAnim is not None:
1061 if obj._loadAnim.isDone():
1062 obj._loadAnim = None
1066 if self._focusObj == obj:
1068 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1071 if self._selectedObj == obj or self._selectedObj is None:
1072 #If we want transparent, then first render a solid black model to remove the printer size lines.
1073 if self.viewMode == 'transparent':
1074 glColor4f(0, 0, 0, 0)
1075 self._renderObject(obj)
1077 glBlendFunc(GL_ONE, GL_ONE)
1078 glDisable(GL_DEPTH_TEST)
1080 if self.viewMode == 'xray':
1081 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1082 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1083 glEnable(GL_STENCIL_TEST)
1085 if self.viewMode == 'overhang':
1086 if self._selectedObj == obj and self.tempMatrix is not None:
1087 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1089 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1091 if not self._scene.checkPlatform(obj):
1092 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1093 self._renderObject(obj)
1095 self._renderObject(obj, brightness)
1096 glDisable(GL_STENCIL_TEST)
1098 glEnable(GL_DEPTH_TEST)
1099 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1101 if self.viewMode == 'xray':
1104 glEnable(GL_STENCIL_TEST)
1105 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1106 glDisable(GL_DEPTH_TEST)
1107 for i in xrange(2, 15, 2):
1108 glStencilFunc(GL_EQUAL, i, 0xFF)
1109 glColor(float(i)/10, float(i)/10, float(i)/5)
1111 glVertex3f(-1000,-1000,-10)
1112 glVertex3f( 1000,-1000,-10)
1113 glVertex3f( 1000, 1000,-10)
1114 glVertex3f(-1000, 1000,-10)
1116 for i in xrange(1, 15, 2):
1117 glStencilFunc(GL_EQUAL, i, 0xFF)
1118 glColor(float(i)/10, 0, 0)
1120 glVertex3f(-1000,-1000,-10)
1121 glVertex3f( 1000,-1000,-10)
1122 glVertex3f( 1000, 1000,-10)
1123 glVertex3f(-1000, 1000,-10)
1126 glDisable(GL_STENCIL_TEST)
1127 glEnable(GL_DEPTH_TEST)
1129 self._objectShader.unbind()
1131 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1133 if self._objectLoadShader is not None:
1134 self._objectLoadShader.bind()
1135 glColor4f(0.2, 0.6, 1.0, 1.0)
1136 for obj in self._scene.objects():
1137 if obj._loadAnim is None:
1139 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1140 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1141 self._renderObject(obj)
1142 self._objectLoadShader.unbind()
1147 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1149 z = self._usbPrintMonitor.getZ()
1150 size = self._machineSize
1151 glColor4ub(255,255,0,128)
1153 glVertex3f(-size[0]/2,-size[1]/2, z)
1154 glVertex3f( size[0]/2,-size[1]/2, z)
1155 glVertex3f( size[0]/2, size[1]/2, z)
1156 glVertex3f(-size[0]/2, size[1]/2, z)
1159 if self.viewMode == 'gcode':
1160 if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1161 glDisable(GL_DEPTH_TEST)
1164 glTranslate(0,-4,-10)
1165 glColor4ub(60,60,60,255)
1166 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1169 #Draw the object box-shadow, so you can see where it will collide with other objects.
1170 if self._selectedObj is not None:
1172 glEnable(GL_CULL_FACE)
1173 glColor4f(0,0,0,0.16)
1175 for obj in self._scene.objects():
1177 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1178 glBegin(GL_TRIANGLE_FAN)
1179 for p in obj._boundaryHull[::-1]:
1180 glVertex3f(p[0], p[1], 0)
1184 glColor4f(0,0,0,0.06)
1185 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1186 glBegin(GL_TRIANGLE_FAN)
1187 for p in self._selectedObj._printAreaHull[::-1]:
1188 glVertex3f(p[0], p[1], 0)
1190 glBegin(GL_TRIANGLE_FAN)
1191 for p in self._selectedObj._headAreaHull[::-1]:
1192 glVertex3f(p[0], p[1], 0)
1195 glDisable(GL_CULL_FACE)
1198 #Draw the outline of the selected object, on top of everything else except the GUI.
1199 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1200 glDisable(GL_DEPTH_TEST)
1201 glEnable(GL_CULL_FACE)
1202 glEnable(GL_STENCIL_TEST)
1204 glStencilFunc(GL_EQUAL, 0, 255)
1206 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1208 glColor4f(1,1,1,0.5)
1209 self._renderObject(self._selectedObj)
1210 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1212 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1213 glDisable(GL_STENCIL_TEST)
1214 glDisable(GL_CULL_FACE)
1215 glEnable(GL_DEPTH_TEST)
1217 if self._selectedObj is not None:
1219 pos = self.getObjectCenterPos()
1220 glTranslate(pos[0], pos[1], pos[2])
1223 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1224 glDisable(GL_DEPTH_TEST)
1227 glTranslate(0,-4,-10)
1228 glColor4ub(60,60,60,255)
1229 opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1232 def _renderObject(self, obj, brightness = False, addSink = True):
1235 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1237 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1239 if self.tempMatrix is not None and obj == self._selectedObj:
1240 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1241 glMultMatrixf(tempMatrix)
1243 offset = obj.getDrawOffset()
1244 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1246 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1247 glMultMatrixf(tempMatrix)
1250 for m in obj._meshList:
1252 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1254 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1259 def _drawMachine(self):
1260 glEnable(GL_CULL_FACE)
1263 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1265 machine = profile.getMachineSetting('machine_type')
1266 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
1267 if machine not in self._platformMesh:
1268 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1270 self._platformMesh[machine] = meshes[0]
1272 self._platformMesh[machine] = None
1273 if machine == 'ultimaker2':
1274 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1276 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1277 glColor4f(1,1,1,0.5)
1278 self._objectShader.bind()
1279 self._renderObject(self._platformMesh[machine], False, False)
1280 self._objectShader.unbind()
1282 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1283 if machine == 'ultimaker2':
1284 if not hasattr(self._platformMesh[machine], 'texture'):
1285 self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1286 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1287 glEnable(GL_TEXTURE_2D)
1291 glTranslate(0,150,-5)
1296 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1299 glVertex3f( w, 0, h)
1301 glVertex3f(-w, 0, h)
1303 glVertex3f(-w, 0, 0)
1305 glVertex3f( w, 0, 0)
1308 glVertex3f(-w, d, h)
1310 glVertex3f( w, d, h)
1312 glVertex3f( w, d, 0)
1314 glVertex3f(-w, d, 0)
1316 glDisable(GL_TEXTURE_2D)
1317 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1323 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1324 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1325 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1326 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1327 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1328 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1331 #Cornerpoints for big blue square
1332 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1333 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1334 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1335 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1336 v4 = [ size[0] / 2, size[1] / 2, 0]
1337 v5 = [ size[0] / 2,-size[1] / 2, 0]
1338 v6 = [-size[0] / 2, size[1] / 2, 0]
1339 v7 = [-size[0] / 2,-size[1] / 2, 0]
1341 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1342 glEnableClientState(GL_VERTEX_ARRAY)
1343 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1346 glColor4ub(5, 171, 231, 64)
1347 glDrawArrays(GL_QUADS, 0, 4)
1348 glColor4ub(5, 171, 231, 96)
1349 glDrawArrays(GL_QUADS, 4, 8)
1350 glColor4ub(5, 171, 231, 128)
1351 glDrawArrays(GL_QUADS, 12, 8)
1352 glDisableClientState(GL_VERTEX_ARRAY)
1355 sx = self._machineSize[0]
1356 sy = self._machineSize[1]
1357 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1358 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1363 x1 = max(min(x1, sx/2), -sx/2)
1364 y1 = max(min(y1, sy/2), -sy/2)
1365 x2 = max(min(x2, sx/2), -sx/2)
1366 y2 = max(min(y2, sy/2), -sy/2)
1367 #Black or "white" checker
1368 if (x & 1) == (y & 1):
1369 glColor4ub(5, 171, 231, 127)
1371 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1373 glVertex3f(x1, y1, 0)
1374 glVertex3f(x2, y1, 0)
1375 glVertex3f(x2, y2, 0)
1376 glVertex3f(x1, y2, 0)
1379 if machine == 'ultimaker2':
1381 glColor4ub(127, 127, 127, 200)
1382 #if UM2, draw bat-area zone for head. THe head can't stop there, because its bat-area.
1386 posX = sx / 2 - clipWidth
1387 posY = sy / 2 - clipHeight
1389 glVertex3f(posX, posY, 0)
1390 glVertex3f(posX+clipWidth, posY, 0)
1391 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1392 glVertex3f(posX, posY+clipHeight, 0)
1398 posY = sy / 2 - clipHeight
1400 glVertex3f(posX, posY, 0)
1401 glVertex3f(posX+clipWidth, posY, 0)
1402 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1403 glVertex3f(posX, posY+clipHeight, 0)
1408 posX = sx / 2 - clipWidth
1411 glVertex3f(posX, posY, 0)
1412 glVertex3f(posX+clipWidth, posY, 0)
1413 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1414 glVertex3f(posX, posY+clipHeight, 0)
1422 glVertex3f(posX, posY, 0)
1423 glVertex3f(posX+clipWidth, posY, 0)
1424 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1425 glVertex3f(posX, posY+clipHeight, 0)
1430 glDisable(GL_CULL_FACE)
1432 def _generateGCodeVBOs(self, layer):
1434 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1435 if ':' in extrudeType:
1436 extruder = int(extrudeType[extrudeType.find(':')+1:])
1437 extrudeType = extrudeType[0:extrudeType.find(':')]
1440 pointList = numpy.zeros((0,3), numpy.float32)
1442 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1444 a = numpy.concatenate((a[:-1], a[1:]), 1)
1445 a = a.reshape((len(a) * 2, 3))
1446 pointList = numpy.concatenate((pointList, a))
1447 ret.append(opengl.GLVBO(pointList))
1450 def _generateGCodeVBOs2(self, layer):
1451 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1452 filamentArea = math.pi * filamentRadius * filamentRadius
1453 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1456 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1457 if ':' in extrudeType:
1458 extruder = int(extrudeType[extrudeType.find(':')+1:])
1459 extrudeType = extrudeType[0:extrudeType.find(':')]
1462 pointList = numpy.zeros((0,3), numpy.float32)
1464 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1466 if extrudeType == 'FILL':
1469 normal = a[1:] - a[:-1]
1470 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1471 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1474 ePerDist = path['extrusion'][1:] / lens
1476 lineWidth = ePerDist / path['layerThickness'] / 2.0
1478 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1480 normal[:,0] *= lineWidth
1481 normal[:,1] *= lineWidth
1483 b = numpy.zeros((len(a)-1, 0), numpy.float32)
1484 b = numpy.concatenate((b, a[1:] + normal), 1)
1485 b = numpy.concatenate((b, a[1:] - normal), 1)
1486 b = numpy.concatenate((b, a[:-1] - normal), 1)
1487 b = numpy.concatenate((b, a[:-1] + normal), 1)
1488 b = b.reshape((len(b) * 4, 3))
1491 normal2 = normal[:-1] + normal[1:]
1492 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1493 normal2[:,0] /= lens2
1494 normal2[:,1] /= lens2
1495 normal2[:,0] *= lineWidth[:-1]
1496 normal2[:,1] *= lineWidth[:-1]
1498 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1499 c = numpy.concatenate((c, a[1:-1]), 1)
1500 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1501 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1502 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1504 c = numpy.concatenate((c, a[1:-1]), 1)
1505 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1506 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1507 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1509 c = c.reshape((len(c) * 8, 3))
1511 pointList = numpy.concatenate((pointList, b, c))
1513 pointList = numpy.concatenate((pointList, b))
1514 ret.append(opengl.GLVBO(pointList))
1516 pointList = numpy.zeros((0,3), numpy.float32)
1518 if path['type'] == 'move':
1519 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1520 a = numpy.concatenate((a[:-1], a[1:]), 1)
1521 a = a.reshape((len(a) * 2, 3))
1522 pointList = numpy.concatenate((pointList, a))
1523 if path['type'] == 'retract':
1524 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1525 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1526 a = a.reshape((len(a) * 2, 3))
1527 pointList = numpy.concatenate((pointList, a))
1528 ret.append(opengl.GLVBO(pointList))
1532 def getObjectCenterPos(self):
1533 if self._selectedObj is None:
1534 return [0.0, 0.0, 0.0]
1535 pos = self._selectedObj.getPosition()
1536 size = self._selectedObj.getSize()
1537 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1539 def getObjectBoundaryCircle(self):
1540 if self._selectedObj is None:
1542 return self._selectedObj.getBoundaryCircle()
1544 def getObjectSize(self):
1545 if self._selectedObj is None:
1546 return [0.0, 0.0, 0.0]
1547 return self._selectedObj.getSize()
1549 def getObjectMatrix(self):
1550 if self._selectedObj is None:
1551 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1552 return self._selectedObj.getMatrix()
1554 class shaderEditor(wx.Dialog):
1555 def __init__(self, parent, callback, v, f):
1556 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1557 self._callback = callback
1558 s = wx.BoxSizer(wx.VERTICAL)
1560 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1561 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1562 s.Add(self._vertex, 1, flag=wx.EXPAND)
1563 s.Add(self._fragment, 1, flag=wx.EXPAND)
1565 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1566 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1568 self.SetPosition(self.GetParent().GetPosition())
1569 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1572 def OnText(self, e):
1573 self._callback(self._vertex.GetValue(), self._fragment.GetValue())