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)
98 sizer = wx.BoxSizer(wx.VERTICAL)
99 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
100 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
103 self.checkReloadFileTimer = wx.Timer(self)
104 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
105 self.checkReloadFileTimer.Start(1000)
108 self.rotateToolButton = openglGui.glRadioButton(self.glCanvas, 1, 'Rotate', (0,1), group, self.OnToolSelect)
109 self.scaleToolButton = openglGui.glRadioButton(self.glCanvas, 2, 'Scale', (0,2), group, self.OnToolSelect)
110 self.mirrorToolButton = openglGui.glRadioButton(self.glCanvas, 12, 'Mirror', (0,3), group, self.OnToolSelect)
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.mirrorXButton = openglGui.glButton(self.glCanvas, 12, 'Mirror X', (1,1), lambda : self.OnMirror(0))
119 self.mirrorYButton = openglGui.glButton(self.glCanvas, 13, 'Mirror Y', (1,2), lambda : self.OnMirror(1))
120 self.mirrorZButton = openglGui.glButton(self.glCanvas, 14, 'Mirror Z', (1,3), lambda : self.OnMirror(2))
122 self.openFileButton = openglGui.glButton(self.glCanvas, 3, 'Load model', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
123 self.sliceButton = openglGui.glButton(self.glCanvas, 6, 'Prepare model', (0,-2), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
124 self.printButton = openglGui.glButton(self.glCanvas, 7, 'Print model', (0,-1), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
126 extruderCount = int(profile.getPreference('extruder_amount'))
127 if extruderCount > 1:
128 openglGui.glButton(self.glCanvas, 3, 'Load dual model', (1,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
129 if extruderCount > 2:
130 openglGui.glButton(self.glCanvas, 3, 'Load triple model', (2,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
131 if extruderCount > 3:
132 openglGui.glButton(self.glCanvas, 3, 'Load quad model', (3,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
134 self.scaleForm = openglGui.glFrame(self.glCanvas, (1, 3))
135 openglGui.glGuiLayoutGrid(self.scaleForm)
136 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
137 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
138 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
139 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
140 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
141 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
142 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
143 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
144 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
145 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
146 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
147 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
148 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
149 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
153 self.returnToModelViewAndUpdateModel()
155 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
157 def returnToModelViewAndUpdateModel(self):
158 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
159 self.setViewMode('Normal')
160 self.updateModelTransform()
162 def OnToolSelect(self):
163 if self.rotateToolButton.getSelected():
164 self.tool = previewTools.toolRotate(self.glCanvas)
165 elif self.scaleToolButton.getSelected():
166 self.tool = previewTools.toolScale(self.glCanvas)
167 elif self.mirrorToolButton.getSelected():
168 self.tool = previewTools.toolNone(self.glCanvas)
170 self.tool = previewTools.toolNone(self.glCanvas)
171 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
172 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
173 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
174 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
175 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
176 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
177 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
178 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
179 self.returnToModelViewAndUpdateModel()
181 def OnScaleEntry(self, value, axis):
186 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
187 scale = value / scale
190 if self.scaleUniform.getValue():
191 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
193 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
194 matrix[axis][axis] = scale
195 self.matrix *= numpy.matrix(matrix, numpy.float64)
196 self.updateModelTransform()
198 def OnScaleEntryMM(self, value, axis):
203 scale = self.objectsSize[axis]
204 scale = value / scale
207 if self.scaleUniform.getValue():
208 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
210 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
211 matrix[axis][axis] = scale
212 self.matrix *= numpy.matrix(matrix, numpy.float64)
213 self.updateModelTransform()
215 def OnMirror(self, axis):
216 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
217 matrix[axis][axis] = -1
218 self.matrix *= numpy.matrix(matrix, numpy.float64)
219 for obj in self.objectList:
221 obj.steepDirty = True
222 self.updateModelTransform()
224 def OnMove(self, e = None):
227 x, y = self.glCanvas.ClientToScreenXY(0, 0)
228 sx, sy = self.glCanvas.GetClientSizeTuple()
229 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
231 def OnScaleMax(self, onlyScaleDown = False):
232 if self.objectsMinV is None:
234 vMin = self.objectsMinV
235 vMax = self.objectsMaxV
237 if profile.getProfileSettingFloat('skirt_line_count') > 0:
238 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
239 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
240 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
241 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
242 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
243 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
244 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
245 if scale > 1.0 and onlyScaleDown:
247 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
248 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
249 self.setViewMode('Normal')
250 self.updateModelTransform()
252 def OnRotateReset(self):
253 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
254 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
255 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
256 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
257 for obj in self.objectList:
258 obj.steepDirty = True
259 self.updateModelTransform()
261 def OnScaleReset(self):
262 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
263 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
264 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
265 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
266 for obj in self.objectList:
267 obj.steepDirty = True
268 self.updateModelTransform()
271 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
272 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
275 for v in transformedVertexes:
276 diff = v - minZvertex
277 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
280 dot = (diff[2] / len)
286 rad = -math.atan2(dotV[1], dotV[0])
287 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
288 rad = -math.asin(dotMin)
289 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
292 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
293 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
296 for v in transformedVertexes:
297 diff = v - minZvertex
298 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
301 dot = (diff[2] / len)
308 rad = math.asin(dotMin)
310 rad = -math.asin(dotMin)
311 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
313 for obj in self.objectList:
314 obj.steepDirty = True
315 self.updateModelTransform()
318 self.glCanvas.yaw = 30
319 self.glCanvas.pitch = 60
320 self.glCanvas.zoom = 300
321 self.glCanvas.view3D = True
322 self.glCanvas.Refresh()
324 def OnTopClick(self):
325 self.glCanvas.view3D = False
326 self.glCanvas.zoom = 100
327 self.glCanvas.offsetX = 0
328 self.glCanvas.offsetY = 0
329 self.glCanvas.Refresh()
331 def OnLayerNrChange(self, e):
332 self.glCanvas.Refresh()
334 def setViewMode(self, mode):
336 self.normalViewButton.SetValue(True)
338 self.gcodeViewButton.SetValue(True)
339 self.glCanvas.viewMode = mode
340 wx.CallAfter(self.glCanvas.Refresh)
342 def loadModelFiles(self, filelist, showWarning = False):
343 while len(filelist) > len(self.objectList):
344 self.objectList.append(previewObject())
345 for idx in xrange(len(filelist), len(self.objectList)):
346 self.objectList[idx].mesh = None
347 self.objectList[idx].filename = None
348 for idx in xrange(0, len(filelist)):
349 obj = self.objectList[idx]
350 if obj.filename != filelist[idx]:
352 self.gcodeFileTime = None
353 self.logFileTime = None
354 obj.filename = filelist[idx]
356 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
357 #Do the STL file loading in a background thread so we don't block the UI.
358 if self.loadThread is not None and self.loadThread.isAlive():
359 self.loadThread.join()
360 self.loadThread = threading.Thread(target=self.doFileLoadThread)
361 self.loadThread.daemon = True
362 self.loadThread.start()
365 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
366 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
368 def OnCheckReloadFile(self, e):
369 #Only show the reload popup when the window has focus, because the popup goes over other programs.
370 if self.GetParent().FindFocus() is None:
372 for obj in self.objectList:
373 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
374 self.checkReloadFileTimer.Stop()
375 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
376 if wx.TheClipboard.Open():
377 data = wx.TextDataObject()
378 if wx.TheClipboard.GetData(data):
379 data = data.GetText()
380 if re.match('^http://.*/.*$', data):
381 if data.endswith(tuple(meshLoader.supportedExtensions())):
382 #Got an url on the clipboard with a model file.
384 wx.TheClipboard.Close()
386 def reloadModelFiles(self, filelist = None):
387 if filelist is not None:
388 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
389 for idx in xrange(0, len(filelist)):
390 if self.objectList[idx].filename != filelist[idx]:
394 for idx in xrange(0, len(self.objectList)):
395 filelist.append(self.objectList[idx].filename)
396 self.loadModelFiles(filelist)
399 def doFileLoadThread(self):
400 for obj in self.objectList:
401 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
402 obj.fileTime = os.stat(obj.filename).st_mtime
403 mesh = meshLoader.loadMesh(obj.filename)
406 obj.steepDirty = True
407 self.updateModelTransform()
408 self.OnScaleMax(True)
409 self.glCanvas.zoom = numpy.max(self.objectsSize) * 3.5
411 wx.CallAfter(self.updateToolbar)
412 wx.CallAfter(self.glCanvas.Refresh)
414 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
415 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
416 gcode = gcodeInterpreter.gcode()
417 gcode.progressCallback = self.loadProgress
418 gcode.load(self.gcodeFilename)
419 self.gcodeDirty = False
421 self.gcodeDirty = True
424 for line in open(self.gcodeFilename, "rt"):
425 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)
427 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
428 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
429 errorList.append([v1, v2])
430 self.errorList = errorList
432 wx.CallAfter(self.updateToolbar)
433 wx.CallAfter(self.glCanvas.Refresh)
434 elif not os.path.isfile(self.gcodeFilename):
436 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
438 def loadProgress(self, progress):
441 def OnResetAll(self, e = None):
442 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
443 profile.setPluginConfig([])
444 self.updateProfileToControls()
446 def ShowWarningPopup(self, text, callback = None):
447 self.warningPopup.text.SetLabel(text)
448 self.warningPopup.callback = callback
450 self.warningPopup.yesButton.Show(False)
451 self.warningPopup.noButton.SetLabel('ok')
453 self.warningPopup.yesButton.Show(True)
454 self.warningPopup.noButton.SetLabel('no')
455 self.warningPopup.Fit()
456 self.warningPopup.Layout()
458 self.warningPopup.Show(True)
459 self.warningPopup.timer.Start(5000)
461 def OnWarningPopup(self, e):
462 self.warningPopup.Show(False)
463 self.warningPopup.timer.Stop()
464 self.warningPopup.callback()
466 def OnHideWarning(self, e):
467 self.warningPopup.Show(False)
468 self.warningPopup.timer.Stop()
470 def updateToolbar(self):
471 self.gcodeViewButton.Show(self.gcode is not None)
472 self.mixedViewButton.Show(self.gcode is not None)
473 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
474 self.printButton.setDisabled(self.gcode is None)
475 if self.gcode is not None:
476 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
477 self.toolbar.Realize()
480 def OnViewChange(self):
481 if self.normalViewButton.GetValue():
482 self.glCanvas.viewMode = "Normal"
483 elif self.transparentViewButton.GetValue():
484 self.glCanvas.viewMode = "Transparent"
485 elif self.xrayViewButton.GetValue():
486 self.glCanvas.viewMode = "X-Ray"
487 elif self.gcodeViewButton.GetValue():
488 self.layerSpin.SetValue(self.layerSpin.GetMax())
489 self.glCanvas.viewMode = "GCode"
490 elif self.mixedViewButton.GetValue():
491 self.glCanvas.viewMode = "Mixed"
492 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
493 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
495 self.glCanvas.Refresh()
497 def updateModelTransform(self, f=0):
498 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
501 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
502 for obj in self.objectList:
505 obj.mesh.matrix = self.matrix
506 obj.mesh.processMatrix()
508 minV = self.objectList[0].mesh.getMinimum()
509 maxV = self.objectList[0].mesh.getMaximum()
510 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
511 for obj in self.objectList:
515 minV = numpy.minimum(minV, obj.mesh.getMinimum())
516 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
517 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
519 self.objectsMaxV = maxV
520 self.objectsMinV = minV
521 self.objectsSize = self.objectsMaxV - self.objectsMinV
522 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
524 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
525 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
526 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
527 self.scaleXctrl.setValue(round(scaleX, 2))
528 self.scaleYctrl.setValue(round(scaleY, 2))
529 self.scaleZctrl.setValue(round(scaleZ, 2))
530 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
531 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
532 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
534 self.glCanvas.Refresh()
536 def updateProfileToControls(self):
537 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
538 self.updateModelTransform()
539 for obj in self.objectList:
540 obj.steepDirty = True
541 self.glCanvas.updateProfileToControls()
543 class PreviewGLCanvas(openglGui.glGuiPanel):
544 def __init__(self, parent):
545 super(PreviewGLCanvas, self).__init__(parent)
546 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
551 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
553 self.gcodeDisplayList = None
554 self.gcodeDisplayListMade = None
555 self.gcodeDisplayListCount = 0
556 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]]
560 self.tempMatrix = None
563 def updateProfileToControls(self):
564 self.objColor[0] = profile.getPreferenceColour('model_colour')
565 self.objColor[1] = profile.getPreferenceColour('model_colour2')
566 self.objColor[2] = profile.getPreferenceColour('model_colour3')
567 self.objColor[3] = profile.getPreferenceColour('model_colour4')
569 def OnMouseMotion(self,e):
570 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
571 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
572 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
573 p0 -= self.viewTarget
574 p1 -= self.viewTarget
575 if not e.Dragging() or self.dragType != 'tool':
576 self.parent.tool.OnMouseMove(p0, p1)
581 if e.Dragging() and e.LeftIsDown():
582 if self.dragType == '':
583 #Define the drag type depending on the cursor position.
584 self.dragType = 'viewRotate'
585 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
586 if self.parent.tool.OnDragStart(p0, p1):
587 self.dragType = 'tool'
589 if self.dragType == 'viewRotate':
591 self.yaw += e.GetX() - self.oldX
592 self.pitch -= e.GetY() - self.oldY
598 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
599 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
600 elif self.dragType == 'tool':
601 self.parent.tool.OnDrag(p0, p1)
603 #Workaround for buggy ATI cards.
604 size = self.GetSizeTuple()
605 self.SetSize((size[0]+1, size[1]))
606 self.SetSize((size[0], size[1]))
609 if self.dragType != '':
610 if self.tempMatrix is not None:
611 self.parent.matrix *= self.tempMatrix
612 self.parent.updateModelTransform()
613 self.tempMatrix = None
614 for obj in self.parent.objectList:
615 obj.steepDirty = True
616 self.parent.tool.OnDragEnd()
618 if e.Dragging() and e.RightIsDown():
619 self.zoom += e.GetY() - self.oldY
627 def getObjectBoundaryCircle(self):
628 return self.parent.objectsBoundaryCircleSize
630 def getObjectSize(self):
631 return self.parent.objectsSize
633 def getObjectMatrix(self):
634 return self.parent.matrix
636 def getObjectCenterPos(self):
637 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
639 def OnMouseWheel(self,e):
640 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
648 opengl.InitGL(self, self.view3D, self.zoom)
650 glTranslate(0,0,-self.zoom)
651 glTranslate(self.zoom/20.0,0,0)
652 glRotate(-self.pitch, 1,0,0)
653 glRotate(self.yaw, 0,0,1)
655 if self.viewMode == "GCode" or self.viewMode == "Mixed":
656 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:
657 self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z
659 if self.parent.objectsMaxV is not None:
660 self.viewTarget = self.getObjectCenterPos()
661 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
663 self.viewport = glGetIntegerv(GL_VIEWPORT)
664 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
665 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
670 machineSize = self.parent.machineSize
672 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
673 if self.parent.gcodeDirty:
674 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
675 if self.gcodeDisplayList is not None:
676 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
677 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
678 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
679 self.parent.gcodeDirty = False
680 self.gcodeDisplayListMade = 0
682 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
683 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
684 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
686 self.gcodeDisplayListMade += 1
687 wx.CallAfter(self.Refresh)
690 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
691 for obj in self.parent.objectList:
694 if obj.displayList is None:
695 obj.displayList = glGenLists(1)
696 obj.steepDisplayList = glGenLists(1)
697 obj.outlineDisplayList = glGenLists(1)
700 glNewList(obj.displayList, GL_COMPILE)
701 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
703 glNewList(obj.outlineDisplayList, GL_COMPILE)
704 opengl.DrawMeshOutline(obj.mesh)
707 if self.viewMode == "Mixed":
709 glColor3f(0.0,0.0,0.0)
710 self.drawModel(obj.displayList)
711 glColor3f(1.0,1.0,1.0)
712 glClear(GL_DEPTH_BUFFER_BIT)
716 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
718 if profile.getPreference('machine_center_is_zero') == 'True':
719 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
720 glEnable(GL_COLOR_MATERIAL)
721 glEnable(GL_LIGHTING)
722 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
723 starttime = time.time()
724 for i in xrange(drawUpToLayer - 1, -1, -1):
726 if i < self.parent.layerSpin.GetValue():
727 c = 0.9 - (drawUpToLayer - i) * 0.1
732 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
733 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
734 glCallList(self.gcodeDisplayList + i)
735 if time.time() - starttime > 0.1:
738 glDisable(GL_LIGHTING)
739 glDisable(GL_COLOR_MATERIAL)
740 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
741 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
744 glColor3f(1.0,1.0,1.0)
746 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
747 for obj in self.parent.objectList:
751 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
752 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
753 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
754 #If we want transparent, then first render a solid black model to remove the printer size lines.
755 if self.viewMode != "Mixed":
757 glColor3f(0.0,0.0,0.0)
758 self.drawModel(obj.displayList)
759 glColor3f(1.0,1.0,1.0)
760 #After the black model is rendered, render the model again but now with lighting and no depth testing.
761 glDisable(GL_DEPTH_TEST)
762 glEnable(GL_LIGHTING)
764 glBlendFunc(GL_ONE, GL_ONE)
765 glEnable(GL_LIGHTING)
766 self.drawModel(obj.displayList)
767 glEnable(GL_DEPTH_TEST)
768 elif self.viewMode == "X-Ray":
769 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
770 glDisable(GL_LIGHTING)
771 glDisable(GL_DEPTH_TEST)
772 glEnable(GL_STENCIL_TEST)
773 glStencilFunc(GL_ALWAYS, 1, 1)
774 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
775 self.drawModel(obj.displayList)
776 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
778 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
779 glStencilFunc(GL_EQUAL, 0, 1)
781 self.drawModel(obj.displayList)
782 glStencilFunc(GL_EQUAL, 1, 1)
784 self.drawModel(obj.displayList)
788 for i in xrange(2, 15, 2):
789 glStencilFunc(GL_EQUAL, i, 0xFF);
790 glColor(float(i)/10, float(i)/10, float(i)/5)
792 glVertex3f(-1000,-1000,-1)
793 glVertex3f( 1000,-1000,-1)
794 glVertex3f( 1000, 1000,-1)
795 glVertex3f(-1000, 1000,-1)
797 for i in xrange(1, 15, 2):
798 glStencilFunc(GL_EQUAL, i, 0xFF);
799 glColor(float(i)/10, 0, 0)
801 glVertex3f(-1000,-1000,-1)
802 glVertex3f( 1000,-1000,-1)
803 glVertex3f( 1000, 1000,-1)
804 glVertex3f(-1000, 1000,-1)
808 glDisable(GL_STENCIL_TEST)
809 glEnable(GL_DEPTH_TEST)
811 #Fix the depth buffer
812 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
813 self.drawModel(obj.displayList)
814 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
815 elif self.viewMode == "Normal":
816 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
817 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
818 glEnable(GL_LIGHTING)
819 self.drawModel(obj.displayList)
821 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
822 glEnable(GL_DEPTH_TEST)
823 glDisable(GL_LIGHTING)
825 self.drawModel(obj.outlineDisplayList)
827 if self.drawSteepOverhang:
829 obj.steepDirty = False
830 glNewList(obj.steepDisplayList, GL_COMPILE)
831 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
833 glDisable(GL_LIGHTING)
835 self.drawModel(obj.steepDisplayList)
838 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
839 # glDisable(GL_LIGHTING)
840 # glDisable(GL_DEPTH_TEST)
841 # glDisable(GL_BLEND)
844 # for err in self.parent.errorList:
845 # glVertex3f(err[0].x, err[0].y, err[0].z)
846 # glVertex3f(err[1].x, err[1].y, err[1].z)
848 # glEnable(GL_DEPTH_TEST)
850 opengl.DrawMachine(machineSize)
852 #Draw the current selected tool
853 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
855 pos = self.getObjectCenterPos()
856 glTranslate(pos[0], pos[1], pos[2])
857 self.parent.tool.OnDraw()
860 def drawModel(self, displayList):
861 vMin = self.parent.objectsMinV
862 vMax = self.parent.objectsMaxV
863 offset = - vMin - (vMax - vMin) / 2
865 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
868 glTranslate(0, 0, self.parent.objectsSize[2]/2)
869 if self.tempMatrix is not None:
870 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
871 glMultMatrixf(tempMatrix)
872 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
873 glTranslate(offset[0], offset[1], -vMin[2])
874 glMultMatrixf(matrix)
875 glCallList(displayList)