1 from __future__ import absolute_import
2 from __future__ import division
13 from wx import glcanvas
16 OpenGL.ERROR_CHECKING = False
17 from OpenGL.GLU import *
18 from OpenGL.GL import *
20 from Cura.util import profile
21 from Cura.util import gcodeInterpreter
22 from Cura.util import meshLoader
23 from Cura.util import util3d
24 from Cura.util import sliceRun
26 from Cura.gui.util import opengl
27 from Cura.gui.util import previewTools
28 from Cura.gui.util import openglGui
30 class previewObject():
34 self.displayList = None
37 class previewPanel(wx.Panel):
38 def __init__(self, parent):
39 super(previewPanel, self).__init__(parent,-1)
41 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
42 self.SetMinSize((440,320))
47 self.objectsMinV = None
48 self.objectsMaxV = None
49 self.objectsBoundaryCircleSize = None
50 self.loadThread = None
51 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
52 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
54 self.glCanvas = PreviewGLCanvas(self)
55 #Create the popup window
56 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
57 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
58 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
59 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
60 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
61 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
62 self.warningPopup.SetSizer(self.warningPopup.sizer)
63 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
64 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
65 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
66 self.warningPopup.Fit()
67 self.warningPopup.Layout()
68 self.warningPopup.timer = wx.Timer(self)
69 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
71 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
72 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
73 parent.Bind(wx.EVT_MOVE, self.OnMove)
74 parent.Bind(wx.EVT_SIZE, self.OnMove)
76 sizer = wx.BoxSizer(wx.VERTICAL)
77 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
80 self.checkReloadFileTimer = wx.Timer(self)
81 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
82 self.checkReloadFileTimer.Start(1000)
85 self.rotateToolButton = openglGui.glRadioButton(self.glCanvas, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
86 self.scaleToolButton = openglGui.glRadioButton(self.glCanvas, 9, 'Scale', (1,-1), group, self.OnToolSelect)
87 self.mirrorToolButton = openglGui.glRadioButton(self.glCanvas, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
89 self.resetRotationButton = openglGui.glButton(self.glCanvas, 12, 'Reset', (0,-2), self.OnRotateReset)
90 self.layFlatButton = openglGui.glButton(self.glCanvas, 16, 'Lay flat', (0,-3), self.OnLayFlat)
92 self.resetScaleButton = openglGui.glButton(self.glCanvas, 13, 'Reset', (1,-2), self.OnScaleReset)
93 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 17, 'To max', (1,-3), self.OnScaleMax)
95 self.mirrorXButton = openglGui.glButton(self.glCanvas, 14, 'Mirror X', (2,-2), lambda : self.OnMirror(0))
96 self.mirrorYButton = openglGui.glButton(self.glCanvas, 18, 'Mirror Y', (2,-3), lambda : self.OnMirror(1))
97 self.mirrorZButton = openglGui.glButton(self.glCanvas, 22, 'Mirror Z', (2,-4), lambda : self.OnMirror(2))
99 self.openFileButton = openglGui.glButton(self.glCanvas, 4, 'Load', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
100 self.sliceButton = openglGui.glButton(self.glCanvas, 5, 'Prepare', (1,0), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
101 self.printButton = openglGui.glButton(self.glCanvas, 6, 'Print', (2,0), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
103 self.rotateToolButton.setExpandArrow(True)
104 self.scaleToolButton.setExpandArrow(True)
105 self.mirrorToolButton.setExpandArrow(True)
107 extruderCount = int(profile.getPreference('extruder_amount'))
108 if extruderCount > 1:
109 openglGui.glButton(self.glCanvas, 4, 'Load dual', (0,1), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
110 if extruderCount > 2:
111 openglGui.glButton(self.glCanvas, 4, 'Load triple', (0,2), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
112 if extruderCount > 3:
113 openglGui.glButton(self.glCanvas, 4, 'Load quad', (0,3), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
115 self.scaleForm = openglGui.glFrame(self.glCanvas, (2, -2))
116 openglGui.glGuiLayoutGrid(self.scaleForm)
117 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
118 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
119 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
120 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
121 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
122 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
123 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
124 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
125 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
126 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
127 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
128 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
129 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
130 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
132 self.viewSelection = openglGui.glComboButton(self.glCanvas, 'View mode', [7,11,15,19,23], ['Normal', 'Transparent', 'X-Ray', 'Overhang', 'Layers'], (-1,0), self.OnViewChange)
133 self.layerSelect = openglGui.glSlider(self.glCanvas, 0, 0, 100, (-1,-2), lambda : self.Refresh())
137 self.updateModelTransform()
139 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
141 def OnToolSelect(self):
142 if self.rotateToolButton.getSelected():
143 self.tool = previewTools.toolRotate(self.glCanvas)
144 elif self.scaleToolButton.getSelected():
145 self.tool = previewTools.toolScale(self.glCanvas)
146 elif self.mirrorToolButton.getSelected():
147 self.tool = previewTools.toolNone(self.glCanvas)
149 self.tool = previewTools.toolNone(self.glCanvas)
150 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
151 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
152 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
153 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
154 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
155 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
156 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
157 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
158 self.updateModelTransform()
160 def OnScaleEntry(self, value, axis):
165 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
166 scale = value / scale
169 if self.scaleUniform.getValue():
170 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
172 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
173 matrix[axis][axis] = scale
174 self.matrix *= numpy.matrix(matrix, numpy.float64)
175 self.updateModelTransform()
177 def OnScaleEntryMM(self, value, axis):
182 scale = self.objectsSize[axis]
183 scale = value / scale
186 if self.scaleUniform.getValue():
187 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
189 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
190 matrix[axis][axis] = scale
191 self.matrix *= numpy.matrix(matrix, numpy.float64)
192 self.updateModelTransform()
194 def OnMirror(self, axis):
195 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
196 matrix[axis][axis] = -1
197 self.matrix *= numpy.matrix(matrix, numpy.float64)
198 for obj in self.objectList:
200 obj.steepDirty = True
201 self.updateModelTransform()
203 def OnMove(self, e = None):
206 x, y = self.glCanvas.ClientToScreenXY(0, 0)
207 sx, sy = self.glCanvas.GetClientSizeTuple()
208 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
210 def OnScaleMax(self):
211 if self.objectsMinV is None:
213 vMin = self.objectsMinV
214 vMax = self.objectsMaxV
216 if profile.getProfileSettingFloat('skirt_line_count') > 0:
217 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
218 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
219 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
220 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
221 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
222 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
223 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
224 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
225 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
226 self.setViewMode('Normal')
227 self.updateModelTransform()
229 def OnRotateReset(self):
230 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
231 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
232 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
233 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
234 for obj in self.objectList:
236 obj.steepDirty = True
237 self.updateModelTransform()
239 def OnScaleReset(self):
240 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
241 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
242 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
243 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
244 for obj in self.objectList:
245 obj.steepDirty = True
246 self.updateModelTransform()
249 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
250 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
253 for v in transformedVertexes:
254 diff = v - minZvertex
255 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
258 dot = (diff[2] / len)
264 rad = -math.atan2(dotV[1], dotV[0])
265 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
266 rad = -math.asin(dotMin)
267 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
270 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
271 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
274 for v in transformedVertexes:
275 diff = v - minZvertex
276 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
279 dot = (diff[2] / len)
286 rad = math.asin(dotMin)
288 rad = -math.asin(dotMin)
289 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
291 for obj in self.objectList:
292 obj.steepDirty = True
293 self.updateModelTransform()
295 def setViewMode(self, mode):
297 self.viewSelection.setValue(0)
299 self.viewSelection.setValue(4)
300 wx.CallAfter(self.glCanvas.Refresh)
302 def loadModelFiles(self, filelist, showWarning = False):
303 while len(filelist) > len(self.objectList):
304 self.objectList.append(previewObject())
305 for idx in xrange(len(filelist), len(self.objectList)):
306 self.objectList[idx].mesh = None
307 self.objectList[idx].filename = None
308 for idx in xrange(0, len(filelist)):
309 obj = self.objectList[idx]
310 if obj.filename != filelist[idx]:
312 self.gcodeFileTime = None
313 self.logFileTime = None
315 obj.filename = filelist[idx]
318 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
320 #Do the STL file loading in a background thread so we don't block the UI.
321 if self.loadThread is not None and self.loadThread.isAlive():
322 self.abortLoading = True
323 self.loadThread.join()
324 self.abortLoading = False
325 self.loadThread = threading.Thread(target=self.doFileLoadThread)
326 self.loadThread.daemon = True
327 self.loadThread.start()
330 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
331 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
333 def OnCheckReloadFile(self, e):
334 #Only show the reload popup when the window has focus, because the popup goes over other programs.
335 if self.GetParent().FindFocus() is None:
337 for obj in self.objectList:
338 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
339 self.checkReloadFileTimer.Stop()
340 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
341 if wx.TheClipboard.Open():
342 data = wx.TextDataObject()
343 if wx.TheClipboard.GetData(data):
344 data = data.GetText()
345 if re.match('^http://.*/.*$', data):
346 if data.endswith(tuple(meshLoader.loadSupportedExtensions())):
347 #Got an url on the clipboard with a model file.
349 wx.TheClipboard.Close()
351 def reloadModelFiles(self, filelist = None):
352 if filelist is not None:
353 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
354 for idx in xrange(0, len(filelist)):
355 if self.objectList[idx].filename != filelist[idx]:
359 for idx in xrange(0, len(self.objectList)):
360 filelist.append(self.objectList[idx].filename)
361 self.loadModelFiles(filelist)
364 def doFileLoadThread(self):
365 for obj in self.objectList:
366 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
367 obj.fileTime = os.stat(obj.filename).st_mtime
369 mesh = meshLoader.loadMeshes(obj.filename)[0]
371 traceback.print_exc(file=sys.stdout)
372 wx.CallAfter(self.ShowWarningPopup, 'Failed to load %s' % (obj.filename))
378 obj.steepDirty = True
379 self.updateModelTransform()
380 if self.objectsBoundaryCircleSize is not None:
381 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
383 wx.CallAfter(self.updateToolbar)
384 wx.CallAfter(self.glCanvas.Refresh)
385 elif obj.filename is None or not os.path.isfile(obj.filename):
389 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
390 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
391 self.gcodeDirty = True
392 self.gcode = gcodeInterpreter.gcode()
393 self.gcode.progressCallback = self.loadProgress
394 self.gcode.load(self.gcodeFilename)
397 for line in open(self.gcodeFilename, "rt"):
398 res = re.search(';Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line)
400 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
401 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
402 errorList.append([v1, v2])
403 self.errorList = errorList
405 wx.CallAfter(self.updateToolbar)
406 wx.CallAfter(self.glCanvas.Refresh)
407 elif not os.path.isfile(self.gcodeFilename):
409 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
411 def loadProgress(self, progress):
412 if self.gcode is None:
414 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
415 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
416 self.layerSelect.setValue(self.layerSelect.getMaxValue())
418 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
419 return self.abortLoading
421 def OnResetAll(self, e = None):
422 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
423 profile.setPluginConfig([])
424 self.updateProfileToControls()
426 def ShowWarningPopup(self, text, callback = None):
427 self.warningPopup.text.SetLabel(text)
428 self.warningPopup.callback = callback
430 self.warningPopup.yesButton.Show(False)
431 self.warningPopup.noButton.SetLabel('ok')
433 self.warningPopup.yesButton.Show(True)
434 self.warningPopup.noButton.SetLabel('no')
435 self.warningPopup.Fit()
436 self.warningPopup.Layout()
438 self.warningPopup.Show(True)
439 self.warningPopup.timer.Start(5000)
441 def OnWarningPopup(self, e):
442 self.warningPopup.Show(False)
443 self.warningPopup.timer.Stop()
444 self.warningPopup.callback()
446 def OnHideWarning(self, e):
447 self.warningPopup.Show(False)
448 self.warningPopup.timer.Stop()
450 def updateToolbar(self):
451 self.sliceButton.setDisabled(len(self.objectList) < 1 or self.objectList[0].mesh is None)
452 self.printButton.setDisabled(self.gcode is None)
453 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
454 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
455 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
456 if self.gcode is not None:
457 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
460 def OnViewChange(self):
461 selection = self.viewSelection.getValue()
462 self.glCanvas.drawSteepOverhang = False
463 self.glCanvas.drawBorders = False
465 self.glCanvas.viewMode = "Normal"
467 self.glCanvas.viewMode = "Transparent"
469 self.glCanvas.viewMode = "X-Ray"
471 self.glCanvas.viewMode = "Normal"
472 self.glCanvas.drawSteepOverhang = True
474 self.layerSelect.setValue(self.layerSelect.getMaxValue())
475 self.glCanvas.viewMode = "GCode"
477 self.glCanvas.viewMode = "Mixed"
478 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
481 self.glCanvas.Refresh()
483 def deselectTool(self):
484 self.rotateToolButton.setSelected(False)
485 self.scaleToolButton.setSelected(False)
486 self.mirrorToolButton.setSelected(False)
489 def updateModelTransform(self, f=0):
490 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
493 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
494 for obj in self.objectList:
497 obj.mesh.matrix = self.matrix
498 obj.mesh.processMatrix()
500 minV = self.objectList[0].mesh.getMinimum()
501 maxV = self.objectList[0].mesh.getMaximum()
502 objectsBoundaryCircleSize = self.objectList[0].mesh.getBoundaryCircle()
503 for obj in self.objectList:
507 minV = numpy.minimum(minV, obj.mesh.getMinimum())
508 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
509 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.getBoundaryCircle())
511 self.objectsMaxV = maxV
512 self.objectsMinV = minV
513 self.objectsSize = self.objectsMaxV - self.objectsMinV
514 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
516 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
517 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
518 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
519 self.scaleXctrl.setValue(round(scaleX, 2))
520 self.scaleYctrl.setValue(round(scaleY, 2))
521 self.scaleZctrl.setValue(round(scaleZ, 2))
522 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
523 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
524 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
526 self.glCanvas.Refresh()
528 def updateProfileToControls(self):
529 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
530 self.updateModelTransform()
531 for obj in self.objectList:
532 obj.steepDirty = True
533 self.glCanvas.updateProfileToControls()
535 class PreviewGLCanvas(openglGui.glGuiPanel):
536 def __init__(self, parent):
537 super(PreviewGLCanvas, self).__init__(parent)
538 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
543 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
545 self.gcodeDisplayList = []
546 self.gcodeQuickDisplayList = []
547 self.gcodeDisplayListMade = 0
548 self.gcodeQuickDisplayListMade = 0
549 self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]]
553 self.tempMatrix = None
556 def updateProfileToControls(self):
557 self.objColor[0] = profile.getPreferenceColour('model_colour')
558 self.objColor[1] = profile.getPreferenceColour('model_colour2')
559 self.objColor[2] = profile.getPreferenceColour('model_colour3')
560 self.objColor[3] = profile.getPreferenceColour('model_colour4')
562 def OnMouseMotion(self,e):
563 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
564 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
565 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
566 p0 -= self.viewTarget
567 p1 -= self.viewTarget
568 if not e.Dragging() or self.dragType != 'tool':
569 self.parent.tool.OnMouseMove(p0, p1)
574 if e.Dragging() and e.LeftIsDown():
575 if self.dragType == '':
576 #Define the drag type depending on the cursor position.
577 self.dragType = 'viewRotate'
578 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
579 if self.parent.tool.OnDragStart(p0, p1):
580 self.dragType = 'tool'
582 if self.dragType == 'viewRotate':
584 self.yaw += e.GetX() - self.oldX
585 self.pitch -= e.GetY() - self.oldY
591 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
592 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
593 elif self.dragType == 'tool':
594 self.parent.tool.OnDrag(p0, p1)
596 #Workaround for buggy ATI cards.
597 size = self.GetSizeTuple()
598 self.SetSize((size[0]+1, size[1]))
599 self.SetSize((size[0], size[1]))
602 if self.dragType != '':
603 if self.tempMatrix is not None:
604 self.parent.matrix *= self.tempMatrix
605 self.parent.updateModelTransform()
606 self.tempMatrix = None
607 for obj in self.parent.objectList:
608 obj.steepDirty = True
609 self.parent.tool.OnDragEnd()
611 if e.Dragging() and e.RightIsDown():
612 self.zoom += e.GetY() - self.oldY
620 def getObjectBoundaryCircle(self):
621 return self.parent.objectsBoundaryCircleSize
623 def getObjectSize(self):
624 return self.parent.objectsSize
626 def getObjectMatrix(self):
627 return self.parent.matrix
629 def getObjectCenterPos(self):
630 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
632 def OnMouseWheel(self,e):
633 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
640 def OnKeyChar(self, keycode):
641 if keycode == wx.WXK_UP:
642 self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() + 1)
644 elif keycode == wx.WXK_DOWN:
645 self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() - 1)
647 elif keycode == wx.WXK_PAGEUP:
648 self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() + 10)
650 elif keycode == wx.WXK_PAGEDOWN:
651 self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() - 10)
655 opengl.InitGL(self, self.view3D, self.zoom)
657 glTranslate(0,0,-self.zoom)
658 glTranslate(self.zoom/20.0,0,0)
659 glRotate(-self.pitch, 1,0,0)
660 glRotate(self.yaw, 0,0,1)
662 if self.viewMode == "GCode" or self.viewMode == "Mixed":
663 n = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue())
664 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
665 self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
667 if self.parent.objectsMaxV is not None:
668 self.viewTarget = self.getObjectCenterPos()
669 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
671 self.viewport = glGetIntegerv(GL_VIEWPORT)
672 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
673 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
677 if len(self.parent.objectList) > 0 and self.parent.objectList[0].mesh is None and self.parent.objectList[0].filename is not None:
678 glDisable(GL_DEPTH_TEST)
680 glColor3ub(255,255,255)
681 glTranslate(0, -3, -10)
682 opengl.glDrawStringCenter('Loading %s ...' % (os.path.basename(self.parent.objectList[0].filename)))
685 machineSize = self.parent.machineSize
687 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
688 if self.parent.gcodeDirty:
689 self.parent.gcodeDirty = False
690 self.gcodeDisplayListMade = 0
691 self.gcodeQuickDisplayListMade = 0
693 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
694 gcodeGenStartTime = time.time()
695 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeQuickDisplayListMade < len(self.parent.gcode.layerList):
696 if len(self.gcodeQuickDisplayList) == self.gcodeQuickDisplayListMade:
697 self.gcodeQuickDisplayList.append(glGenLists(1))
698 glNewList(self.gcodeQuickDisplayList[self.gcodeQuickDisplayListMade], GL_COMPILE)
699 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeQuickDisplayListMade], True)
701 self.gcodeQuickDisplayListMade += 1
702 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
703 if len(self.gcodeDisplayList) == self.gcodeDisplayListMade:
704 self.gcodeDisplayList.append(glGenLists(1))
705 glNewList(self.gcodeDisplayList[self.gcodeDisplayListMade], GL_COMPILE)
706 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade], False)
708 self.gcodeDisplayListMade += 1
709 wx.CallAfter(self.Refresh)
712 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
713 for obj in self.parent.objectList:
716 if obj.displayList is None:
717 obj.displayList = glGenLists(1)
718 obj.steepDisplayList = glGenLists(1)
719 obj.outlineDisplayList = glGenLists(1)
722 glNewList(obj.displayList, GL_COMPILE)
723 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
725 glNewList(obj.outlineDisplayList, GL_COMPILE)
726 opengl.DrawMeshOutline(obj.mesh)
729 if self.viewMode == "Mixed":
731 glColor3f(0.0,0.0,0.0)
732 self.drawModel(obj.displayList)
733 glColor3f(1.0,1.0,1.0)
734 glClear(GL_DEPTH_BUFFER_BIT)
738 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
740 if profile.getPreference('machine_center_is_zero') == 'True':
741 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
742 glEnable(GL_COLOR_MATERIAL)
743 glEnable(GL_LIGHTING)
744 drawQuickUpToLayer = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue() + 1)
745 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
747 for i in xrange(drawQuickUpToLayer - 1, -1, -1):
749 if i < self.parent.layerSelect.getValue():
750 c = 0.9 - (drawQuickUpToLayer - i) * 0.1
755 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
756 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
757 if self.gcodeDisplayListMade > i and drawUpToLayer - i < 15:
758 glCallList(self.gcodeDisplayList[i])
760 glCallList(self.gcodeQuickDisplayList[i])
762 glDisable(GL_LIGHTING)
763 glDisable(GL_COLOR_MATERIAL)
764 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
765 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
768 glColor3f(1.0,1.0,1.0)
770 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
771 for obj in self.parent.objectList:
775 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
776 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
777 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
778 #If we want transparent, then first render a solid black model to remove the printer size lines.
779 if self.viewMode != "Mixed":
781 glColor3f(0.0,0.0,0.0)
782 self.drawModel(obj.displayList)
783 glColor3f(1.0,1.0,1.0)
784 #After the black model is rendered, render the model again but now with lighting and no depth testing.
785 glDisable(GL_DEPTH_TEST)
786 glEnable(GL_LIGHTING)
788 glBlendFunc(GL_ONE, GL_ONE)
789 glEnable(GL_LIGHTING)
790 self.drawModel(obj.displayList)
791 glEnable(GL_DEPTH_TEST)
792 elif self.viewMode == "X-Ray":
793 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
794 glDisable(GL_LIGHTING)
795 glDisable(GL_DEPTH_TEST)
796 glEnable(GL_STENCIL_TEST)
797 glStencilFunc(GL_ALWAYS, 1, 1)
798 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
799 self.drawModel(obj.displayList)
800 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP)
802 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
803 glStencilFunc(GL_EQUAL, 0, 1)
805 self.drawModel(obj.displayList)
806 glStencilFunc(GL_EQUAL, 1, 1)
808 self.drawModel(obj.displayList)
812 for i in xrange(2, 15, 2):
813 glStencilFunc(GL_EQUAL, i, 0xFF);
814 glColor(float(i)/10, float(i)/10, float(i)/5)
816 glVertex3f(-1000,-1000,-1)
817 glVertex3f( 1000,-1000,-1)
818 glVertex3f( 1000, 1000,-1)
819 glVertex3f(-1000, 1000,-1)
821 for i in xrange(1, 15, 2):
822 glStencilFunc(GL_EQUAL, i, 0xFF);
823 glColor(float(i)/10, 0, 0)
825 glVertex3f(-1000,-1000,-1)
826 glVertex3f( 1000,-1000,-1)
827 glVertex3f( 1000, 1000,-1)
828 glVertex3f(-1000, 1000,-1)
832 glDisable(GL_STENCIL_TEST)
833 glEnable(GL_DEPTH_TEST)
835 #Fix the depth buffer
836 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
837 self.drawModel(obj.displayList)
838 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
839 elif self.viewMode == "Normal":
840 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
841 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
842 glEnable(GL_LIGHTING)
843 self.drawModel(obj.displayList)
845 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
846 glEnable(GL_DEPTH_TEST)
847 glDisable(GL_LIGHTING)
849 self.drawModel(obj.outlineDisplayList)
851 if self.drawSteepOverhang:
853 obj.steepDirty = False
854 glNewList(obj.steepDisplayList, GL_COMPILE)
855 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
857 glDisable(GL_LIGHTING)
859 self.drawModel(obj.steepDisplayList)
862 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
863 # glDisable(GL_LIGHTING)
864 # glDisable(GL_DEPTH_TEST)
865 # glDisable(GL_BLEND)
868 # for err in self.parent.errorList:
869 # glVertex3f(err[0].x, err[0].y, err[0].z)
870 # glVertex3f(err[1].x, err[1].y, err[1].z)
872 # glEnable(GL_DEPTH_TEST)
874 opengl.DrawMachine(machineSize)
876 #Draw the current selected tool
877 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
879 pos = self.getObjectCenterPos()
880 glTranslate(pos[0], pos[1], pos[2])
881 self.parent.tool.OnDraw()
884 def drawModel(self, displayList):
885 vMin = self.parent.objectsMinV
886 vMax = self.parent.objectsMaxV
889 offset = - vMin - (vMax - vMin) / 2
891 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
894 glTranslate(0, 0, self.parent.objectsSize[2]/2)
895 if self.tempMatrix is not None:
896 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
897 glMultMatrixf(tempMatrix)
898 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
899 glTranslate(offset[0], offset[1], -vMin[2])
900 glMultMatrixf(matrix)
901 glCallList(displayList)