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, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
84 self.scaleToolButton = openglGui.glRadioButton(self.glCanvas, 9, 'Scale', (1,-1), group, self.OnToolSelect)
85 self.mirrorToolButton = openglGui.glRadioButton(self.glCanvas, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
87 self.resetRotationButton = openglGui.glButton(self.glCanvas, 12, 'Reset', (0,-2), self.OnRotateReset)
88 self.layFlatButton = openglGui.glButton(self.glCanvas, 16, 'Lay flat', (0,-3), self.OnLayFlat)
90 self.resetScaleButton = openglGui.glButton(self.glCanvas, 13, 'Reset', (1,-2), self.OnScaleReset)
91 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 17, 'To max', (1,-3), self.OnScaleMax)
93 self.mirrorXButton = openglGui.glButton(self.glCanvas, 14, 'Mirror X', (2,-2), lambda : self.OnMirror(0))
94 self.mirrorYButton = openglGui.glButton(self.glCanvas, 18, 'Mirror Y', (2,-3), lambda : self.OnMirror(1))
95 self.mirrorZButton = openglGui.glButton(self.glCanvas, 22, 'Mirror Z', (2,-4), lambda : self.OnMirror(2))
97 self.openFileButton = openglGui.glButton(self.glCanvas, 4, 'Load', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
98 self.sliceButton = openglGui.glButton(self.glCanvas, 5, 'Prepare', (1,0), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
99 self.printButton = openglGui.glButton(self.glCanvas, 6, 'Print', (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, 4, 'Load dual', (0,1), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
104 if extruderCount > 2:
105 openglGui.glButton(self.glCanvas, 4, 'Load triple', (0,2), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
106 if extruderCount > 3:
107 openglGui.glButton(self.glCanvas, 4, 'Load quad', (0,3), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
109 self.scaleForm = openglGui.glFrame(self.glCanvas, (2, -2))
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', [7,11,15,19,23], ['Normal', 'Transparent', 'X-Ray', 'Overhang', 'Layers'], (-1,0), self.OnViewChange)
127 self.layerSelect = openglGui.glSlider(self.glCanvas, 0, 0, 100, (-1,-2), lambda : self.Refresh())
131 self.updateModelTransform()
133 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
135 def OnToolSelect(self):
136 if self.rotateToolButton.getSelected():
137 self.tool = previewTools.toolRotate(self.glCanvas)
138 elif self.scaleToolButton.getSelected():
139 self.tool = previewTools.toolScale(self.glCanvas)
140 elif self.mirrorToolButton.getSelected():
141 self.tool = previewTools.toolNone(self.glCanvas)
143 self.tool = previewTools.toolNone(self.glCanvas)
144 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
145 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
146 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
147 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
148 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
149 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
150 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
151 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
152 self.updateModelTransform()
154 def OnScaleEntry(self, value, axis):
159 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
160 scale = value / scale
163 if self.scaleUniform.getValue():
164 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
166 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
167 matrix[axis][axis] = scale
168 self.matrix *= numpy.matrix(matrix, numpy.float64)
169 self.updateModelTransform()
171 def OnScaleEntryMM(self, value, axis):
176 scale = self.objectsSize[axis]
177 scale = value / scale
180 if self.scaleUniform.getValue():
181 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
183 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
184 matrix[axis][axis] = scale
185 self.matrix *= numpy.matrix(matrix, numpy.float64)
186 self.updateModelTransform()
188 def OnMirror(self, axis):
189 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
190 matrix[axis][axis] = -1
191 self.matrix *= numpy.matrix(matrix, numpy.float64)
192 for obj in self.objectList:
194 obj.steepDirty = True
195 self.updateModelTransform()
197 def OnMove(self, e = None):
200 x, y = self.glCanvas.ClientToScreenXY(0, 0)
201 sx, sy = self.glCanvas.GetClientSizeTuple()
202 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
204 def OnScaleMax(self):
205 if self.objectsMinV is None:
207 vMin = self.objectsMinV
208 vMax = self.objectsMaxV
210 if profile.getProfileSettingFloat('skirt_line_count') > 0:
211 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
212 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
213 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
214 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
215 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
216 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
217 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
218 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
219 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
220 self.setViewMode('Normal')
221 self.updateModelTransform()
223 def OnRotateReset(self):
224 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
225 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
226 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
227 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
228 for obj in self.objectList:
229 obj.steepDirty = True
230 self.updateModelTransform()
232 def OnScaleReset(self):
233 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
234 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
235 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
236 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
237 for obj in self.objectList:
238 obj.steepDirty = True
239 self.updateModelTransform()
242 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
243 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
246 for v in transformedVertexes:
247 diff = v - minZvertex
248 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
251 dot = (diff[2] / len)
257 rad = -math.atan2(dotV[1], dotV[0])
258 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
259 rad = -math.asin(dotMin)
260 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
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[1] * diff[1] + diff[2] * diff[2])
272 dot = (diff[2] / len)
279 rad = math.asin(dotMin)
281 rad = -math.asin(dotMin)
282 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
284 for obj in self.objectList:
285 obj.steepDirty = True
286 self.updateModelTransform()
288 def setViewMode(self, mode):
290 self.viewSelection.setValue(0)
292 self.viewSelection.setValue(4)
293 wx.CallAfter(self.glCanvas.Refresh)
295 def loadModelFiles(self, filelist, showWarning = False):
296 while len(filelist) > len(self.objectList):
297 self.objectList.append(previewObject())
298 for idx in xrange(len(filelist), len(self.objectList)):
299 self.objectList[idx].mesh = None
300 self.objectList[idx].filename = None
301 for idx in xrange(0, len(filelist)):
302 obj = self.objectList[idx]
303 if obj.filename != filelist[idx]:
305 self.gcodeFileTime = None
306 self.logFileTime = None
307 obj.filename = filelist[idx]
311 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
313 #Do the STL file loading in a background thread so we don't block the UI.
314 if self.loadThread is not None and self.loadThread.isAlive():
315 self.abortLoading = True
316 self.loadThread.join()
317 self.abortLoading = False
318 self.loadThread = threading.Thread(target=self.doFileLoadThread)
319 self.loadThread.daemon = True
320 self.loadThread.start()
323 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
324 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
326 def OnCheckReloadFile(self, e):
327 #Only show the reload popup when the window has focus, because the popup goes over other programs.
328 if self.GetParent().FindFocus() is None:
330 for obj in self.objectList:
331 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
332 self.checkReloadFileTimer.Stop()
333 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
334 if wx.TheClipboard.Open():
335 data = wx.TextDataObject()
336 if wx.TheClipboard.GetData(data):
337 data = data.GetText()
338 if re.match('^http://.*/.*$', data):
339 if data.endswith(tuple(meshLoader.supportedExtensions())):
340 #Got an url on the clipboard with a model file.
342 wx.TheClipboard.Close()
344 def reloadModelFiles(self, filelist = None):
345 if filelist is not None:
346 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
347 for idx in xrange(0, len(filelist)):
348 if self.objectList[idx].filename != filelist[idx]:
352 for idx in xrange(0, len(self.objectList)):
353 filelist.append(self.objectList[idx].filename)
354 self.loadModelFiles(filelist)
357 def doFileLoadThread(self):
358 for obj in self.objectList:
359 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
360 obj.fileTime = os.stat(obj.filename).st_mtime
361 mesh = meshLoader.loadMesh(obj.filename)
364 obj.steepDirty = True
365 self.updateModelTransform()
366 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
368 wx.CallAfter(self.updateToolbar)
369 wx.CallAfter(self.glCanvas.Refresh)
371 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
372 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
373 self.gcodeDirty = True
374 self.gcode = gcodeInterpreter.gcode()
375 self.gcode.progressCallback = self.loadProgress
376 self.gcode.load(self.gcodeFilename)
379 for line in open(self.gcodeFilename, "rt"):
380 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)
382 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
383 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
384 errorList.append([v1, v2])
385 self.errorList = errorList
387 wx.CallAfter(self.updateToolbar)
388 wx.CallAfter(self.glCanvas.Refresh)
389 elif not os.path.isfile(self.gcodeFilename):
391 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
393 def loadProgress(self, progress):
394 if self.gcode is None:
396 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
397 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
398 self.layerSelect.setValue(self.layerSelect.getMaxValue())
400 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
401 return self.abortLoading
403 def OnResetAll(self, e = None):
404 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
405 profile.setPluginConfig([])
406 self.updateProfileToControls()
408 def ShowWarningPopup(self, text, callback = None):
409 self.warningPopup.text.SetLabel(text)
410 self.warningPopup.callback = callback
412 self.warningPopup.yesButton.Show(False)
413 self.warningPopup.noButton.SetLabel('ok')
415 self.warningPopup.yesButton.Show(True)
416 self.warningPopup.noButton.SetLabel('no')
417 self.warningPopup.Fit()
418 self.warningPopup.Layout()
420 self.warningPopup.Show(True)
421 self.warningPopup.timer.Start(5000)
423 def OnWarningPopup(self, e):
424 self.warningPopup.Show(False)
425 self.warningPopup.timer.Stop()
426 self.warningPopup.callback()
428 def OnHideWarning(self, e):
429 self.warningPopup.Show(False)
430 self.warningPopup.timer.Stop()
432 def updateToolbar(self):
433 self.sliceButton.setDisabled(len(self.objectList) < 1 or self.objectList[0].mesh is None)
434 self.printButton.setDisabled(self.gcode is None)
435 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
436 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
437 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
438 if self.gcode is not None:
439 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
442 def OnViewChange(self):
443 selection = self.viewSelection.getValue()
444 self.glCanvas.drawSteepOverhang = False
445 self.glCanvas.drawBorders = False
447 self.glCanvas.viewMode = "Normal"
449 self.glCanvas.viewMode = "Transparent"
451 self.glCanvas.viewMode = "X-Ray"
453 self.glCanvas.viewMode = "Normal"
454 self.glCanvas.drawSteepOverhang = True
456 self.layerSelect.setValue(self.layerSelect.getMaxValue())
457 self.glCanvas.viewMode = "GCode"
459 self.glCanvas.viewMode = "Mixed"
460 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
463 self.glCanvas.Refresh()
465 def deselectTool(self):
466 self.rotateToolButton.setSelected(False)
467 self.scaleToolButton.setSelected(False)
468 self.mirrorToolButton.setSelected(False)
471 def updateModelTransform(self, f=0):
472 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
475 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
476 for obj in self.objectList:
479 obj.mesh.matrix = self.matrix
480 obj.mesh.processMatrix()
482 minV = self.objectList[0].mesh.getMinimum()
483 maxV = self.objectList[0].mesh.getMaximum()
484 objectsBoundaryCircleSize = self.objectList[0].mesh.boundaryCircleSize
485 for obj in self.objectList:
489 minV = numpy.minimum(minV, obj.mesh.getMinimum())
490 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
491 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.boundaryCircleSize)
493 self.objectsMaxV = maxV
494 self.objectsMinV = minV
495 self.objectsSize = self.objectsMaxV - self.objectsMinV
496 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
498 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
499 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
500 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
501 self.scaleXctrl.setValue(round(scaleX, 2))
502 self.scaleYctrl.setValue(round(scaleY, 2))
503 self.scaleZctrl.setValue(round(scaleZ, 2))
504 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
505 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
506 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
508 self.glCanvas.Refresh()
510 def updateProfileToControls(self):
511 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
512 self.updateModelTransform()
513 for obj in self.objectList:
514 obj.steepDirty = True
515 self.glCanvas.updateProfileToControls()
517 class PreviewGLCanvas(openglGui.glGuiPanel):
518 def __init__(self, parent):
519 super(PreviewGLCanvas, self).__init__(parent)
520 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
525 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
527 self.gcodeDisplayList = []
528 self.gcodeQuickDisplayList = []
529 self.gcodeDisplayListMade = 0
530 self.gcodeQuickDisplayListMade = 0
531 self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]]
535 self.tempMatrix = None
538 def updateProfileToControls(self):
539 self.objColor[0] = profile.getPreferenceColour('model_colour')
540 self.objColor[1] = profile.getPreferenceColour('model_colour2')
541 self.objColor[2] = profile.getPreferenceColour('model_colour3')
542 self.objColor[3] = profile.getPreferenceColour('model_colour4')
544 def OnMouseMotion(self,e):
545 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
546 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
547 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
548 p0 -= self.viewTarget
549 p1 -= self.viewTarget
550 if not e.Dragging() or self.dragType != 'tool':
551 self.parent.tool.OnMouseMove(p0, p1)
556 if e.Dragging() and e.LeftIsDown():
557 if self.dragType == '':
558 #Define the drag type depending on the cursor position.
559 self.dragType = 'viewRotate'
560 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
561 if self.parent.tool.OnDragStart(p0, p1):
562 self.dragType = 'tool'
564 if self.dragType == 'viewRotate':
566 self.yaw += e.GetX() - self.oldX
567 self.pitch -= e.GetY() - self.oldY
573 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
574 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
575 elif self.dragType == 'tool':
576 self.parent.tool.OnDrag(p0, p1)
578 #Workaround for buggy ATI cards.
579 size = self.GetSizeTuple()
580 self.SetSize((size[0]+1, size[1]))
581 self.SetSize((size[0], size[1]))
584 if self.dragType != '':
585 if self.tempMatrix is not None:
586 self.parent.matrix *= self.tempMatrix
587 self.parent.updateModelTransform()
588 self.tempMatrix = None
589 for obj in self.parent.objectList:
590 obj.steepDirty = True
591 self.parent.tool.OnDragEnd()
593 if e.Dragging() and e.RightIsDown():
594 self.zoom += e.GetY() - self.oldY
602 def getObjectBoundaryCircle(self):
603 return self.parent.objectsBoundaryCircleSize
605 def getObjectSize(self):
606 return self.parent.objectsSize
608 def getObjectMatrix(self):
609 return self.parent.matrix
611 def getObjectCenterPos(self):
612 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
614 def OnMouseWheel(self,e):
615 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
623 opengl.InitGL(self, self.view3D, self.zoom)
625 glTranslate(0,0,-self.zoom)
626 glTranslate(self.zoom/20.0,0,0)
627 glRotate(-self.pitch, 1,0,0)
628 glRotate(self.yaw, 0,0,1)
630 if self.viewMode == "GCode" or self.viewMode == "Mixed":
631 n = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue())
632 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
633 self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
635 if self.parent.objectsMaxV is not None:
636 self.viewTarget = self.getObjectCenterPos()
637 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
639 self.viewport = glGetIntegerv(GL_VIEWPORT)
640 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
641 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
645 if len(self.parent.objectList) > 0 and self.parent.objectList[0].mesh is None:
646 glDisable(GL_DEPTH_TEST)
648 glColor3ub(255,255,255)
649 glTranslate(0, -3, -10)
650 opengl.glDrawStringCenter('Loading %s ...' % (os.path.basename(self.parent.objectList[0].filename)))
653 machineSize = self.parent.machineSize
655 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
656 if self.parent.gcodeDirty:
657 self.parent.gcodeDirty = False
658 self.gcodeDisplayListMade = 0
659 self.gcodeQuickDisplayListMade = 0
661 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
662 gcodeGenStartTime = time.time()
663 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeQuickDisplayListMade < len(self.parent.gcode.layerList):
664 if len(self.gcodeQuickDisplayList) == self.gcodeQuickDisplayListMade:
665 self.gcodeQuickDisplayList.append(glGenLists(1))
666 glNewList(self.gcodeQuickDisplayList[self.gcodeQuickDisplayListMade], GL_COMPILE)
667 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeQuickDisplayListMade], True)
669 self.gcodeQuickDisplayListMade += 1
670 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
671 if len(self.gcodeDisplayList) == self.gcodeDisplayListMade:
672 self.gcodeDisplayList.append(glGenLists(1))
673 glNewList(self.gcodeDisplayList[self.gcodeDisplayListMade], GL_COMPILE)
674 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade], False)
676 self.gcodeDisplayListMade += 1
677 wx.CallAfter(self.Refresh)
680 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
681 for obj in self.parent.objectList:
684 if obj.displayList is None:
685 obj.displayList = glGenLists(1)
686 obj.steepDisplayList = glGenLists(1)
687 obj.outlineDisplayList = glGenLists(1)
690 glNewList(obj.displayList, GL_COMPILE)
691 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
693 glNewList(obj.outlineDisplayList, GL_COMPILE)
694 opengl.DrawMeshOutline(obj.mesh)
697 if self.viewMode == "Mixed":
699 glColor3f(0.0,0.0,0.0)
700 self.drawModel(obj.displayList)
701 glColor3f(1.0,1.0,1.0)
702 glClear(GL_DEPTH_BUFFER_BIT)
706 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
708 if profile.getPreference('machine_center_is_zero') == 'True':
709 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
710 glEnable(GL_COLOR_MATERIAL)
711 glEnable(GL_LIGHTING)
712 drawQuickUpToLayer = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue() + 1)
713 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
715 for i in xrange(drawQuickUpToLayer - 1, -1, -1):
717 if i < self.parent.layerSelect.getValue():
718 c = 0.9 - (drawQuickUpToLayer - i) * 0.1
723 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
724 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
725 if self.gcodeDisplayListMade > i and drawUpToLayer - i < 15:
726 glCallList(self.gcodeDisplayList[i])
728 glCallList(self.gcodeQuickDisplayList[i])
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, -profile.getProfileSettingFloat('object_sink'))
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 pos = self.getObjectCenterPos()
848 glTranslate(pos[0], pos[1], pos[2])
849 self.parent.tool.OnDraw()
852 def drawModel(self, displayList):
853 vMin = self.parent.objectsMinV
854 vMax = self.parent.objectsMaxV
857 offset = - vMin - (vMax - vMin) / 2
859 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
862 glTranslate(0, 0, self.parent.objectsSize[2]/2)
863 if self.tempMatrix is not None:
864 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
865 glMultMatrixf(tempMatrix)
866 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
867 glTranslate(offset[0], offset[1], -vMin[2])
868 glMultMatrixf(matrix)
869 glCallList(displayList)