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)
107 self.rotateToolButton = openglGui.glButton(self.glCanvas, 1, 'Rotate', (0,1), self.OnRotateSelect)
108 self.scaleToolButton = openglGui.glButton(self.glCanvas, 2, 'Scale', (0,2), self.OnScaleSelect)
110 self.resetRotationButton = openglGui.glButton(self.glCanvas, 4, 'Reset rotation', (1,1), self.OnRotateReset)
111 self.layFlatButton = openglGui.glButton(self.glCanvas, 5, 'Lay flat', (1,2), self.OnLayFlat)
113 self.resetScaleButton = openglGui.glButton(self.glCanvas, 8, 'Scale reset', (1,1), self.OnScaleReset)
114 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 9, 'Scale to machine size', (1,2), self.OnScaleMax)
116 self.openFileButton = openglGui.glButton(self.glCanvas, 3, 'Load model', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
117 self.sliceButton = openglGui.glButton(self.glCanvas, 6, 'Prepare model', (0,-2), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
118 self.printButton = openglGui.glButton(self.glCanvas, 7, 'Print model', (0,-1), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
120 extruderCount = int(profile.getPreference('extruder_amount'))
121 if extruderCount > 1:
122 openglGui.glButton(self.glCanvas, 3, 'Load dual model', (1,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
123 if extruderCount > 2:
124 openglGui.glButton(self.glCanvas, 3, 'Load triple model', (2,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
125 if extruderCount > 3:
126 openglGui.glButton(self.glCanvas, 3, 'Load quad model', (3,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
128 self.scaleForm = openglGui.glFrame(self.glCanvas, (1, 3))
129 openglGui.glGuiLayoutGrid(self.scaleForm)
130 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
131 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
132 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
133 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
134 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
135 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
136 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
137 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
138 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
139 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
140 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
141 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
142 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
143 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
144 self.scaleForm.setHidden(True)
147 self.returnToModelViewAndUpdateModel()
149 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
150 self.tool = previewTools.toolNone(self.glCanvas)
152 def returnToModelViewAndUpdateModel(self):
153 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
154 self.setViewMode('Normal')
155 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
156 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
157 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
158 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
159 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
160 self.updateModelTransform()
162 def OnRotateSelect(self):
163 if self.rotateToolButton.getSelected():
164 self.rotateToolButton.setSelected(False)
165 self.tool = previewTools.toolNone(self.glCanvas)
167 self.rotateToolButton.setSelected(True)
168 self.tool = previewTools.toolRotate(self.glCanvas)
169 self.scaleToolButton.setSelected(False)
170 self.returnToModelViewAndUpdateModel()
172 def OnScaleSelect(self):
173 self.rotateToolButton.setSelected(False)
174 if self.scaleToolButton.getSelected():
175 self.scaleToolButton.setSelected(False)
176 self.tool = previewTools.toolNone(self.glCanvas)
178 self.scaleToolButton.setSelected(True)
179 self.tool = previewTools.toolScale(self.glCanvas)
180 self.returnToModelViewAndUpdateModel()
182 def OnScaleEntry(self, value, axis):
187 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
188 scale = value / scale
191 if self.scaleUniform.getValue():
192 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
194 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
195 matrix[axis][axis] = scale
196 self.matrix *= numpy.matrix(matrix, numpy.float64)
197 self.updateModelTransform()
199 def OnScaleEntryMM(self, value, axis):
204 scale = self.objectsSize[axis]
205 scale = value / scale
208 if self.scaleUniform.getValue():
209 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
211 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
212 matrix[axis][axis] = scale
213 self.matrix *= numpy.matrix(matrix, numpy.float64)
214 self.updateModelTransform()
216 def OnMove(self, e = None):
219 x, y = self.glCanvas.ClientToScreenXY(0, 0)
220 sx, sy = self.glCanvas.GetClientSizeTuple()
221 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
223 def OnScaleMax(self, onlyScaleDown = False):
224 if self.objectsMinV is None:
226 vMin = self.objectsMinV
227 vMax = self.objectsMaxV
229 if profile.getProfileSettingFloat('skirt_line_count') > 0:
230 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
231 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
232 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
233 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
234 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
235 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
236 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
237 if scale > 1.0 and onlyScaleDown:
239 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
240 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
241 self.setViewMode('Normal')
242 self.updateModelTransform()
244 def OnRotateReset(self):
245 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
246 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
247 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
248 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
249 for obj in self.objectList:
250 obj.steepDirty = True
251 self.updateModelTransform()
253 def OnScaleReset(self):
254 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
255 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
256 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
257 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
258 for obj in self.objectList:
259 obj.steepDirty = True
260 self.updateModelTransform()
263 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
264 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
267 for v in transformedVertexes:
268 diff = v - minZvertex
269 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
272 dot = (diff[2] / len)
278 rad = -math.atan2(dotV[1], dotV[0])
279 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
280 rad = -math.asin(dotMin)
281 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
284 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
285 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
288 for v in transformedVertexes:
289 diff = v - minZvertex
290 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
293 dot = (diff[2] / len)
300 rad = math.asin(dotMin)
302 rad = -math.asin(dotMin)
303 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
305 for obj in self.objectList:
306 obj.steepDirty = True
307 self.updateModelTransform()
310 self.glCanvas.yaw = 30
311 self.glCanvas.pitch = 60
312 self.glCanvas.zoom = 300
313 self.glCanvas.view3D = True
314 self.glCanvas.Refresh()
316 def OnTopClick(self):
317 self.glCanvas.view3D = False
318 self.glCanvas.zoom = 100
319 self.glCanvas.offsetX = 0
320 self.glCanvas.offsetY = 0
321 self.glCanvas.Refresh()
323 def OnLayerNrChange(self, e):
324 self.glCanvas.Refresh()
326 def setViewMode(self, mode):
328 self.normalViewButton.SetValue(True)
330 self.gcodeViewButton.SetValue(True)
331 self.glCanvas.viewMode = mode
332 wx.CallAfter(self.glCanvas.Refresh)
334 def loadModelFiles(self, filelist, showWarning = False):
335 while len(filelist) > len(self.objectList):
336 self.objectList.append(previewObject())
337 for idx in xrange(len(filelist), len(self.objectList)):
338 self.objectList[idx].mesh = None
339 self.objectList[idx].filename = None
340 for idx in xrange(0, len(filelist)):
341 obj = self.objectList[idx]
342 if obj.filename != filelist[idx]:
344 self.gcodeFileTime = None
345 self.logFileTime = None
346 obj.filename = filelist[idx]
348 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
349 #Do the STL file loading in a background thread so we don't block the UI.
350 if self.loadThread is not None and self.loadThread.isAlive():
351 self.loadThread.join()
352 self.loadThread = threading.Thread(target=self.doFileLoadThread)
353 self.loadThread.daemon = True
354 self.loadThread.start()
357 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
358 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
360 def OnCheckReloadFile(self, e):
361 #Only show the reload popup when the window has focus, because the popup goes over other programs.
362 if self.GetParent().FindFocus() is None:
364 for obj in self.objectList:
365 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
366 self.checkReloadFileTimer.Stop()
367 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
368 if wx.TheClipboard.Open():
369 data = wx.TextDataObject()
370 if wx.TheClipboard.GetData(data):
371 data = data.GetText()
372 if re.match('^http://.*/.*$', data):
373 if data.endswith(tuple(meshLoader.supportedExtensions())):
374 #Got an url on the clipboard with a model file.
376 wx.TheClipboard.Close()
378 def reloadModelFiles(self, filelist = None):
379 if filelist is not None:
380 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
381 for idx in xrange(0, len(filelist)):
382 if self.objectList[idx].filename != filelist[idx]:
386 for idx in xrange(0, len(self.objectList)):
387 filelist.append(self.objectList[idx].filename)
388 self.loadModelFiles(filelist)
391 def doFileLoadThread(self):
392 for obj in self.objectList:
393 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
394 obj.fileTime = os.stat(obj.filename).st_mtime
395 mesh = meshLoader.loadMesh(obj.filename)
398 obj.steepDirty = True
399 self.updateModelTransform()
400 self.OnScaleMax(True)
401 self.glCanvas.zoom = numpy.max(self.objectsSize) * 3.5
403 wx.CallAfter(self.updateToolbar)
404 wx.CallAfter(self.glCanvas.Refresh)
406 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
407 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
408 gcode = gcodeInterpreter.gcode()
409 gcode.progressCallback = self.loadProgress
410 gcode.load(self.gcodeFilename)
411 self.gcodeDirty = False
413 self.gcodeDirty = True
416 for line in open(self.gcodeFilename, "rt"):
417 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)
419 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
420 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
421 errorList.append([v1, v2])
422 self.errorList = errorList
424 wx.CallAfter(self.updateToolbar)
425 wx.CallAfter(self.glCanvas.Refresh)
426 elif not os.path.isfile(self.gcodeFilename):
428 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
430 def loadProgress(self, progress):
433 def OnResetAll(self, e = None):
434 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
435 profile.setPluginConfig([])
436 self.updateProfileToControls()
438 def ShowWarningPopup(self, text, callback = None):
439 self.warningPopup.text.SetLabel(text)
440 self.warningPopup.callback = callback
442 self.warningPopup.yesButton.Show(False)
443 self.warningPopup.noButton.SetLabel('ok')
445 self.warningPopup.yesButton.Show(True)
446 self.warningPopup.noButton.SetLabel('no')
447 self.warningPopup.Fit()
448 self.warningPopup.Layout()
450 self.warningPopup.Show(True)
451 self.warningPopup.timer.Start(5000)
453 def OnWarningPopup(self, e):
454 self.warningPopup.Show(False)
455 self.warningPopup.timer.Stop()
456 self.warningPopup.callback()
458 def OnHideWarning(self, e):
459 self.warningPopup.Show(False)
460 self.warningPopup.timer.Stop()
462 def updateToolbar(self):
463 self.gcodeViewButton.Show(self.gcode is not None)
464 self.mixedViewButton.Show(self.gcode is not None)
465 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
466 self.printButton.setDisabled(self.gcode is None)
467 if self.gcode is not None:
468 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
469 self.toolbar.Realize()
472 def OnViewChange(self):
473 if self.normalViewButton.GetValue():
474 self.glCanvas.viewMode = "Normal"
475 elif self.transparentViewButton.GetValue():
476 self.glCanvas.viewMode = "Transparent"
477 elif self.xrayViewButton.GetValue():
478 self.glCanvas.viewMode = "X-Ray"
479 elif self.gcodeViewButton.GetValue():
480 self.layerSpin.SetValue(self.layerSpin.GetMax())
481 self.glCanvas.viewMode = "GCode"
482 elif self.mixedViewButton.GetValue():
483 self.glCanvas.viewMode = "Mixed"
484 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
485 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
487 self.glCanvas.Refresh()
489 def updateModelTransform(self, f=0):
490 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
493 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
494 for obj in self.objectList:
497 obj.mesh.matrix = self.matrix
498 obj.mesh.processMatrix()
500 minV = self.objectList[0].mesh.getMinimum()
501 maxV = self.objectList[0].mesh.getMaximum()
502 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
503 for obj in self.objectList:
507 minV = numpy.minimum(minV, obj.mesh.getMinimum())
508 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
509 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
511 self.objectsMaxV = maxV
512 self.objectsMinV = minV
513 self.objectsSize = self.objectsMaxV - self.objectsMinV
514 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
516 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
517 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
518 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
519 self.scaleXctrl.setValue(round(scaleX, 2))
520 self.scaleYctrl.setValue(round(scaleY, 2))
521 self.scaleZctrl.setValue(round(scaleZ, 2))
522 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
523 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
524 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
526 self.glCanvas.Refresh()
528 def updateProfileToControls(self):
529 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
530 self.updateModelTransform()
531 for obj in self.objectList:
532 obj.steepDirty = True
533 self.glCanvas.updateProfileToControls()
535 class PreviewGLCanvas(openglGui.glGuiPanel):
536 def __init__(self, parent):
537 super(PreviewGLCanvas, self).__init__(parent)
538 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
543 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
545 self.gcodeDisplayList = None
546 self.gcodeDisplayListMade = None
547 self.gcodeDisplayListCount = 0
548 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]]
552 self.tempMatrix = None
555 def updateProfileToControls(self):
556 self.objColor[0] = profile.getPreferenceColour('model_colour')
557 self.objColor[1] = profile.getPreferenceColour('model_colour2')
558 self.objColor[2] = profile.getPreferenceColour('model_colour3')
559 self.objColor[3] = profile.getPreferenceColour('model_colour4')
561 def OnMouseMotion(self,e):
562 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
563 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
564 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
565 p0 -= self.viewTarget
566 p1 -= self.viewTarget
567 if not e.Dragging() or self.dragType != 'tool':
568 self.parent.tool.OnMouseMove(p0, p1)
573 if e.Dragging() and e.LeftIsDown():
574 if self.dragType == '':
575 #Define the drag type depending on the cursor position.
576 self.dragType = 'viewRotate'
577 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
578 if self.parent.tool.OnDragStart(p0, p1):
579 self.dragType = 'tool'
581 if self.dragType == 'viewRotate':
583 self.yaw += e.GetX() - self.oldX
584 self.pitch -= e.GetY() - self.oldY
590 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
591 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
592 elif self.dragType == 'tool':
593 self.parent.tool.OnDrag(p0, p1)
595 #Workaround for buggy ATI cards.
596 size = self.GetSizeTuple()
597 self.SetSize((size[0]+1, size[1]))
598 self.SetSize((size[0], size[1]))
601 if self.dragType != '':
602 if self.tempMatrix is not None:
603 self.parent.matrix *= self.tempMatrix
604 self.parent.updateModelTransform()
605 self.tempMatrix = None
606 for obj in self.parent.objectList:
607 obj.steepDirty = True
608 self.parent.tool.OnDragEnd()
610 if e.Dragging() and e.RightIsDown():
611 self.zoom += e.GetY() - self.oldY
619 def getObjectBoundaryCircle(self):
620 return self.parent.objectsBoundaryCircleSize
622 def getObjectSize(self):
623 return self.parent.objectsSize
625 def getObjectMatrix(self):
626 return self.parent.matrix
628 def getObjectCenterPos(self):
629 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2]
631 def OnMouseWheel(self,e):
632 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
640 opengl.InitGL(self, self.view3D, self.zoom)
642 glTranslate(0,0,-self.zoom)
643 glTranslate(self.zoom/20.0,0,0)
644 glRotate(-self.pitch, 1,0,0)
645 glRotate(self.yaw, 0,0,1)
647 if self.viewMode == "GCode" or self.viewMode == "Mixed":
648 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:
649 self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z
651 if self.parent.objectsMaxV is not None:
652 self.viewTarget = self.getObjectCenterPos()
653 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
655 self.viewport = glGetIntegerv(GL_VIEWPORT)
656 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
657 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
662 machineSize = self.parent.machineSize
664 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
665 if self.parent.gcodeDirty:
666 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
667 if self.gcodeDisplayList is not None:
668 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
669 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
670 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
671 self.parent.gcodeDirty = False
672 self.gcodeDisplayListMade = 0
674 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
675 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
676 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
678 self.gcodeDisplayListMade += 1
679 wx.CallAfter(self.Refresh)
682 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
683 for obj in self.parent.objectList:
686 if obj.displayList is None:
687 obj.displayList = glGenLists(1)
688 obj.steepDisplayList = glGenLists(1)
689 obj.outlineDisplayList = glGenLists(1)
692 glNewList(obj.displayList, GL_COMPILE)
693 opengl.DrawMesh(obj.mesh)
695 glNewList(obj.outlineDisplayList, GL_COMPILE)
696 opengl.DrawMeshOutline(obj.mesh)
699 if self.viewMode == "Mixed":
701 glColor3f(0.0,0.0,0.0)
702 self.drawModel(obj.displayList)
703 glColor3f(1.0,1.0,1.0)
704 glClear(GL_DEPTH_BUFFER_BIT)
708 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
710 if profile.getPreference('machine_center_is_zero') == 'True':
711 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
712 glEnable(GL_COLOR_MATERIAL)
713 glEnable(GL_LIGHTING)
714 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
715 starttime = time.time()
716 for i in xrange(drawUpToLayer - 1, -1, -1):
718 if i < self.parent.layerSpin.GetValue():
719 c = 0.9 - (drawUpToLayer - i) * 0.1
724 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
725 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
726 glCallList(self.gcodeDisplayList + i)
727 if time.time() - starttime > 0.1:
730 glDisable(GL_LIGHTING)
731 glDisable(GL_COLOR_MATERIAL)
732 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
733 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
736 glColor3f(1.0,1.0,1.0)
738 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
739 for obj in self.parent.objectList:
743 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
744 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
745 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
746 #If we want transparent, then first render a solid black model to remove the printer size lines.
747 if self.viewMode != "Mixed":
749 glColor3f(0.0,0.0,0.0)
750 self.drawModel(obj.displayList)
751 glColor3f(1.0,1.0,1.0)
752 #After the black model is rendered, render the model again but now with lighting and no depth testing.
753 glDisable(GL_DEPTH_TEST)
754 glEnable(GL_LIGHTING)
756 glBlendFunc(GL_ONE, GL_ONE)
757 glEnable(GL_LIGHTING)
758 self.drawModel(obj.displayList)
759 glEnable(GL_DEPTH_TEST)
760 elif self.viewMode == "X-Ray":
761 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
762 glDisable(GL_LIGHTING)
763 glDisable(GL_DEPTH_TEST)
764 glEnable(GL_STENCIL_TEST)
765 glStencilFunc(GL_ALWAYS, 1, 1)
766 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
767 self.drawModel(obj.displayList)
768 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
770 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
771 glStencilFunc(GL_EQUAL, 0, 1)
773 self.drawModel(obj.displayList)
774 glStencilFunc(GL_EQUAL, 1, 1)
776 self.drawModel(obj.displayList)
780 for i in xrange(2, 15, 2):
781 glStencilFunc(GL_EQUAL, i, 0xFF);
782 glColor(float(i)/10, float(i)/10, float(i)/5)
784 glVertex3f(-1000,-1000,-1)
785 glVertex3f( 1000,-1000,-1)
786 glVertex3f( 1000, 1000,-1)
787 glVertex3f(-1000, 1000,-1)
789 for i in xrange(1, 15, 2):
790 glStencilFunc(GL_EQUAL, i, 0xFF);
791 glColor(float(i)/10, 0, 0)
793 glVertex3f(-1000,-1000,-1)
794 glVertex3f( 1000,-1000,-1)
795 glVertex3f( 1000, 1000,-1)
796 glVertex3f(-1000, 1000,-1)
800 glDisable(GL_STENCIL_TEST)
801 glEnable(GL_DEPTH_TEST)
803 #Fix the depth buffer
804 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
805 self.drawModel(obj.displayList)
806 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
807 elif self.viewMode == "Normal":
808 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
809 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
810 glEnable(GL_LIGHTING)
811 self.drawModel(obj.displayList)
813 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
814 glEnable(GL_DEPTH_TEST)
815 glDisable(GL_LIGHTING)
817 self.drawModel(obj.outlineDisplayList)
819 if self.drawSteepOverhang:
821 obj.steepDirty = False
822 glNewList(obj.steepDisplayList, GL_COMPILE)
823 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
825 glDisable(GL_LIGHTING)
827 self.drawModel(obj.steepDisplayList)
830 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
831 # glDisable(GL_LIGHTING)
832 # glDisable(GL_DEPTH_TEST)
833 # glDisable(GL_BLEND)
836 # for err in self.parent.errorList:
837 # glVertex3f(err[0].x, err[0].y, err[0].z)
838 # glVertex3f(err[1].x, err[1].y, err[1].z)
840 # glEnable(GL_DEPTH_TEST)
842 opengl.DrawMachine(machineSize)
844 #Draw the current selected tool
845 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
847 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2]/2)
848 self.parent.tool.OnDraw()
851 def drawModel(self, displayList):
852 vMin = self.parent.objectsMinV
853 vMax = self.parent.objectsMaxV
854 offset = - vMin - (vMax - vMin) / 2
856 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
859 glTranslate(0, 0, self.parent.objectsSize[2]/2)
860 if self.tempMatrix is not None:
861 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
862 glMultMatrixf(tempMatrix)
863 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
864 glTranslate(offset[0], offset[1], -vMin[2])
865 glMultMatrixf(matrix)
866 glCallList(displayList)