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', (1,-1), group, self.OnToolSelect)
110 self.mirrorToolButton = openglGui.glRadioButton(self.glCanvas, 12, 'Mirror', (2,-1), group, self.OnToolSelect)
112 self.resetRotationButton = openglGui.glButton(self.glCanvas, 4, 'Reset rotation', (0,-2), self.OnRotateReset)
113 self.layFlatButton = openglGui.glButton(self.glCanvas, 5, 'Lay flat', (0,-3), self.OnLayFlat)
115 self.resetScaleButton = openglGui.glButton(self.glCanvas, 8, 'Scale reset', (1,-2), self.OnScaleReset)
116 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 9, 'Scale to machine size', (1,-3), self.OnScaleMax)
118 self.mirrorXButton = openglGui.glButton(self.glCanvas, 12, 'Mirror X', (2,-2), lambda : self.OnMirror(0))
119 self.mirrorYButton = openglGui.glButton(self.glCanvas, 13, 'Mirror Y', (2,-3), lambda : self.OnMirror(1))
120 self.mirrorZButton = openglGui.glButton(self.glCanvas, 14, 'Mirror Z', (2,-4), 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', (1,0), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
124 self.printButton = openglGui.glButton(self.glCanvas, 7, 'Print model', (2,0), 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, (2, -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)
151 self.viewSelection = openglGui.glComboButton(self.glCanvas, 'View mode', [0,1,2,3,4], ['3D Model', 'Transparent', 'X-Ray', 'Overhang', 'Layers'], (-1,0), self.OnViewChange)
155 self.returnToModelViewAndUpdateModel()
157 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
159 def returnToModelViewAndUpdateModel(self):
160 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
161 self.setViewMode('Normal')
162 self.updateModelTransform()
164 def OnToolSelect(self):
165 if self.rotateToolButton.getSelected():
166 self.tool = previewTools.toolRotate(self.glCanvas)
167 elif self.scaleToolButton.getSelected():
168 self.tool = previewTools.toolScale(self.glCanvas)
169 elif self.mirrorToolButton.getSelected():
170 self.tool = previewTools.toolNone(self.glCanvas)
172 self.tool = previewTools.toolNone(self.glCanvas)
173 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
174 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
175 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
176 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
177 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
178 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
179 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
180 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
181 self.returnToModelViewAndUpdateModel()
183 def OnScaleEntry(self, value, axis):
188 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
189 scale = value / scale
192 if self.scaleUniform.getValue():
193 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
195 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
196 matrix[axis][axis] = scale
197 self.matrix *= numpy.matrix(matrix, numpy.float64)
198 self.updateModelTransform()
200 def OnScaleEntryMM(self, value, axis):
205 scale = self.objectsSize[axis]
206 scale = value / scale
209 if self.scaleUniform.getValue():
210 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
212 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
213 matrix[axis][axis] = scale
214 self.matrix *= numpy.matrix(matrix, numpy.float64)
215 self.updateModelTransform()
217 def OnMirror(self, axis):
218 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
219 matrix[axis][axis] = -1
220 self.matrix *= numpy.matrix(matrix, numpy.float64)
221 for obj in self.objectList:
223 obj.steepDirty = True
224 self.updateModelTransform()
226 def OnMove(self, e = None):
229 x, y = self.glCanvas.ClientToScreenXY(0, 0)
230 sx, sy = self.glCanvas.GetClientSizeTuple()
231 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
233 def OnScaleMax(self, onlyScaleDown = False):
234 if self.objectsMinV is None:
236 vMin = self.objectsMinV
237 vMax = self.objectsMaxV
239 if profile.getProfileSettingFloat('skirt_line_count') > 0:
240 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
241 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
242 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
243 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
244 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
245 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
246 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
247 if scale > 1.0 and onlyScaleDown:
249 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
250 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
251 self.setViewMode('Normal')
252 self.updateModelTransform()
254 def OnRotateReset(self):
255 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
256 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
257 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
258 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
259 for obj in self.objectList:
260 obj.steepDirty = True
261 self.updateModelTransform()
263 def OnScaleReset(self):
264 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
265 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
266 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
267 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
268 for obj in self.objectList:
269 obj.steepDirty = True
270 self.updateModelTransform()
273 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
274 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
277 for v in transformedVertexes:
278 diff = v - minZvertex
279 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
282 dot = (diff[2] / len)
288 rad = -math.atan2(dotV[1], dotV[0])
289 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
290 rad = -math.asin(dotMin)
291 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
294 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
295 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
298 for v in transformedVertexes:
299 diff = v - minZvertex
300 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
303 dot = (diff[2] / len)
310 rad = math.asin(dotMin)
312 rad = -math.asin(dotMin)
313 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
315 for obj in self.objectList:
316 obj.steepDirty = True
317 self.updateModelTransform()
320 self.glCanvas.yaw = 30
321 self.glCanvas.pitch = 60
322 self.glCanvas.zoom = 300
323 self.glCanvas.view3D = True
324 self.glCanvas.Refresh()
326 def OnTopClick(self):
327 self.glCanvas.view3D = False
328 self.glCanvas.zoom = 100
329 self.glCanvas.offsetX = 0
330 self.glCanvas.offsetY = 0
331 self.glCanvas.Refresh()
333 def OnLayerNrChange(self, e):
334 self.glCanvas.Refresh()
336 def setViewMode(self, mode):
338 self.normalViewButton.SetValue(True)
340 self.gcodeViewButton.SetValue(True)
341 self.glCanvas.viewMode = mode
342 wx.CallAfter(self.glCanvas.Refresh)
344 def loadModelFiles(self, filelist, showWarning = False):
345 while len(filelist) > len(self.objectList):
346 self.objectList.append(previewObject())
347 for idx in xrange(len(filelist), len(self.objectList)):
348 self.objectList[idx].mesh = None
349 self.objectList[idx].filename = None
350 for idx in xrange(0, len(filelist)):
351 obj = self.objectList[idx]
352 if obj.filename != filelist[idx]:
354 self.gcodeFileTime = None
355 self.logFileTime = None
356 obj.filename = filelist[idx]
358 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
359 #Do the STL file loading in a background thread so we don't block the UI.
360 if self.loadThread is not None and self.loadThread.isAlive():
361 self.loadThread.join()
362 self.loadThread = threading.Thread(target=self.doFileLoadThread)
363 self.loadThread.daemon = True
364 self.loadThread.start()
367 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
368 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
370 def OnCheckReloadFile(self, e):
371 #Only show the reload popup when the window has focus, because the popup goes over other programs.
372 if self.GetParent().FindFocus() is None:
374 for obj in self.objectList:
375 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
376 self.checkReloadFileTimer.Stop()
377 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
378 if wx.TheClipboard.Open():
379 data = wx.TextDataObject()
380 if wx.TheClipboard.GetData(data):
381 data = data.GetText()
382 if re.match('^http://.*/.*$', data):
383 if data.endswith(tuple(meshLoader.supportedExtensions())):
384 #Got an url on the clipboard with a model file.
386 wx.TheClipboard.Close()
388 def reloadModelFiles(self, filelist = None):
389 if filelist is not None:
390 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
391 for idx in xrange(0, len(filelist)):
392 if self.objectList[idx].filename != filelist[idx]:
396 for idx in xrange(0, len(self.objectList)):
397 filelist.append(self.objectList[idx].filename)
398 self.loadModelFiles(filelist)
401 def doFileLoadThread(self):
402 for obj in self.objectList:
403 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
404 obj.fileTime = os.stat(obj.filename).st_mtime
405 mesh = meshLoader.loadMesh(obj.filename)
408 obj.steepDirty = True
409 self.updateModelTransform()
410 self.OnScaleMax(True)
411 self.glCanvas.zoom = numpy.max(self.objectsSize) * 3.5
413 wx.CallAfter(self.updateToolbar)
414 wx.CallAfter(self.glCanvas.Refresh)
416 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
417 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
418 gcode = gcodeInterpreter.gcode()
419 gcode.progressCallback = self.loadProgress
420 gcode.load(self.gcodeFilename)
421 self.gcodeDirty = False
423 self.gcodeDirty = True
426 for line in open(self.gcodeFilename, "rt"):
427 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)
429 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
430 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
431 errorList.append([v1, v2])
432 self.errorList = errorList
434 wx.CallAfter(self.updateToolbar)
435 wx.CallAfter(self.glCanvas.Refresh)
436 elif not os.path.isfile(self.gcodeFilename):
438 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
440 def loadProgress(self, progress):
443 def OnResetAll(self, e = None):
444 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
445 profile.setPluginConfig([])
446 self.updateProfileToControls()
448 def ShowWarningPopup(self, text, callback = None):
449 self.warningPopup.text.SetLabel(text)
450 self.warningPopup.callback = callback
452 self.warningPopup.yesButton.Show(False)
453 self.warningPopup.noButton.SetLabel('ok')
455 self.warningPopup.yesButton.Show(True)
456 self.warningPopup.noButton.SetLabel('no')
457 self.warningPopup.Fit()
458 self.warningPopup.Layout()
460 self.warningPopup.Show(True)
461 self.warningPopup.timer.Start(5000)
463 def OnWarningPopup(self, e):
464 self.warningPopup.Show(False)
465 self.warningPopup.timer.Stop()
466 self.warningPopup.callback()
468 def OnHideWarning(self, e):
469 self.warningPopup.Show(False)
470 self.warningPopup.timer.Stop()
472 def updateToolbar(self):
473 self.gcodeViewButton.Show(self.gcode is not None)
474 self.mixedViewButton.Show(self.gcode is not None)
475 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
476 self.printButton.setDisabled(self.gcode is None)
477 if self.gcode is not None:
478 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
479 self.toolbar.Realize()
482 def OnViewChange(self):
483 selection = self.viewSelection.getValue()
484 self.glCanvas.drawSteepOverhang = False
485 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
487 self.glCanvas.viewMode = "Normal"
489 self.glCanvas.viewMode = "Transparent"
491 self.glCanvas.viewMode = "X-Ray"
493 self.glCanvas.viewMode = "Normal"
494 self.glCanvas.drawSteepOverhang = True
496 self.layerSpin.SetValue(self.layerSpin.GetMax())
497 self.glCanvas.viewMode = "GCode"
499 self.glCanvas.viewMode = "Mixed"
501 self.glCanvas.Refresh()
503 def updateModelTransform(self, f=0):
504 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
507 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
508 for obj in self.objectList:
511 obj.mesh.matrix = self.matrix
512 obj.mesh.processMatrix()
514 minV = self.objectList[0].mesh.getMinimum()
515 maxV = self.objectList[0].mesh.getMaximum()
516 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
517 for obj in self.objectList:
521 minV = numpy.minimum(minV, obj.mesh.getMinimum())
522 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
523 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
525 self.objectsMaxV = maxV
526 self.objectsMinV = minV
527 self.objectsSize = self.objectsMaxV - self.objectsMinV
528 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
530 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
531 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
532 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
533 self.scaleXctrl.setValue(round(scaleX, 2))
534 self.scaleYctrl.setValue(round(scaleY, 2))
535 self.scaleZctrl.setValue(round(scaleZ, 2))
536 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
537 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
538 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
540 self.glCanvas.Refresh()
542 def updateProfileToControls(self):
543 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
544 self.updateModelTransform()
545 for obj in self.objectList:
546 obj.steepDirty = True
547 self.glCanvas.updateProfileToControls()
549 class PreviewGLCanvas(openglGui.glGuiPanel):
550 def __init__(self, parent):
551 super(PreviewGLCanvas, self).__init__(parent)
552 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
557 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
559 self.gcodeDisplayList = None
560 self.gcodeDisplayListMade = None
561 self.gcodeDisplayListCount = 0
562 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]]
566 self.tempMatrix = None
569 def updateProfileToControls(self):
570 self.objColor[0] = profile.getPreferenceColour('model_colour')
571 self.objColor[1] = profile.getPreferenceColour('model_colour2')
572 self.objColor[2] = profile.getPreferenceColour('model_colour3')
573 self.objColor[3] = profile.getPreferenceColour('model_colour4')
575 def OnMouseMotion(self,e):
576 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
577 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
578 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
579 p0 -= self.viewTarget
580 p1 -= self.viewTarget
581 if not e.Dragging() or self.dragType != 'tool':
582 self.parent.tool.OnMouseMove(p0, p1)
587 if e.Dragging() and e.LeftIsDown():
588 if self.dragType == '':
589 #Define the drag type depending on the cursor position.
590 self.dragType = 'viewRotate'
591 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
592 if self.parent.tool.OnDragStart(p0, p1):
593 self.dragType = 'tool'
595 if self.dragType == 'viewRotate':
597 self.yaw += e.GetX() - self.oldX
598 self.pitch -= e.GetY() - self.oldY
604 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
605 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
606 elif self.dragType == 'tool':
607 self.parent.tool.OnDrag(p0, p1)
609 #Workaround for buggy ATI cards.
610 size = self.GetSizeTuple()
611 self.SetSize((size[0]+1, size[1]))
612 self.SetSize((size[0], size[1]))
615 if self.dragType != '':
616 if self.tempMatrix is not None:
617 self.parent.matrix *= self.tempMatrix
618 self.parent.updateModelTransform()
619 self.tempMatrix = None
620 for obj in self.parent.objectList:
621 obj.steepDirty = True
622 self.parent.tool.OnDragEnd()
624 if e.Dragging() and e.RightIsDown():
625 self.zoom += e.GetY() - self.oldY
633 def getObjectBoundaryCircle(self):
634 return self.parent.objectsBoundaryCircleSize
636 def getObjectSize(self):
637 return self.parent.objectsSize
639 def getObjectMatrix(self):
640 return self.parent.matrix
642 def getObjectCenterPos(self):
643 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
645 def OnMouseWheel(self,e):
646 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
654 opengl.InitGL(self, self.view3D, self.zoom)
656 glTranslate(0,0,-self.zoom)
657 glTranslate(self.zoom/20.0,0,0)
658 glRotate(-self.pitch, 1,0,0)
659 glRotate(self.yaw, 0,0,1)
661 if self.viewMode == "GCode" or self.viewMode == "Mixed":
662 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:
663 self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z
665 if self.parent.objectsMaxV is not None:
666 self.viewTarget = self.getObjectCenterPos()
667 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
669 self.viewport = glGetIntegerv(GL_VIEWPORT)
670 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
671 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
676 machineSize = self.parent.machineSize
678 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
679 if self.parent.gcodeDirty:
680 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
681 if self.gcodeDisplayList is not None:
682 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
683 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
684 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
685 self.parent.gcodeDirty = False
686 self.gcodeDisplayListMade = 0
688 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
689 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
690 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
692 self.gcodeDisplayListMade += 1
693 wx.CallAfter(self.Refresh)
696 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
697 for obj in self.parent.objectList:
700 if obj.displayList is None:
701 obj.displayList = glGenLists(1)
702 obj.steepDisplayList = glGenLists(1)
703 obj.outlineDisplayList = glGenLists(1)
706 glNewList(obj.displayList, GL_COMPILE)
707 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
709 glNewList(obj.outlineDisplayList, GL_COMPILE)
710 opengl.DrawMeshOutline(obj.mesh)
713 if self.viewMode == "Mixed":
715 glColor3f(0.0,0.0,0.0)
716 self.drawModel(obj.displayList)
717 glColor3f(1.0,1.0,1.0)
718 glClear(GL_DEPTH_BUFFER_BIT)
722 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
724 if profile.getPreference('machine_center_is_zero') == 'True':
725 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
726 glEnable(GL_COLOR_MATERIAL)
727 glEnable(GL_LIGHTING)
728 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
729 starttime = time.time()
730 for i in xrange(drawUpToLayer - 1, -1, -1):
732 if i < self.parent.layerSpin.GetValue():
733 c = 0.9 - (drawUpToLayer - i) * 0.1
738 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
739 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
740 glCallList(self.gcodeDisplayList + i)
741 if time.time() - starttime > 0.1:
744 glDisable(GL_LIGHTING)
745 glDisable(GL_COLOR_MATERIAL)
746 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
747 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
750 glColor3f(1.0,1.0,1.0)
752 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
753 for obj in self.parent.objectList:
757 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
758 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
759 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
760 #If we want transparent, then first render a solid black model to remove the printer size lines.
761 if self.viewMode != "Mixed":
763 glColor3f(0.0,0.0,0.0)
764 self.drawModel(obj.displayList)
765 glColor3f(1.0,1.0,1.0)
766 #After the black model is rendered, render the model again but now with lighting and no depth testing.
767 glDisable(GL_DEPTH_TEST)
768 glEnable(GL_LIGHTING)
770 glBlendFunc(GL_ONE, GL_ONE)
771 glEnable(GL_LIGHTING)
772 self.drawModel(obj.displayList)
773 glEnable(GL_DEPTH_TEST)
774 elif self.viewMode == "X-Ray":
775 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
776 glDisable(GL_LIGHTING)
777 glDisable(GL_DEPTH_TEST)
778 glEnable(GL_STENCIL_TEST)
779 glStencilFunc(GL_ALWAYS, 1, 1)
780 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
781 self.drawModel(obj.displayList)
782 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
784 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
785 glStencilFunc(GL_EQUAL, 0, 1)
787 self.drawModel(obj.displayList)
788 glStencilFunc(GL_EQUAL, 1, 1)
790 self.drawModel(obj.displayList)
794 for i in xrange(2, 15, 2):
795 glStencilFunc(GL_EQUAL, i, 0xFF);
796 glColor(float(i)/10, float(i)/10, float(i)/5)
798 glVertex3f(-1000,-1000,-1)
799 glVertex3f( 1000,-1000,-1)
800 glVertex3f( 1000, 1000,-1)
801 glVertex3f(-1000, 1000,-1)
803 for i in xrange(1, 15, 2):
804 glStencilFunc(GL_EQUAL, i, 0xFF);
805 glColor(float(i)/10, 0, 0)
807 glVertex3f(-1000,-1000,-1)
808 glVertex3f( 1000,-1000,-1)
809 glVertex3f( 1000, 1000,-1)
810 glVertex3f(-1000, 1000,-1)
814 glDisable(GL_STENCIL_TEST)
815 glEnable(GL_DEPTH_TEST)
817 #Fix the depth buffer
818 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
819 self.drawModel(obj.displayList)
820 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
821 elif self.viewMode == "Normal":
822 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
823 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
824 glEnable(GL_LIGHTING)
825 self.drawModel(obj.displayList)
827 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
828 glEnable(GL_DEPTH_TEST)
829 glDisable(GL_LIGHTING)
831 self.drawModel(obj.outlineDisplayList)
833 if self.drawSteepOverhang:
835 obj.steepDirty = False
836 glNewList(obj.steepDisplayList, GL_COMPILE)
837 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
839 glDisable(GL_LIGHTING)
841 self.drawModel(obj.steepDisplayList)
844 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
845 # glDisable(GL_LIGHTING)
846 # glDisable(GL_DEPTH_TEST)
847 # glDisable(GL_BLEND)
850 # for err in self.parent.errorList:
851 # glVertex3f(err[0].x, err[0].y, err[0].z)
852 # glVertex3f(err[1].x, err[1].y, err[1].z)
854 # glEnable(GL_DEPTH_TEST)
856 opengl.DrawMachine(machineSize)
858 #Draw the current selected tool
859 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
861 pos = self.getObjectCenterPos()
862 glTranslate(pos[0], pos[1], pos[2])
863 self.parent.tool.OnDraw()
866 def drawModel(self, displayList):
867 vMin = self.parent.objectsMinV
868 vMax = self.parent.objectsMaxV
869 offset = - vMin - (vMax - vMin) / 2
871 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
874 glTranslate(0, 0, self.parent.objectsSize[2]/2)
875 if self.tempMatrix is not None:
876 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
877 glMultMatrixf(tempMatrix)
878 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
879 glTranslate(offset[0], offset[1], -vMin[2])
880 glMultMatrixf(matrix)
881 glCallList(displayList)