1 from __future__ import absolute_import
2 from __future__ import division
11 from wx import glcanvas
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
18 from Cura.util import profile
19 from Cura.util import gcodeInterpreter
20 from Cura.util import meshLoader
21 from Cura.util import util3d
22 from Cura.util import sliceRun
24 from Cura.gui.util import opengl
25 from Cura.gui.util import toolbarUtil
26 from Cura.gui.util import previewTools
27 from Cura.gui.util import openglGui
29 class previewObject():
33 self.displayList = None
36 class previewPanel(wx.Panel):
37 def __init__(self, parent):
38 super(previewPanel, self).__init__(parent,-1)
40 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
41 self.SetMinSize((440,320))
46 self.objectsMinV = None
47 self.objectsMaxV = None
48 self.objectsBoundaryCircleSize = None
49 self.loadThread = None
50 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
51 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
53 self.glCanvas = PreviewGLCanvas(self)
54 #Create the popup window
55 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
56 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
57 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
58 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
59 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
60 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
61 self.warningPopup.SetSizer(self.warningPopup.sizer)
62 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
63 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
64 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
65 self.warningPopup.Fit()
66 self.warningPopup.Layout()
67 self.warningPopup.timer = wx.Timer(self)
68 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
70 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
71 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
72 parent.Bind(wx.EVT_MOVE, self.OnMove)
73 parent.Bind(wx.EVT_SIZE, self.OnMove)
75 self.toolbar = toolbarUtil.Toolbar(self)
78 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
79 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
80 self.toolbar.AddSeparator()
82 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
83 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
84 self.toolbar.AddSeparator()
87 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
88 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
89 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
90 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
91 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
92 self.toolbar.AddSeparator()
94 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
95 self.toolbar.AddControl(self.layerSpin)
96 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
100 sizer = wx.BoxSizer(wx.VERTICAL)
101 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
102 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
105 self.checkReloadFileTimer = wx.Timer(self)
106 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
107 self.checkReloadFileTimer.Start(1000)
109 self.rotateToolButton = openglGui.glButton(self.glCanvas, 1, 'Rotate', (0,1), self.OnRotateSelect)
110 self.scaleToolButton = openglGui.glButton(self.glCanvas, 2, 'Scale', (0,2), self.OnScaleSelect)
112 self.resetRotationButton = openglGui.glButton(self.glCanvas, 4, 'Reset rotation', (1,1), self.OnRotateReset)
113 self.layFlatButton = openglGui.glButton(self.glCanvas, 5, 'Lay flat', (1,2), self.OnLayFlat)
115 self.resetScaleButton = openglGui.glButton(self.glCanvas, 8, 'Scale reset', (1,1), self.OnScaleReset)
116 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 9, 'Scale to machine size', (1,2), self.OnScaleMax)
118 self.openFileButton = openglGui.glButton(self.glCanvas, 3, 'Load model', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
119 self.sliceButton = openglGui.glButton(self.glCanvas, 6, 'Prepare model', (0,-2), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
120 self.printButton = openglGui.glButton(self.glCanvas, 7, 'Print model', (0,-1), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
122 self.scaleForm = openglGui.glFrame(self.glCanvas, (1, 3))
123 openglGui.glGuiLayoutGrid(self.scaleForm)
124 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
125 self.scaleXctrl = openglGui.glTextCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
126 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
127 self.scaleYctrl = openglGui.glTextCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
128 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
129 self.scaleZctrl = openglGui.glTextCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
130 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
131 self.scaleXmmctrl = openglGui.glTextCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
132 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
133 self.scaleYmmctrl = openglGui.glTextCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
134 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
135 self.scaleZmmctrl = openglGui.glTextCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
136 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
137 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
138 self.scaleForm.setHidden(True)
140 self.returnToModelViewAndUpdateModel()
142 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
143 self.tool = previewTools.toolNone(self.glCanvas)
145 def returnToModelViewAndUpdateModel(self):
146 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
147 self.setViewMode('Normal')
148 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
149 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
150 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
151 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
152 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
153 self.updateModelTransform()
155 def OnRotateSelect(self):
156 if self.rotateToolButton.getSelected():
157 self.rotateToolButton.setSelected(False)
158 self.tool = previewTools.toolNone(self.glCanvas)
160 self.rotateToolButton.setSelected(True)
161 self.tool = previewTools.toolRotate(self.glCanvas)
162 self.scaleToolButton.setSelected(False)
163 self.returnToModelViewAndUpdateModel()
165 def OnScaleSelect(self):
166 self.rotateToolButton.setSelected(False)
167 if self.scaleToolButton.getSelected():
168 self.scaleToolButton.setSelected(False)
169 self.tool = previewTools.toolNone(self.glCanvas)
171 self.scaleToolButton.setSelected(True)
172 self.tool = previewTools.toolScale(self.glCanvas)
173 self.returnToModelViewAndUpdateModel()
175 def OnScaleEntry(self, value, axis):
180 scale = numpy.linalg.norm(self.matrix[axis].getA().flatten())
181 scale = value / scale
184 if self.scaleUniform.getValue():
185 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
187 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
188 matrix[axis][axis] = scale
189 self.matrix *= numpy.matrix(matrix, numpy.float64)
190 self.updateModelTransform()
192 def OnScaleEntryMM(self, value, axis):
197 scale = self.objectsSize[axis]
198 scale = value / scale
201 if self.scaleUniform.getValue():
202 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
204 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
205 matrix[axis][axis] = scale
206 self.matrix *= numpy.matrix(matrix, numpy.float64)
207 self.updateModelTransform()
209 def OnMove(self, e = None):
212 x, y = self.glCanvas.ClientToScreenXY(0, 0)
213 sx, sy = self.glCanvas.GetClientSizeTuple()
214 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
216 def OnScaleMax(self, onlyScaleDown = False):
217 if self.objectsMinV is None:
219 vMin = self.objectsMinV
220 vMax = self.objectsMaxV
222 if profile.getProfileSettingFloat('skirt_line_count') > 0:
223 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
224 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
225 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
226 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
227 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
228 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
229 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
230 if scale > 1.0 and onlyScaleDown:
232 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
233 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
234 self.setViewMode('Normal')
235 self.updateModelTransform()
237 def OnRotateReset(self):
238 x = numpy.linalg.norm(self.matrix[0].getA().flatten())
239 y = numpy.linalg.norm(self.matrix[1].getA().flatten())
240 z = numpy.linalg.norm(self.matrix[2].getA().flatten())
241 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
242 for obj in self.objectList:
243 obj.steepDirty = True
244 self.updateModelTransform()
246 def OnScaleReset(self):
247 x = 1/numpy.linalg.norm(self.matrix[0].getA().flatten())
248 y = 1/numpy.linalg.norm(self.matrix[1].getA().flatten())
249 z = 1/numpy.linalg.norm(self.matrix[2].getA().flatten())
250 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
251 for obj in self.objectList:
252 obj.steepDirty = True
253 self.updateModelTransform()
256 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
257 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
260 for v in transformedVertexes:
261 diff = v - minZvertex
262 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
265 dot = (diff[2] / len)
271 rad = -math.atan2(dotV[1], dotV[0])
272 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
273 rad = -math.asin(dotMin)
274 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
277 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
278 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
281 for v in transformedVertexes:
282 diff = v - minZvertex
283 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
286 dot = (diff[2] / len)
293 rad = math.asin(dotMin)
295 rad = -math.asin(dotMin)
296 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
298 for obj in self.objectList:
299 obj.steepDirty = True
300 self.updateModelTransform()
303 self.glCanvas.yaw = 30
304 self.glCanvas.pitch = 60
305 self.glCanvas.zoom = 300
306 self.glCanvas.view3D = True
307 self.glCanvas.Refresh()
309 def OnTopClick(self):
310 self.glCanvas.view3D = False
311 self.glCanvas.zoom = 100
312 self.glCanvas.offsetX = 0
313 self.glCanvas.offsetY = 0
314 self.glCanvas.Refresh()
316 def OnLayerNrChange(self, e):
317 self.glCanvas.Refresh()
319 def setViewMode(self, mode):
321 self.normalViewButton.SetValue(True)
323 self.gcodeViewButton.SetValue(True)
324 self.glCanvas.viewMode = mode
325 wx.CallAfter(self.glCanvas.Refresh)
327 def loadModelFiles(self, filelist, showWarning = False):
328 while len(filelist) > len(self.objectList):
329 self.objectList.append(previewObject())
330 for idx in xrange(len(filelist), len(self.objectList)):
331 self.objectList[idx].mesh = None
332 self.objectList[idx].filename = None
333 for idx in xrange(0, len(filelist)):
334 obj = self.objectList[idx]
335 if obj.filename != filelist[idx]:
337 self.gcodeFileTime = None
338 self.logFileTime = None
339 obj.filename = filelist[idx]
341 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
342 #Do the STL file loading in a background thread so we don't block the UI.
343 if self.loadThread is not None and self.loadThread.isAlive():
344 self.loadThread.join()
345 self.loadThread = threading.Thread(target=self.doFileLoadThread)
346 self.loadThread.daemon = True
347 self.loadThread.start()
350 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
351 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
353 def OnCheckReloadFile(self, e):
354 #Only show the reload popup when the window has focus, because the popup goes over other programs.
355 if self.GetParent().FindFocus() is None:
357 for obj in self.objectList:
358 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
359 self.checkReloadFileTimer.Stop()
360 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
362 def reloadModelFiles(self, filelist = None):
363 if filelist is not None:
364 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
365 for idx in xrange(0, len(filelist)):
366 if self.objectList[idx].filename != filelist[idx]:
370 for idx in xrange(0, len(self.objectList)):
371 filelist.append(self.objectList[idx].filename)
372 self.loadModelFiles(filelist)
375 def doFileLoadThread(self):
376 for obj in self.objectList:
377 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
378 obj.fileTime = os.stat(obj.filename).st_mtime
379 mesh = meshLoader.loadMesh(obj.filename)
382 obj.steepDirty = True
383 self.updateModelTransform()
384 self.OnScaleMax(True)
385 self.glCanvas.zoom = numpy.max(self.objectsSize) * 3.5
387 wx.CallAfter(self.updateToolbar)
388 wx.CallAfter(self.glCanvas.Refresh)
390 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
391 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
392 gcode = gcodeInterpreter.gcode()
393 gcode.progressCallback = self.loadProgress
394 gcode.load(self.gcodeFilename)
395 self.gcodeDirty = False
397 self.gcodeDirty = True
400 for line in open(self.gcodeFilename, "rt"):
401 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)
403 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
404 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
405 errorList.append([v1, v2])
406 self.errorList = errorList
408 wx.CallAfter(self.updateToolbar)
409 wx.CallAfter(self.glCanvas.Refresh)
410 elif not os.path.isfile(self.gcodeFilename):
412 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
414 def loadProgress(self, progress):
417 def OnResetAll(self, e = None):
418 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
419 profile.setPluginConfig([])
420 self.updateProfileToControls()
422 def ShowWarningPopup(self, text, callback = None):
423 self.warningPopup.text.SetLabel(text)
424 self.warningPopup.callback = callback
426 self.warningPopup.yesButton.Show(False)
427 self.warningPopup.noButton.SetLabel('ok')
429 self.warningPopup.yesButton.Show(True)
430 self.warningPopup.noButton.SetLabel('no')
431 self.warningPopup.Fit()
432 self.warningPopup.Layout()
434 self.warningPopup.Show(True)
435 self.warningPopup.timer.Start(5000)
437 def OnWarningPopup(self, e):
438 self.warningPopup.Show(False)
439 self.warningPopup.timer.Stop()
440 self.warningPopup.callback()
442 def OnHideWarning(self, e):
443 self.warningPopup.Show(False)
444 self.warningPopup.timer.Stop()
446 def updateToolbar(self):
447 self.gcodeViewButton.Show(self.gcode is not None)
448 self.mixedViewButton.Show(self.gcode is not None)
449 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
450 if self.gcode is not None:
451 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
452 self.toolbar.Realize()
455 def OnViewChange(self):
456 if self.normalViewButton.GetValue():
457 self.glCanvas.viewMode = "Normal"
458 elif self.transparentViewButton.GetValue():
459 self.glCanvas.viewMode = "Transparent"
460 elif self.xrayViewButton.GetValue():
461 self.glCanvas.viewMode = "X-Ray"
462 elif self.gcodeViewButton.GetValue():
463 self.layerSpin.SetValue(self.layerSpin.GetMax())
464 self.glCanvas.viewMode = "GCode"
465 elif self.mixedViewButton.GetValue():
466 self.glCanvas.viewMode = "Mixed"
467 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
468 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
470 self.glCanvas.Refresh()
472 def updateModelTransform(self, f=0):
473 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
476 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
477 for obj in self.objectList:
480 obj.mesh.matrix = self.matrix
481 obj.mesh.processMatrix()
483 minV = self.objectList[0].mesh.getMinimum()
484 maxV = self.objectList[0].mesh.getMaximum()
485 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
486 for obj in self.objectList:
490 minV = numpy.minimum(minV, obj.mesh.getMinimum())
491 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
492 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
494 self.objectsMaxV = maxV
495 self.objectsMinV = minV
496 self.objectsSize = self.objectsMaxV - self.objectsMinV
497 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
499 scaleX = numpy.linalg.norm(self.matrix[0].getA().flatten())
500 scaleY = numpy.linalg.norm(self.matrix[1].getA().flatten())
501 scaleZ = numpy.linalg.norm(self.matrix[2].getA().flatten())
502 self.scaleXctrl.setValue(round(scaleX, 2))
503 self.scaleYctrl.setValue(round(scaleY, 2))
504 self.scaleZctrl.setValue(round(scaleZ, 2))
505 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
506 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
507 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
509 self.glCanvas.Refresh()
511 def updateProfileToControls(self):
512 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
513 self.updateModelTransform()
514 for obj in self.objectList:
515 obj.steepDirty = True
516 self.glCanvas.updateProfileToControls()
518 class PreviewGLCanvas(openglGui.glGuiPanel):
519 def __init__(self, parent):
520 super(PreviewGLCanvas, self).__init__(parent)
521 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
526 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
528 self.gcodeDisplayList = None
529 self.gcodeDisplayListMade = None
530 self.gcodeDisplayListCount = 0
531 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]]
535 self.tempMatrix = None
538 def updateProfileToControls(self):
539 self.objColor[0] = profile.getPreferenceColour('model_colour')
540 self.objColor[1] = profile.getPreferenceColour('model_colour2')
541 self.objColor[2] = profile.getPreferenceColour('model_colour3')
542 self.objColor[3] = profile.getPreferenceColour('model_colour4')
544 def OnMouseMotion(self,e):
545 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
546 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
547 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
548 p0 -= self.viewTarget
549 p1 -= self.viewTarget
550 if not e.Dragging() or self.dragType != 'tool':
551 self.parent.tool.OnMouseMove(p0, p1)
556 if e.Dragging() and e.LeftIsDown():
557 if self.dragType == '':
558 #Define the drag type depending on the cursor position.
559 self.dragType = 'viewRotate'
560 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
561 if self.parent.tool.OnDragStart(p0, p1):
562 self.dragType = 'tool'
564 if self.dragType == 'viewRotate':
566 self.yaw += e.GetX() - self.oldX
567 self.pitch -= e.GetY() - self.oldY
573 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
574 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
575 elif self.dragType == 'tool':
576 self.parent.tool.OnDrag(p0, p1)
578 #Workaround for buggy ATI cards.
579 size = self.GetSizeTuple()
580 self.SetSize((size[0]+1, size[1]))
581 self.SetSize((size[0], size[1]))
584 if self.dragType != '':
585 if self.tempMatrix is not None:
586 self.parent.matrix *= self.tempMatrix
587 self.parent.updateModelTransform()
588 self.tempMatrix = None
589 for obj in self.parent.objectList:
590 obj.steepDirty = True
591 self.parent.tool.OnDragEnd()
593 if e.Dragging() and e.RightIsDown():
594 self.zoom += e.GetY() - self.oldY
602 def getObjectBoundaryCircle(self):
603 return self.parent.objectsBoundaryCircleSize
605 def getObjectSize(self):
606 return self.parent.objectsSize
608 def getObjectMatrix(self):
609 return self.parent.matrix
611 def getObjectCenterPos(self):
612 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2]
614 def OnMouseWheel(self,e):
615 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
623 opengl.InitGL(self, self.view3D, self.zoom)
625 glTranslate(0,0,-self.zoom)
626 glTranslate(self.zoom/20.0,0,0)
627 glRotate(-self.pitch, 1,0,0)
628 glRotate(self.yaw, 0,0,1)
630 if self.viewMode == "GCode" or self.viewMode == "Mixed":
631 if self.parent.gcode is not None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:
632 self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z
634 if self.parent.objectsMaxV is not None:
635 self.viewTarget = self.getObjectCenterPos()
636 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
638 self.viewport = glGetIntegerv(GL_VIEWPORT)
639 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
640 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
645 machineSize = self.parent.machineSize
647 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
648 if self.parent.gcodeDirty:
649 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
650 if self.gcodeDisplayList is not None:
651 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
652 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
653 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
654 self.parent.gcodeDirty = False
655 self.gcodeDisplayListMade = 0
657 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
658 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
659 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
661 self.gcodeDisplayListMade += 1
662 wx.CallAfter(self.Refresh)
665 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
666 for obj in self.parent.objectList:
669 if obj.displayList is None:
670 obj.displayList = glGenLists(1)
671 obj.steepDisplayList = glGenLists(1)
672 obj.outlineDisplayList = glGenLists(1)
675 glNewList(obj.displayList, GL_COMPILE)
676 opengl.DrawMesh(obj.mesh)
678 glNewList(obj.outlineDisplayList, GL_COMPILE)
679 opengl.DrawMeshOutline(obj.mesh)
682 if self.viewMode == "Mixed":
684 glColor3f(0.0,0.0,0.0)
685 self.drawModel(obj.displayList)
686 glColor3f(1.0,1.0,1.0)
687 glClear(GL_DEPTH_BUFFER_BIT)
691 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
693 if profile.getPreference('machine_center_is_zero') == 'True':
694 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
695 glEnable(GL_COLOR_MATERIAL)
696 glEnable(GL_LIGHTING)
697 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
698 starttime = time.time()
699 for i in xrange(drawUpToLayer - 1, -1, -1):
701 if i < self.parent.layerSpin.GetValue():
702 c = 0.9 - (drawUpToLayer - i) * 0.1
707 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
708 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
709 glCallList(self.gcodeDisplayList + i)
710 if time.time() - starttime > 0.1:
713 glDisable(GL_LIGHTING)
714 glDisable(GL_COLOR_MATERIAL)
715 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
716 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
719 glColor3f(1.0,1.0,1.0)
721 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
722 for obj in self.parent.objectList:
726 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
727 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
728 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
729 #If we want transparent, then first render a solid black model to remove the printer size lines.
730 if self.viewMode != "Mixed":
732 glColor3f(0.0,0.0,0.0)
733 self.drawModel(obj.displayList)
734 glColor3f(1.0,1.0,1.0)
735 #After the black model is rendered, render the model again but now with lighting and no depth testing.
736 glDisable(GL_DEPTH_TEST)
737 glEnable(GL_LIGHTING)
739 glBlendFunc(GL_ONE, GL_ONE)
740 glEnable(GL_LIGHTING)
741 self.drawModel(obj.displayList)
742 glEnable(GL_DEPTH_TEST)
743 elif self.viewMode == "X-Ray":
744 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
745 glDisable(GL_LIGHTING)
746 glDisable(GL_DEPTH_TEST)
747 glEnable(GL_STENCIL_TEST)
748 glStencilFunc(GL_ALWAYS, 1, 1)
749 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
750 self.drawModel(obj.displayList)
751 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
753 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
754 glStencilFunc(GL_EQUAL, 0, 1)
756 self.drawModel(obj.displayList)
757 glStencilFunc(GL_EQUAL, 1, 1)
759 self.drawModel(obj.displayList)
763 for i in xrange(2, 15, 2):
764 glStencilFunc(GL_EQUAL, i, 0xFF);
765 glColor(float(i)/10, float(i)/10, float(i)/5)
767 glVertex3f(-1000,-1000,-1)
768 glVertex3f( 1000,-1000,-1)
769 glVertex3f( 1000, 1000,-1)
770 glVertex3f(-1000, 1000,-1)
772 for i in xrange(1, 15, 2):
773 glStencilFunc(GL_EQUAL, i, 0xFF);
774 glColor(float(i)/10, 0, 0)
776 glVertex3f(-1000,-1000,-1)
777 glVertex3f( 1000,-1000,-1)
778 glVertex3f( 1000, 1000,-1)
779 glVertex3f(-1000, 1000,-1)
783 glDisable(GL_STENCIL_TEST)
784 glEnable(GL_DEPTH_TEST)
786 #Fix the depth buffer
787 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
788 self.drawModel(obj.displayList)
789 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
790 elif self.viewMode == "Normal":
791 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
792 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
793 glEnable(GL_LIGHTING)
794 self.drawModel(obj.displayList)
796 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
797 glEnable(GL_DEPTH_TEST)
798 glDisable(GL_LIGHTING)
800 self.drawModel(obj.outlineDisplayList)
802 if self.drawSteepOverhang:
804 obj.steepDirty = False
805 glNewList(obj.steepDisplayList, GL_COMPILE)
806 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
808 glDisable(GL_LIGHTING)
810 self.drawModel(obj.steepDisplayList)
813 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
814 # glDisable(GL_LIGHTING)
815 # glDisable(GL_DEPTH_TEST)
816 # glDisable(GL_BLEND)
819 # for err in self.parent.errorList:
820 # glVertex3f(err[0].x, err[0].y, err[0].z)
821 # glVertex3f(err[1].x, err[1].y, err[1].z)
823 # glEnable(GL_DEPTH_TEST)
825 opengl.DrawMachine(machineSize)
827 #Draw the current selected tool
828 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
830 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2]/2)
831 self.parent.tool.OnDraw()
834 def drawModel(self, displayList):
835 vMin = self.parent.objectsMinV
836 vMax = self.parent.objectsMaxV
837 offset = - vMin - (vMax - vMin) / 2
839 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
842 glTranslate(0, 0, self.parent.objectsSize[2]/2)
843 if self.tempMatrix is not None:
844 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
845 glMultMatrixf(tempMatrix)
846 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
847 glTranslate(offset[0], offset[1], -vMin[2])
848 glMultMatrixf(matrix)
849 glCallList(displayList)