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 previewTools
26 from Cura.gui.util import openglGui
28 class previewObject():
32 self.displayList = None
35 class previewPanel(wx.Panel):
36 def __init__(self, parent):
37 super(previewPanel, self).__init__(parent,-1)
39 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
40 self.SetMinSize((440,320))
45 self.objectsMinV = None
46 self.objectsMaxV = None
47 self.objectsBoundaryCircleSize = None
48 self.loadThread = None
49 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
50 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
52 self.glCanvas = PreviewGLCanvas(self)
53 #Create the popup window
54 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
55 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
56 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
57 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
58 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
59 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
60 self.warningPopup.SetSizer(self.warningPopup.sizer)
61 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
62 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
63 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
64 self.warningPopup.Fit()
65 self.warningPopup.Layout()
66 self.warningPopup.timer = wx.Timer(self)
67 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
69 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
70 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
71 parent.Bind(wx.EVT_MOVE, self.OnMove)
72 parent.Bind(wx.EVT_SIZE, self.OnMove)
74 sizer = wx.BoxSizer(wx.VERTICAL)
75 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
78 self.checkReloadFileTimer = wx.Timer(self)
79 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
80 self.checkReloadFileTimer.Start(1000)
83 self.rotateToolButton = openglGui.glRadioButton(self.glCanvas, 1, 'Rotate', (0,-1), group, self.OnToolSelect)
84 self.scaleToolButton = openglGui.glRadioButton(self.glCanvas, 2, 'Scale', (1,-1), group, self.OnToolSelect)
85 self.mirrorToolButton = openglGui.glRadioButton(self.glCanvas, 12, 'Mirror', (2,-1), group, self.OnToolSelect)
87 self.resetRotationButton = openglGui.glButton(self.glCanvas, 4, 'Reset rotation', (0,-2), self.OnRotateReset)
88 self.layFlatButton = openglGui.glButton(self.glCanvas, 5, 'Lay flat', (0,-3), self.OnLayFlat)
90 self.resetScaleButton = openglGui.glButton(self.glCanvas, 8, 'Scale reset', (1,-2), self.OnScaleReset)
91 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 9, 'Scale to machine size', (1,-3), self.OnScaleMax)
93 self.mirrorXButton = openglGui.glButton(self.glCanvas, 12, 'Mirror X', (2,-2), lambda : self.OnMirror(0))
94 self.mirrorYButton = openglGui.glButton(self.glCanvas, 13, 'Mirror Y', (2,-3), lambda : self.OnMirror(1))
95 self.mirrorZButton = openglGui.glButton(self.glCanvas, 14, 'Mirror Z', (2,-4), lambda : self.OnMirror(2))
97 self.openFileButton = openglGui.glButton(self.glCanvas, 3, 'Load model', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
98 self.sliceButton = openglGui.glButton(self.glCanvas, 6, 'Prepare model', (1,0), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
99 self.printButton = openglGui.glButton(self.glCanvas, 7, 'Print model', (2,0), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
101 extruderCount = int(profile.getPreference('extruder_amount'))
102 if extruderCount > 1:
103 openglGui.glButton(self.glCanvas, 3, 'Load dual model', (1,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
104 if extruderCount > 2:
105 openglGui.glButton(self.glCanvas, 3, 'Load triple model', (2,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
106 if extruderCount > 3:
107 openglGui.glButton(self.glCanvas, 3, 'Load quad model', (3,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
109 self.scaleForm = openglGui.glFrame(self.glCanvas, (2, -3))
110 openglGui.glGuiLayoutGrid(self.scaleForm)
111 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
112 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
113 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
114 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
115 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
116 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
117 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
118 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
119 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
120 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
121 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
122 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
123 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
124 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
126 self.viewSelection = openglGui.glComboButton(self.glCanvas, 'View mode', [0,1,2,3,4], ['3D Model', 'Transparent', 'X-Ray', 'Overhang', 'Layers'], (-1,0), self.OnViewChange)
127 self.layerSelect = openglGui.glSlider(self.glCanvas, 0, 0, 100, (-1,-1), self.OnLayerNrChange)
131 self.returnToModelViewAndUpdateModel()
133 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
135 def returnToModelViewAndUpdateModel(self):
136 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
137 self.setViewMode('Normal')
138 self.updateModelTransform()
140 def OnToolSelect(self):
141 if self.rotateToolButton.getSelected():
142 self.tool = previewTools.toolRotate(self.glCanvas)
143 elif self.scaleToolButton.getSelected():
144 self.tool = previewTools.toolScale(self.glCanvas)
145 elif self.mirrorToolButton.getSelected():
146 self.tool = previewTools.toolNone(self.glCanvas)
148 self.tool = previewTools.toolNone(self.glCanvas)
149 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
150 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
151 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
152 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
153 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
154 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
155 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
156 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
157 self.returnToModelViewAndUpdateModel()
159 def OnScaleEntry(self, value, axis):
164 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
165 scale = value / scale
168 if self.scaleUniform.getValue():
169 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
171 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
172 matrix[axis][axis] = scale
173 self.matrix *= numpy.matrix(matrix, numpy.float64)
174 self.updateModelTransform()
176 def OnScaleEntryMM(self, value, axis):
181 scale = self.objectsSize[axis]
182 scale = value / scale
185 if self.scaleUniform.getValue():
186 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
188 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
189 matrix[axis][axis] = scale
190 self.matrix *= numpy.matrix(matrix, numpy.float64)
191 self.updateModelTransform()
193 def OnMirror(self, axis):
194 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
195 matrix[axis][axis] = -1
196 self.matrix *= numpy.matrix(matrix, numpy.float64)
197 for obj in self.objectList:
199 obj.steepDirty = True
200 self.updateModelTransform()
202 def OnMove(self, e = None):
205 x, y = self.glCanvas.ClientToScreenXY(0, 0)
206 sx, sy = self.glCanvas.GetClientSizeTuple()
207 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
209 def OnScaleMax(self, onlyScaleDown = False):
210 if self.objectsMinV is None:
212 vMin = self.objectsMinV
213 vMax = self.objectsMaxV
215 if profile.getProfileSettingFloat('skirt_line_count') > 0:
216 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
217 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
218 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
219 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
220 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
221 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
222 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
223 if scale > 1.0 and onlyScaleDown:
225 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
226 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
227 self.setViewMode('Normal')
228 self.updateModelTransform()
230 def OnRotateReset(self):
231 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
232 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
233 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
234 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
235 for obj in self.objectList:
236 obj.steepDirty = True
237 self.updateModelTransform()
239 def OnScaleReset(self):
240 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
241 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
242 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
243 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
244 for obj in self.objectList:
245 obj.steepDirty = True
246 self.updateModelTransform()
249 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
250 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
253 for v in transformedVertexes:
254 diff = v - minZvertex
255 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
258 dot = (diff[2] / len)
264 rad = -math.atan2(dotV[1], dotV[0])
265 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
266 rad = -math.asin(dotMin)
267 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
270 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
271 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
274 for v in transformedVertexes:
275 diff = v - minZvertex
276 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
279 dot = (diff[2] / len)
286 rad = math.asin(dotMin)
288 rad = -math.asin(dotMin)
289 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
291 for obj in self.objectList:
292 obj.steepDirty = True
293 self.updateModelTransform()
296 self.glCanvas.yaw = 30
297 self.glCanvas.pitch = 60
298 self.glCanvas.zoom = 300
299 self.glCanvas.view3D = True
300 self.glCanvas.Refresh()
302 def OnTopClick(self):
303 self.glCanvas.view3D = False
304 self.glCanvas.zoom = 100
305 self.glCanvas.offsetX = 0
306 self.glCanvas.offsetY = 0
307 self.glCanvas.Refresh()
309 def OnLayerNrChange(self):
310 self.glCanvas.Refresh()
312 def setViewMode(self, mode):
314 self.viewSelection.setValue(0)
316 self.viewSelection.setValue(5)
317 wx.CallAfter(self.glCanvas.Refresh)
319 def loadModelFiles(self, filelist, showWarning = False):
320 while len(filelist) > len(self.objectList):
321 self.objectList.append(previewObject())
322 for idx in xrange(len(filelist), len(self.objectList)):
323 self.objectList[idx].mesh = None
324 self.objectList[idx].filename = None
325 for idx in xrange(0, len(filelist)):
326 obj = self.objectList[idx]
327 if obj.filename != filelist[idx]:
329 self.gcodeFileTime = None
330 self.logFileTime = None
331 obj.filename = filelist[idx]
333 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
334 #Do the STL file loading in a background thread so we don't block the UI.
335 if self.loadThread is not None and self.loadThread.isAlive():
336 self.abortLoading = True
337 self.loadThread.join()
338 self.abortLoading = False
339 self.loadThread = threading.Thread(target=self.doFileLoadThread)
340 self.loadThread.daemon = True
341 self.loadThread.start()
344 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
345 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
347 def OnCheckReloadFile(self, e):
348 #Only show the reload popup when the window has focus, because the popup goes over other programs.
349 if self.GetParent().FindFocus() is None:
351 for obj in self.objectList:
352 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
353 self.checkReloadFileTimer.Stop()
354 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
355 if wx.TheClipboard.Open():
356 data = wx.TextDataObject()
357 if wx.TheClipboard.GetData(data):
358 data = data.GetText()
359 if re.match('^http://.*/.*$', data):
360 if data.endswith(tuple(meshLoader.supportedExtensions())):
361 #Got an url on the clipboard with a model file.
363 wx.TheClipboard.Close()
365 def reloadModelFiles(self, filelist = None):
366 if filelist is not None:
367 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
368 for idx in xrange(0, len(filelist)):
369 if self.objectList[idx].filename != filelist[idx]:
373 for idx in xrange(0, len(self.objectList)):
374 filelist.append(self.objectList[idx].filename)
375 self.loadModelFiles(filelist)
378 def doFileLoadThread(self):
379 for obj in self.objectList:
380 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
381 obj.fileTime = os.stat(obj.filename).st_mtime
382 mesh = meshLoader.loadMesh(obj.filename)
385 obj.steepDirty = True
386 self.updateModelTransform()
387 self.OnScaleMax(True)
388 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
390 wx.CallAfter(self.updateToolbar)
391 wx.CallAfter(self.glCanvas.Refresh)
393 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
394 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
395 gcode = gcodeInterpreter.gcode()
396 gcode.progressCallback = self.loadProgress
397 gcode.load(self.gcodeFilename)
398 self.gcodeDirty = False
400 self.gcodeDirty = True
403 for line in open(self.gcodeFilename, "rt"):
404 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)
406 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
407 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
408 errorList.append([v1, v2])
409 self.errorList = errorList
411 wx.CallAfter(self.updateToolbar)
412 wx.CallAfter(self.glCanvas.Refresh)
413 elif not os.path.isfile(self.gcodeFilename):
415 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
417 def loadProgress(self, progress):
418 return self.abortLoading
420 def OnResetAll(self, e = None):
421 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
422 profile.setPluginConfig([])
423 self.updateProfileToControls()
425 def ShowWarningPopup(self, text, callback = None):
426 self.warningPopup.text.SetLabel(text)
427 self.warningPopup.callback = callback
429 self.warningPopup.yesButton.Show(False)
430 self.warningPopup.noButton.SetLabel('ok')
432 self.warningPopup.yesButton.Show(True)
433 self.warningPopup.noButton.SetLabel('no')
434 self.warningPopup.Fit()
435 self.warningPopup.Layout()
437 self.warningPopup.Show(True)
438 self.warningPopup.timer.Start(5000)
440 def OnWarningPopup(self, e):
441 self.warningPopup.Show(False)
442 self.warningPopup.timer.Stop()
443 self.warningPopup.callback()
445 def OnHideWarning(self, e):
446 self.warningPopup.Show(False)
447 self.warningPopup.timer.Stop()
449 def updateToolbar(self):
450 self.printButton.setDisabled(self.gcode is None)
451 if self.gcode is not None:
452 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
455 def OnViewChange(self):
456 selection = self.viewSelection.getValue()
457 self.glCanvas.drawSteepOverhang = False
458 self.glCanvas.drawBorders = False
460 self.glCanvas.viewMode = "Normal"
462 self.glCanvas.viewMode = "Transparent"
464 self.glCanvas.viewMode = "X-Ray"
466 self.glCanvas.viewMode = "Normal"
467 self.glCanvas.drawSteepOverhang = True
469 self.layerSelect.setValue(self.layerSelect.getMaxValue())
470 self.glCanvas.viewMode = "GCode"
472 self.glCanvas.viewMode = "Mixed"
473 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
475 self.glCanvas.Refresh()
477 def updateModelTransform(self, f=0):
478 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
481 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
482 for obj in self.objectList:
485 obj.mesh.matrix = self.matrix
486 obj.mesh.processMatrix()
488 minV = self.objectList[0].mesh.getMinimum()
489 maxV = self.objectList[0].mesh.getMaximum()
490 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
491 for obj in self.objectList:
495 minV = numpy.minimum(minV, obj.mesh.getMinimum())
496 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
497 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
499 self.objectsMaxV = maxV
500 self.objectsMinV = minV
501 self.objectsSize = self.objectsMaxV - self.objectsMinV
502 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
504 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
505 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
506 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
507 self.scaleXctrl.setValue(round(scaleX, 2))
508 self.scaleYctrl.setValue(round(scaleY, 2))
509 self.scaleZctrl.setValue(round(scaleZ, 2))
510 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
511 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
512 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
514 self.glCanvas.Refresh()
516 def updateProfileToControls(self):
517 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
518 self.updateModelTransform()
519 for obj in self.objectList:
520 obj.steepDirty = True
521 self.glCanvas.updateProfileToControls()
523 class PreviewGLCanvas(openglGui.glGuiPanel):
524 def __init__(self, parent):
525 super(PreviewGLCanvas, self).__init__(parent)
526 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
531 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
533 self.gcodeDisplayList = None
534 self.gcodeDisplayListMade = None
535 self.gcodeDisplayListCount = 0
536 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]]
540 self.tempMatrix = None
543 def updateProfileToControls(self):
544 self.objColor[0] = profile.getPreferenceColour('model_colour')
545 self.objColor[1] = profile.getPreferenceColour('model_colour2')
546 self.objColor[2] = profile.getPreferenceColour('model_colour3')
547 self.objColor[3] = profile.getPreferenceColour('model_colour4')
549 def OnMouseMotion(self,e):
550 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
551 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
552 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
553 p0 -= self.viewTarget
554 p1 -= self.viewTarget
555 if not e.Dragging() or self.dragType != 'tool':
556 self.parent.tool.OnMouseMove(p0, p1)
561 if e.Dragging() and e.LeftIsDown():
562 if self.dragType == '':
563 #Define the drag type depending on the cursor position.
564 self.dragType = 'viewRotate'
565 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
566 if self.parent.tool.OnDragStart(p0, p1):
567 self.dragType = 'tool'
569 if self.dragType == 'viewRotate':
571 self.yaw += e.GetX() - self.oldX
572 self.pitch -= e.GetY() - self.oldY
578 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
579 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
580 elif self.dragType == 'tool':
581 self.parent.tool.OnDrag(p0, p1)
583 #Workaround for buggy ATI cards.
584 size = self.GetSizeTuple()
585 self.SetSize((size[0]+1, size[1]))
586 self.SetSize((size[0], size[1]))
589 if self.dragType != '':
590 if self.tempMatrix is not None:
591 self.parent.matrix *= self.tempMatrix
592 self.parent.updateModelTransform()
593 self.tempMatrix = None
594 for obj in self.parent.objectList:
595 obj.steepDirty = True
596 self.parent.tool.OnDragEnd()
598 if e.Dragging() and e.RightIsDown():
599 self.zoom += e.GetY() - self.oldY
607 def getObjectBoundaryCircle(self):
608 return self.parent.objectsBoundaryCircleSize
610 def getObjectSize(self):
611 return self.parent.objectsSize
613 def getObjectMatrix(self):
614 return self.parent.matrix
616 def getObjectCenterPos(self):
617 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
619 def OnMouseWheel(self,e):
620 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
628 opengl.InitGL(self, self.view3D, self.zoom)
630 glTranslate(0,0,-self.zoom)
631 glTranslate(self.zoom/20.0,0,0)
632 glRotate(-self.pitch, 1,0,0)
633 glRotate(self.yaw, 0,0,1)
635 if self.viewMode == "GCode" or self.viewMode == "Mixed":
636 if self.parent.gcode is not None and len(self.parent.gcode.layerList) > self.parent.layerSelect.getValue() and len(self.parent.gcode.layerList[self.parent.layerSelect.getValue()]) > 0:
637 self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSelect.getValue()][0].list[-1].z
639 if self.parent.objectsMaxV is not None:
640 self.viewTarget = self.getObjectCenterPos()
641 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
643 self.viewport = glGetIntegerv(GL_VIEWPORT)
644 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
645 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
650 machineSize = self.parent.machineSize
652 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
653 if self.parent.gcodeDirty:
654 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
655 if self.gcodeDisplayList is not None:
656 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
657 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
658 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
659 self.parent.gcodeDirty = False
660 self.gcodeDisplayListMade = 0
662 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
663 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
664 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
666 self.gcodeDisplayListMade += 1
667 wx.CallAfter(self.Refresh)
670 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
671 for obj in self.parent.objectList:
674 if obj.displayList is None:
675 obj.displayList = glGenLists(1)
676 obj.steepDisplayList = glGenLists(1)
677 obj.outlineDisplayList = glGenLists(1)
680 glNewList(obj.displayList, GL_COMPILE)
681 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
683 glNewList(obj.outlineDisplayList, GL_COMPILE)
684 opengl.DrawMeshOutline(obj.mesh)
687 if self.viewMode == "Mixed":
689 glColor3f(0.0,0.0,0.0)
690 self.drawModel(obj.displayList)
691 glColor3f(1.0,1.0,1.0)
692 glClear(GL_DEPTH_BUFFER_BIT)
696 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
698 if profile.getPreference('machine_center_is_zero') == 'True':
699 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
700 glEnable(GL_COLOR_MATERIAL)
701 glEnable(GL_LIGHTING)
702 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
703 starttime = time.time()
704 for i in xrange(drawUpToLayer - 1, -1, -1):
706 if i < self.parent.layerSelect.getValue():
707 c = 0.9 - (drawUpToLayer - i) * 0.1
712 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
713 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
714 glCallList(self.gcodeDisplayList + i)
715 if time.time() - starttime > 0.1:
718 glDisable(GL_LIGHTING)
719 glDisable(GL_COLOR_MATERIAL)
720 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
721 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
724 glColor3f(1.0,1.0,1.0)
726 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
727 for obj in self.parent.objectList:
731 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
732 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
733 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
734 #If we want transparent, then first render a solid black model to remove the printer size lines.
735 if self.viewMode != "Mixed":
737 glColor3f(0.0,0.0,0.0)
738 self.drawModel(obj.displayList)
739 glColor3f(1.0,1.0,1.0)
740 #After the black model is rendered, render the model again but now with lighting and no depth testing.
741 glDisable(GL_DEPTH_TEST)
742 glEnable(GL_LIGHTING)
744 glBlendFunc(GL_ONE, GL_ONE)
745 glEnable(GL_LIGHTING)
746 self.drawModel(obj.displayList)
747 glEnable(GL_DEPTH_TEST)
748 elif self.viewMode == "X-Ray":
749 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
750 glDisable(GL_LIGHTING)
751 glDisable(GL_DEPTH_TEST)
752 glEnable(GL_STENCIL_TEST)
753 glStencilFunc(GL_ALWAYS, 1, 1)
754 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
755 self.drawModel(obj.displayList)
756 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
758 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
759 glStencilFunc(GL_EQUAL, 0, 1)
761 self.drawModel(obj.displayList)
762 glStencilFunc(GL_EQUAL, 1, 1)
764 self.drawModel(obj.displayList)
768 for i in xrange(2, 15, 2):
769 glStencilFunc(GL_EQUAL, i, 0xFF);
770 glColor(float(i)/10, float(i)/10, float(i)/5)
772 glVertex3f(-1000,-1000,-1)
773 glVertex3f( 1000,-1000,-1)
774 glVertex3f( 1000, 1000,-1)
775 glVertex3f(-1000, 1000,-1)
777 for i in xrange(1, 15, 2):
778 glStencilFunc(GL_EQUAL, i, 0xFF);
779 glColor(float(i)/10, 0, 0)
781 glVertex3f(-1000,-1000,-1)
782 glVertex3f( 1000,-1000,-1)
783 glVertex3f( 1000, 1000,-1)
784 glVertex3f(-1000, 1000,-1)
788 glDisable(GL_STENCIL_TEST)
789 glEnable(GL_DEPTH_TEST)
791 #Fix the depth buffer
792 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
793 self.drawModel(obj.displayList)
794 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
795 elif self.viewMode == "Normal":
796 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
797 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
798 glEnable(GL_LIGHTING)
799 self.drawModel(obj.displayList)
801 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
802 glEnable(GL_DEPTH_TEST)
803 glDisable(GL_LIGHTING)
805 self.drawModel(obj.outlineDisplayList)
807 if self.drawSteepOverhang:
809 obj.steepDirty = False
810 glNewList(obj.steepDisplayList, GL_COMPILE)
811 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
813 glDisable(GL_LIGHTING)
815 self.drawModel(obj.steepDisplayList)
818 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
819 # glDisable(GL_LIGHTING)
820 # glDisable(GL_DEPTH_TEST)
821 # glDisable(GL_BLEND)
824 # for err in self.parent.errorList:
825 # glVertex3f(err[0].x, err[0].y, err[0].z)
826 # glVertex3f(err[1].x, err[1].y, err[1].z)
828 # glEnable(GL_DEPTH_TEST)
830 opengl.DrawMachine(machineSize)
832 #Draw the current selected tool
833 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
835 pos = self.getObjectCenterPos()
836 glTranslate(pos[0], pos[1], pos[2])
837 self.parent.tool.OnDraw()
840 def drawModel(self, displayList):
841 vMin = self.parent.objectsMinV
842 vMax = self.parent.objectsMaxV
843 offset = - vMin - (vMax - vMin) / 2
845 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
848 glTranslate(0, 0, self.parent.objectsSize[2]/2)
849 if self.tempMatrix is not None:
850 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
851 glMultMatrixf(tempMatrix)
852 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
853 glTranslate(offset[0], offset[1], -vMin[2])
854 glMultMatrixf(matrix)
855 glCallList(displayList)