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 if not self._scene.checkPlatform(obj):
591 self._scene.centerAll()
592 self._selectObject(obj)
593 if obj.getScale()[0] < 1.0:
594 self.notification.message("Warning: Object scaled down.")
597 def _deleteObject(self, obj):
598 if obj == self._selectedObj:
599 self._selectObject(None)
600 if obj == self._focusObj:
601 self._focusObj = None
602 self._scene.remove(obj)
603 for m in obj._meshList:
604 if m.vbo is not None and m.vbo.decRef():
605 self.glReleaseList.append(m.vbo)
610 def _selectObject(self, obj, zoom = True):
611 if obj != self._selectedObj:
612 self._selectedObj = obj
613 self.updateProfileToControls()
614 self.updateToolButtons()
615 if zoom and obj is not None:
616 newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
617 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
618 newZoom = obj.getBoundaryCircle() * 6
619 if newZoom > numpy.max(self._machineSize) * 3:
620 newZoom = numpy.max(self._machineSize) * 3
621 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
623 def updateProfileToControls(self):
624 oldSimpleMode = self._isSimpleMode
625 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
626 if self._isSimpleMode != oldSimpleMode:
627 self._scene.arrangeAll()
629 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
630 self._objColors[0] = profile.getPreferenceColour('model_colour')
631 self._objColors[1] = profile.getPreferenceColour('model_colour2')
632 self._objColors[2] = profile.getPreferenceColour('model_colour3')
633 self._objColors[3] = profile.getPreferenceColour('model_colour4')
634 self._scene.setMachineSize(self._machineSize)
635 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'))
637 if self._selectedObj is not None:
638 scale = self._selectedObj.getScale()
639 size = self._selectedObj.getSize()
640 self.scaleXctrl.setValue(round(scale[0], 2))
641 self.scaleYctrl.setValue(round(scale[1], 2))
642 self.scaleZctrl.setValue(round(scale[2], 2))
643 self.scaleXmmctrl.setValue(round(size[0], 2))
644 self.scaleYmmctrl.setValue(round(size[1], 2))
645 self.scaleZmmctrl.setValue(round(size[2], 2))
647 def OnKeyChar(self, keyCode):
648 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
649 if self._selectedObj is not None:
650 self._deleteObject(self._selectedObj)
652 if keyCode == wx.WXK_UP:
653 self.layerSelect.setValue(self.layerSelect.getValue() + 1)
655 elif keyCode == wx.WXK_DOWN:
656 self.layerSelect.setValue(self.layerSelect.getValue() - 1)
658 elif keyCode == wx.WXK_PAGEUP:
659 self.layerSelect.setValue(self.layerSelect.getValue() + 10)
661 elif keyCode == wx.WXK_PAGEDOWN:
662 self.layerSelect.setValue(self.layerSelect.getValue() - 10)
665 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
666 shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
667 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
668 from collections import defaultdict
669 from gc import get_objects
670 self._beforeLeakTest = defaultdict(int)
671 for i in get_objects():
672 self._beforeLeakTest[type(i)] += 1
673 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
674 from collections import defaultdict
675 from gc import get_objects
676 self._afterLeakTest = defaultdict(int)
677 for i in get_objects():
678 self._afterLeakTest[type(i)] += 1
679 for k in self._afterLeakTest:
680 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
681 print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
683 def ShaderUpdate(self, v, f):
684 s = opengl.GLShader(v, f)
686 self._objectLoadShader.release()
687 self._objectLoadShader = s
688 for obj in self._scene.objects():
689 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
692 def OnMouseDown(self,e):
693 self._mouseX = e.GetX()
694 self._mouseY = e.GetY()
695 self._mouseClick3DPos = self._mouse3Dpos
696 self._mouseClickFocus = self._focusObj
698 self._mouseState = 'doubleClick'
700 self._mouseState = 'dragOrClick'
701 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
702 p0 -= self.getObjectCenterPos() - self._viewTarget
703 p1 -= self.getObjectCenterPos() - self._viewTarget
704 if self.tool.OnDragStart(p0, p1):
705 self._mouseState = 'tool'
706 if self._mouseState == 'dragOrClick':
707 if e.GetButton() == 1:
708 if self._focusObj is not None:
709 self._selectObject(self._focusObj, False)
712 def OnMouseUp(self, e):
713 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
715 if self._mouseState == 'dragOrClick':
716 if e.GetButton() == 1:
717 self._selectObject(self._focusObj)
718 if e.GetButton() == 3:
720 if self._focusObj is not None:
721 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
722 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
723 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
724 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
725 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:
726 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
727 if len(self._scene.objects()) > 0:
728 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
729 if menu.MenuItemCount > 0:
732 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
733 self._scene.pushFree()
735 elif self._mouseState == 'tool':
736 if self.tempMatrix is not None and self._selectedObj is not None:
737 self._selectedObj.applyMatrix(self.tempMatrix)
738 self._scene.pushFree()
739 self._selectObject(self._selectedObj)
740 self.tempMatrix = None
741 self.tool.OnDragEnd()
743 self._mouseState = None
745 def OnMouseMotion(self,e):
746 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
747 p0 -= self.getObjectCenterPos() - self._viewTarget
748 p1 -= self.getObjectCenterPos() - self._viewTarget
750 if e.Dragging() and self._mouseState is not None:
751 if self._mouseState == 'tool':
752 self.tool.OnDrag(p0, p1)
753 elif not e.LeftIsDown() and e.RightIsDown():
754 self._mouseState = 'drag'
755 if wx.GetKeyState(wx.WXK_SHIFT):
756 a = math.cos(math.radians(self._yaw)) / 3.0
757 b = math.sin(math.radians(self._yaw)) / 3.0
758 self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
759 self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
760 self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
761 self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
763 self._yaw += e.GetX() - self._mouseX
764 self._pitch -= e.GetY() - self._mouseY
765 if self._pitch > 170:
769 elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
770 self._mouseState = 'drag'
771 self._zoom += e.GetY() - self._mouseY
774 if self._zoom > numpy.max(self._machineSize) * 3:
775 self._zoom = numpy.max(self._machineSize) * 3
776 elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
777 self._mouseState = 'dragObject'
778 z = max(0, self._mouseClick3DPos[2])
779 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
780 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
785 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
786 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
787 diff = cursorZ1 - cursorZ0
788 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
789 if not e.Dragging() or self._mouseState != 'tool':
790 self.tool.OnMouseMove(p0, p1)
792 self._mouseX = e.GetX()
793 self._mouseY = e.GetY()
795 def OnMouseWheel(self, e):
796 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
797 delta = max(min(delta,4),-4)
798 self._zoom *= 1.0 - delta / 10.0
801 if self._zoom > numpy.max(self._machineSize) * 3:
802 self._zoom = numpy.max(self._machineSize) * 3
805 def OnMouseLeave(self, e):
809 def getMouseRay(self, x, y):
810 if self._viewport is None:
811 return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
812 p0 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
813 p1 = opengl.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
814 p0 -= self._viewTarget
815 p1 -= self._viewTarget
818 def _init3DView(self):
819 # set viewing projection
820 size = self.GetSize()
821 glViewport(0, 0, size.GetWidth(), size.GetHeight())
824 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
826 glDisable(GL_RESCALE_NORMAL)
827 glDisable(GL_LIGHTING)
829 glEnable(GL_DEPTH_TEST)
830 glDisable(GL_CULL_FACE)
832 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
834 glClearColor(0.8, 0.8, 0.8, 1.0)
838 glMatrixMode(GL_PROJECTION)
840 aspect = float(size.GetWidth()) / float(size.GetHeight())
841 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
843 glMatrixMode(GL_MODELVIEW)
845 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
848 connectionEntry = self._printerConnectionManager.getAvailableConnection()
849 if machineCom.machineIsConnected():
850 self.printButton._imageID = 6
851 self.printButton._tooltip = _("Print")
852 elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
853 self.printButton._imageID = 2
854 self.printButton._tooltip = _("Toolpath to SD")
855 elif connectionEntry is not None:
856 self.printButton._imageID = connectionEntry.icon
857 self.printButton._tooltip = _("Print with %s") % (connectionEntry.name)
859 self.printButton._imageID = 3
860 self.printButton._tooltip = _("Save toolpath")
862 if self._animView is not None:
863 self._viewTarget = self._animView.getPosition()
864 if self._animView.isDone():
865 self._animView = None
866 if self._animZoom is not None:
867 self._zoom = self._animZoom.getPosition()
868 if self._animZoom.isDone():
869 self._animZoom = None
870 if self.viewMode == 'gcode' and self._gcode is not None:
872 self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
875 if self._objectShader is None:
876 if opengl.hasShaderSupport():
877 self._objectShader = opengl.GLShader("""
878 varying float light_amount;
882 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
883 gl_FrontColor = gl_Color;
885 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
889 varying float light_amount;
893 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
896 self._objectOverhangShader = opengl.GLShader("""
897 uniform float cosAngle;
898 uniform mat3 rotMatrix;
899 varying float light_amount;
903 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
904 gl_FrontColor = gl_Color;
906 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
908 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
910 light_amount = -10.0;
914 varying float light_amount;
918 if (light_amount == -10.0)
920 gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
922 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
926 self._objectLoadShader = opengl.GLShader("""
927 uniform float intensity;
929 varying float light_amount;
933 vec4 tmp = gl_Vertex;
934 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
935 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
936 gl_Position = gl_ModelViewProjectionMatrix * tmp;
937 gl_FrontColor = gl_Color;
939 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
943 uniform float intensity;
944 varying float light_amount;
948 gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
951 if self._objectShader is None or not self._objectShader.isValid():
952 self._objectShader = opengl.GLFakeShader()
953 self._objectOverhangShader = opengl.GLFakeShader()
954 self._objectLoadShader = None
956 glTranslate(0,0,-self._zoom)
957 glRotate(-self._pitch, 1,0,0)
958 glRotate(self._yaw, 0,0,1)
959 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
961 self._viewport = glGetIntegerv(GL_VIEWPORT)
962 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
963 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
965 glClearColor(1,1,1,1)
966 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
968 if self.viewMode != 'gcode':
969 for n in xrange(0, len(self._scene.objects())):
970 obj = self._scene.objects()[n]
971 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
972 self._renderObject(obj)
974 if self._mouseX > -1:
976 n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
977 if n < len(self._scene.objects()):
978 self._focusObj = self._scene.objects()[n]
980 self._focusObj = None
981 f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
982 #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
983 self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
984 self._mouse3Dpos -= self._viewTarget
987 glTranslate(0,0,-self._zoom)
988 glRotate(-self._pitch, 1,0,0)
989 glRotate(self._yaw, 0,0,1)
990 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
992 if self.viewMode == 'gcode':
993 if self._gcode is not None and self._gcode.layerList is None:
994 self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
995 self._gcodeLoadThread.daemon = True
996 self._gcodeLoadThread.start()
997 if self._gcode is not None and self._gcode.layerList is not None:
999 if profile.getMachineSetting('machine_center_is_zero') != 'True':
1000 glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
1002 drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
1003 for n in xrange(0, drawUpTill):
1004 c = 1.0 - float(drawUpTill - n) / 15
1006 if len(self._gcodeVBOs) < n + 1:
1007 self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
1008 if time.time() - t > 0.5:
1011 #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
1012 if n == drawUpTill - 1:
1013 if len(self._gcodeVBOs[n]) < 9:
1014 self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
1016 self._gcodeVBOs[n][8].render(GL_QUADS)
1017 glColor3f(c/2, 0, c)
1018 self._gcodeVBOs[n][9].render(GL_QUADS)
1019 glColor3f(0, c, c/2)
1020 self._gcodeVBOs[n][10].render(GL_QUADS)
1022 self._gcodeVBOs[n][11].render(GL_QUADS)
1025 self._gcodeVBOs[n][12].render(GL_QUADS)
1026 glColor3f(c/2, c/2, 0.0)
1027 self._gcodeVBOs[n][13].render(GL_QUADS)
1029 self._gcodeVBOs[n][14].render(GL_QUADS)
1030 self._gcodeVBOs[n][15].render(GL_QUADS)
1032 self._gcodeVBOs[n][16].render(GL_LINES)
1035 self._gcodeVBOs[n][0].render(GL_LINES)
1036 glColor3f(c/2, 0, c)
1037 self._gcodeVBOs[n][1].render(GL_LINES)
1038 glColor3f(0, c, c/2)
1039 self._gcodeVBOs[n][2].render(GL_LINES)
1041 self._gcodeVBOs[n][3].render(GL_LINES)
1044 self._gcodeVBOs[n][4].render(GL_LINES)
1045 glColor3f(c/2, c/2, 0.0)
1046 self._gcodeVBOs[n][5].render(GL_LINES)
1048 self._gcodeVBOs[n][6].render(GL_LINES)
1049 self._gcodeVBOs[n][7].render(GL_LINES)
1052 glStencilFunc(GL_ALWAYS, 1, 1)
1053 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1055 if self.viewMode == 'overhang':
1056 self._objectOverhangShader.bind()
1057 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
1059 self._objectShader.bind()
1060 for obj in self._scene.objects():
1061 if obj._loadAnim is not None:
1062 if obj._loadAnim.isDone():
1063 obj._loadAnim = None
1067 if self._focusObj == obj:
1069 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1072 if self._selectedObj == obj or self._selectedObj is None:
1073 #If we want transparent, then first render a solid black model to remove the printer size lines.
1074 if self.viewMode == 'transparent':
1075 glColor4f(0, 0, 0, 0)
1076 self._renderObject(obj)
1078 glBlendFunc(GL_ONE, GL_ONE)
1079 glDisable(GL_DEPTH_TEST)
1081 if self.viewMode == 'xray':
1082 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1083 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1084 glEnable(GL_STENCIL_TEST)
1086 if self.viewMode == 'overhang':
1087 if self._selectedObj == obj and self.tempMatrix is not None:
1088 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1090 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1092 if not self._scene.checkPlatform(obj):
1093 glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1094 self._renderObject(obj)
1096 self._renderObject(obj, brightness)
1097 glDisable(GL_STENCIL_TEST)
1099 glEnable(GL_DEPTH_TEST)
1100 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1102 if self.viewMode == 'xray':
1105 glEnable(GL_STENCIL_TEST)
1106 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
1107 glDisable(GL_DEPTH_TEST)
1108 for i in xrange(2, 15, 2):
1109 glStencilFunc(GL_EQUAL, i, 0xFF)
1110 glColor(float(i)/10, float(i)/10, float(i)/5)
1112 glVertex3f(-1000,-1000,-10)
1113 glVertex3f( 1000,-1000,-10)
1114 glVertex3f( 1000, 1000,-10)
1115 glVertex3f(-1000, 1000,-10)
1117 for i in xrange(1, 15, 2):
1118 glStencilFunc(GL_EQUAL, i, 0xFF)
1119 glColor(float(i)/10, 0, 0)
1121 glVertex3f(-1000,-1000,-10)
1122 glVertex3f( 1000,-1000,-10)
1123 glVertex3f( 1000, 1000,-10)
1124 glVertex3f(-1000, 1000,-10)
1127 glDisable(GL_STENCIL_TEST)
1128 glEnable(GL_DEPTH_TEST)
1130 self._objectShader.unbind()
1132 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1134 if self._objectLoadShader is not None:
1135 self._objectLoadShader.bind()
1136 glColor4f(0.2, 0.6, 1.0, 1.0)
1137 for obj in self._scene.objects():
1138 if obj._loadAnim is None:
1140 self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1141 self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1142 self._renderObject(obj)
1143 self._objectLoadShader.unbind()
1148 if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
1150 z = self._usbPrintMonitor.getZ()
1151 size = self._machineSize
1152 glColor4ub(255,255,0,128)
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)
1157 glVertex3f(-size[0]/2, size[1]/2, z)
1160 if self.viewMode == 'gcode':
1161 if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
1162 glDisable(GL_DEPTH_TEST)
1165 glTranslate(0,-4,-10)
1166 glColor4ub(60,60,60,255)
1167 opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
1170 #Draw the object box-shadow, so you can see where it will collide with other objects.
1171 if self._selectedObj is not None:
1173 glEnable(GL_CULL_FACE)
1174 glColor4f(0,0,0,0.16)
1176 for obj in self._scene.objects():
1178 glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1179 glBegin(GL_TRIANGLE_FAN)
1180 for p in obj._boundaryHull[::-1]:
1181 glVertex3f(p[0], p[1], 0)
1185 glColor4f(0,0,0,0.06)
1186 glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1187 glBegin(GL_TRIANGLE_FAN)
1188 for p in self._selectedObj._printAreaHull[::-1]:
1189 glVertex3f(p[0], p[1], 0)
1191 glBegin(GL_TRIANGLE_FAN)
1192 for p in self._selectedObj._headAreaMinHull[::-1]:
1193 glVertex3f(p[0], p[1], 0)
1196 glDisable(GL_CULL_FACE)
1199 #Draw the outline of the selected object, on top of everything else except the GUI.
1200 if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1201 glDisable(GL_DEPTH_TEST)
1202 glEnable(GL_CULL_FACE)
1203 glEnable(GL_STENCIL_TEST)
1205 glStencilFunc(GL_EQUAL, 0, 255)
1207 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1209 glColor4f(1,1,1,0.5)
1210 self._renderObject(self._selectedObj)
1211 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1213 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1214 glDisable(GL_STENCIL_TEST)
1215 glDisable(GL_CULL_FACE)
1216 glEnable(GL_DEPTH_TEST)
1218 if self._selectedObj is not None:
1220 pos = self.getObjectCenterPos()
1221 glTranslate(pos[0], pos[1], pos[2])
1224 if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
1225 glDisable(GL_DEPTH_TEST)
1228 glTranslate(0,-4,-10)
1229 glColor4ub(60,60,60,255)
1230 opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1233 def _renderObject(self, obj, brightness = False, addSink = True):
1236 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1238 glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1240 if self.tempMatrix is not None and obj == self._selectedObj:
1241 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
1242 glMultMatrixf(tempMatrix)
1244 offset = obj.getDrawOffset()
1245 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1247 tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
1248 glMultMatrixf(tempMatrix)
1251 for m in obj._meshList:
1253 m.vbo = opengl.GLVBO(m.vertexes, m.normal)
1255 glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
1260 def _drawMachine(self):
1261 glEnable(GL_CULL_FACE)
1264 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1266 machine = profile.getMachineSetting('machine_type')
1267 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
1268 if machine not in self._platformMesh:
1269 meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
1271 self._platformMesh[machine] = meshes[0]
1273 self._platformMesh[machine] = None
1274 if machine == 'ultimaker2':
1275 self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
1277 self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
1278 glColor4f(1,1,1,0.5)
1279 self._objectShader.bind()
1280 self._renderObject(self._platformMesh[machine], False, False)
1281 self._objectShader.unbind()
1283 #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1284 if machine == 'ultimaker2':
1285 if not hasattr(self._platformMesh[machine], 'texture'):
1286 self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
1287 glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
1288 glEnable(GL_TEXTURE_2D)
1292 glTranslate(0,150,-5)
1297 glBlendFunc(GL_DST_COLOR, GL_ZERO)
1300 glVertex3f( w, 0, h)
1302 glVertex3f(-w, 0, h)
1304 glVertex3f(-w, 0, 0)
1306 glVertex3f( w, 0, 0)
1309 glVertex3f(-w, d, h)
1311 glVertex3f( w, d, h)
1313 glVertex3f( w, d, 0)
1315 glVertex3f(-w, d, 0)
1317 glDisable(GL_TEXTURE_2D)
1318 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1324 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1325 glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1326 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1327 glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1328 glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1329 glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1332 #Cornerpoints for big blue square
1333 v0 = [ size[0] / 2, size[1] / 2, size[2]]
1334 v1 = [ size[0] / 2,-size[1] / 2, size[2]]
1335 v2 = [-size[0] / 2, size[1] / 2, size[2]]
1336 v3 = [-size[0] / 2,-size[1] / 2, size[2]]
1337 v4 = [ size[0] / 2, size[1] / 2, 0]
1338 v5 = [ size[0] / 2,-size[1] / 2, 0]
1339 v6 = [-size[0] / 2, size[1] / 2, 0]
1340 v7 = [-size[0] / 2,-size[1] / 2, 0]
1342 vList = [v0,v1,v3,v2, v1,v0,v4,v5, v2,v3,v7,v6, v0,v2,v6,v4, v3,v1,v5,v7]
1343 glEnableClientState(GL_VERTEX_ARRAY)
1344 glVertexPointer(3, GL_FLOAT, 3*4, vList)
1347 glColor4ub(5, 171, 231, 64)
1348 glDrawArrays(GL_QUADS, 0, 4)
1349 glColor4ub(5, 171, 231, 96)
1350 glDrawArrays(GL_QUADS, 4, 8)
1351 glColor4ub(5, 171, 231, 128)
1352 glDrawArrays(GL_QUADS, 12, 8)
1353 glDisableClientState(GL_VERTEX_ARRAY)
1356 sx = self._machineSize[0]
1357 sy = self._machineSize[1]
1358 for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
1359 for y in xrange(-int(sx/20)-1, int(sy / 20) + 1):
1364 x1 = max(min(x1, sx/2), -sx/2)
1365 y1 = max(min(y1, sy/2), -sy/2)
1366 x2 = max(min(x2, sx/2), -sx/2)
1367 y2 = max(min(y2, sy/2), -sy/2)
1368 #Black or "white" checker
1369 if (x & 1) == (y & 1):
1370 glColor4ub(5, 171, 231, 127)
1372 glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
1374 glVertex3f(x1, y1, 0)
1375 glVertex3f(x2, y1, 0)
1376 glVertex3f(x2, y2, 0)
1377 glVertex3f(x1, y2, 0)
1380 if machine == 'ultimaker2':
1382 glColor4ub(127, 127, 127, 200)
1383 #if UM2, draw bat-area zone for head. THe head can't stop there, because its bat-area.
1387 posX = sx / 2 - clipWidth
1388 posY = sy / 2 - clipHeight
1390 glVertex3f(posX, posY, 0)
1391 glVertex3f(posX+clipWidth, posY, 0)
1392 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1393 glVertex3f(posX, posY+clipHeight, 0)
1399 posY = sy / 2 - clipHeight
1401 glVertex3f(posX, posY, 0)
1402 glVertex3f(posX+clipWidth, posY, 0)
1403 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1404 glVertex3f(posX, posY+clipHeight, 0)
1409 posX = sx / 2 - clipWidth
1412 glVertex3f(posX, posY, 0)
1413 glVertex3f(posX+clipWidth, posY, 0)
1414 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1415 glVertex3f(posX, posY+clipHeight, 0)
1423 glVertex3f(posX, posY, 0)
1424 glVertex3f(posX+clipWidth, posY, 0)
1425 glVertex3f(posX+clipWidth, posY+clipHeight, 0)
1426 glVertex3f(posX, posY+clipHeight, 0)
1431 glDisable(GL_CULL_FACE)
1433 def _generateGCodeVBOs(self, layer):
1435 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1436 if ':' in extrudeType:
1437 extruder = int(extrudeType[extrudeType.find(':')+1:])
1438 extrudeType = extrudeType[0:extrudeType.find(':')]
1441 pointList = numpy.zeros((0,3), numpy.float32)
1443 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1445 a = numpy.concatenate((a[:-1], a[1:]), 1)
1446 a = a.reshape((len(a) * 2, 3))
1447 pointList = numpy.concatenate((pointList, a))
1448 ret.append(opengl.GLVBO(pointList))
1451 def _generateGCodeVBOs2(self, layer):
1452 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
1453 filamentArea = math.pi * filamentRadius * filamentRadius
1454 useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
1457 for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
1458 if ':' in extrudeType:
1459 extruder = int(extrudeType[extrudeType.find(':')+1:])
1460 extrudeType = extrudeType[0:extrudeType.find(':')]
1463 pointList = numpy.zeros((0,3), numpy.float32)
1465 if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
1467 if extrudeType == 'FILL':
1470 normal = a[1:] - a[:-1]
1471 lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
1472 normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
1475 ePerDist = path['extrusion'][1:] / lens
1477 lineWidth = ePerDist / path['layerThickness'] / 2.0
1479 lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
1481 normal[:,0] *= lineWidth
1482 normal[:,1] *= lineWidth
1484 b = numpy.zeros((len(a)-1, 0), numpy.float32)
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 = numpy.concatenate((b, a[:-1] + normal), 1)
1489 b = b.reshape((len(b) * 4, 3))
1492 normal2 = normal[:-1] + normal[1:]
1493 lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
1494 normal2[:,0] /= lens2
1495 normal2[:,1] /= lens2
1496 normal2[:,0] *= lineWidth[:-1]
1497 normal2[:,1] *= lineWidth[:-1]
1499 c = numpy.zeros((len(a)-2, 0), numpy.float32)
1500 c = numpy.concatenate((c, a[1:-1]), 1)
1501 c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
1502 c = numpy.concatenate((c, a[1:-1]+normal2), 1)
1503 c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
1505 c = numpy.concatenate((c, a[1:-1]), 1)
1506 c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
1507 c = numpy.concatenate((c, a[1:-1]-normal2), 1)
1508 c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
1510 c = c.reshape((len(c) * 8, 3))
1512 pointList = numpy.concatenate((pointList, b, c))
1514 pointList = numpy.concatenate((pointList, b))
1515 ret.append(opengl.GLVBO(pointList))
1517 pointList = numpy.zeros((0,3), numpy.float32)
1519 if path['type'] == 'move':
1520 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1521 a = numpy.concatenate((a[:-1], a[1:]), 1)
1522 a = a.reshape((len(a) * 2, 3))
1523 pointList = numpy.concatenate((pointList, a))
1524 if path['type'] == 'retract':
1525 a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
1526 a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
1527 a = a.reshape((len(a) * 2, 3))
1528 pointList = numpy.concatenate((pointList, a))
1529 ret.append(opengl.GLVBO(pointList))
1533 def getObjectCenterPos(self):
1534 if self._selectedObj is None:
1535 return [0.0, 0.0, 0.0]
1536 pos = self._selectedObj.getPosition()
1537 size = self._selectedObj.getSize()
1538 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1540 def getObjectBoundaryCircle(self):
1541 if self._selectedObj is None:
1543 return self._selectedObj.getBoundaryCircle()
1545 def getObjectSize(self):
1546 if self._selectedObj is None:
1547 return [0.0, 0.0, 0.0]
1548 return self._selectedObj.getSize()
1550 def getObjectMatrix(self):
1551 if self._selectedObj is None:
1552 return numpy.matrix([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1553 return self._selectedObj.getMatrix()
1555 class shaderEditor(wx.Dialog):
1556 def __init__(self, parent, callback, v, f):
1557 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1558 self._callback = callback
1559 s = wx.BoxSizer(wx.VERTICAL)
1561 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1562 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1563 s.Add(self._vertex, 1, flag=wx.EXPAND)
1564 s.Add(self._fragment, 1, flag=wx.EXPAND)
1566 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1567 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1569 self.SetPosition(self.GetParent().GetPosition())
1570 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1573 def OnText(self, e):
1574 self._callback(self._vertex.GetValue(), self._fragment.GetValue())