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 model', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
98 self.sliceButton = openglGui.glButton(self.glCanvas, 5, 'Prepare model', (1,0), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
99 self.printButton = openglGui.glButton(self.glCanvas, 6, '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, 4, 'Load dual model', (0,1), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
104 if extruderCount > 2:
105 openglGui.glButton(self.glCanvas, 4, 'Load triple model', (0,2), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
106 if extruderCount > 3:
107 openglGui.glButton(self.glCanvas, 4, 'Load quad model', (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], ['3D Model', 'Transparent', 'X-Ray', 'Overhang', 'Layers'], (-1,0), self.OnViewChange)
127 self.layerSelect = openglGui.glSlider(self.glCanvas, 0, 0, 100, (-1,-2), self.OnLayerNrChange)
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()
289 self.glCanvas.yaw = 30
290 self.glCanvas.pitch = 60
291 self.glCanvas.zoom = 300
292 self.glCanvas.view3D = True
293 self.glCanvas.Refresh()
295 def OnTopClick(self):
296 self.glCanvas.view3D = False
297 self.glCanvas.zoom = 100
298 self.glCanvas.offsetX = 0
299 self.glCanvas.offsetY = 0
300 self.glCanvas.Refresh()
302 def OnLayerNrChange(self):
303 self.glCanvas.Refresh()
305 def setViewMode(self, mode):
307 self.viewSelection.setValue(0)
309 self.viewSelection.setValue(4)
310 wx.CallAfter(self.glCanvas.Refresh)
312 def loadModelFiles(self, filelist, showWarning = False):
313 while len(filelist) > len(self.objectList):
314 self.objectList.append(previewObject())
315 for idx in xrange(len(filelist), len(self.objectList)):
316 self.objectList[idx].mesh = None
317 self.objectList[idx].filename = None
318 for idx in xrange(0, len(filelist)):
319 obj = self.objectList[idx]
320 if obj.filename != filelist[idx]:
322 self.gcodeFileTime = None
323 self.logFileTime = None
324 obj.filename = filelist[idx]
327 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
328 #Do the STL file loading in a background thread so we don't block the UI.
329 if self.loadThread is not None and self.loadThread.isAlive():
330 self.abortLoading = True
331 self.loadThread.join()
332 self.abortLoading = False
333 self.loadThread = threading.Thread(target=self.doFileLoadThread)
334 self.loadThread.daemon = True
335 self.loadThread.start()
338 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
339 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
341 def OnCheckReloadFile(self, e):
342 #Only show the reload popup when the window has focus, because the popup goes over other programs.
343 if self.GetParent().FindFocus() is None:
345 for obj in self.objectList:
346 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
347 self.checkReloadFileTimer.Stop()
348 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
349 if wx.TheClipboard.Open():
350 data = wx.TextDataObject()
351 if wx.TheClipboard.GetData(data):
352 data = data.GetText()
353 if re.match('^http://.*/.*$', data):
354 if data.endswith(tuple(meshLoader.supportedExtensions())):
355 #Got an url on the clipboard with a model file.
357 wx.TheClipboard.Close()
359 def reloadModelFiles(self, filelist = None):
360 if filelist is not None:
361 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
362 for idx in xrange(0, len(filelist)):
363 if self.objectList[idx].filename != filelist[idx]:
367 for idx in xrange(0, len(self.objectList)):
368 filelist.append(self.objectList[idx].filename)
369 self.loadModelFiles(filelist)
372 def doFileLoadThread(self):
373 for obj in self.objectList:
374 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
375 obj.fileTime = os.stat(obj.filename).st_mtime
376 mesh = meshLoader.loadMesh(obj.filename)
379 obj.steepDirty = True
380 self.updateModelTransform()
381 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
383 wx.CallAfter(self.updateToolbar)
384 wx.CallAfter(self.glCanvas.Refresh)
386 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
387 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
388 self.gcodeDirty = True
389 self.gcode = gcodeInterpreter.gcode()
390 self.gcode.progressCallback = self.loadProgress
391 self.gcode.load(self.gcodeFilename)
394 for line in open(self.gcodeFilename, "rt"):
395 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)
397 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
398 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
399 errorList.append([v1, v2])
400 self.errorList = errorList
402 wx.CallAfter(self.updateToolbar)
403 wx.CallAfter(self.glCanvas.Refresh)
404 elif not os.path.isfile(self.gcodeFilename):
406 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
408 def loadProgress(self, progress):
409 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
410 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
411 self.layerSelect.setValue(self.layerSelect.getMaxValue())
413 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
414 return self.abortLoading
416 def OnResetAll(self, e = None):
417 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
418 profile.setPluginConfig([])
419 self.updateProfileToControls()
421 def ShowWarningPopup(self, text, callback = None):
422 self.warningPopup.text.SetLabel(text)
423 self.warningPopup.callback = callback
425 self.warningPopup.yesButton.Show(False)
426 self.warningPopup.noButton.SetLabel('ok')
428 self.warningPopup.yesButton.Show(True)
429 self.warningPopup.noButton.SetLabel('no')
430 self.warningPopup.Fit()
431 self.warningPopup.Layout()
433 self.warningPopup.Show(True)
434 self.warningPopup.timer.Start(5000)
436 def OnWarningPopup(self, e):
437 self.warningPopup.Show(False)
438 self.warningPopup.timer.Stop()
439 self.warningPopup.callback()
441 def OnHideWarning(self, e):
442 self.warningPopup.Show(False)
443 self.warningPopup.timer.Stop()
445 def updateToolbar(self):
446 self.printButton.setDisabled(self.gcode is None)
447 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
448 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
449 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
450 if self.gcode is not None:
451 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
454 def OnViewChange(self):
455 selection = self.viewSelection.getValue()
456 self.glCanvas.drawSteepOverhang = False
457 self.glCanvas.drawBorders = False
459 self.glCanvas.viewMode = "Normal"
461 self.glCanvas.viewMode = "Transparent"
463 self.glCanvas.viewMode = "X-Ray"
465 self.glCanvas.viewMode = "Normal"
466 self.glCanvas.drawSteepOverhang = True
468 self.layerSelect.setValue(self.layerSelect.getMaxValue())
469 self.glCanvas.viewMode = "GCode"
471 self.glCanvas.viewMode = "Mixed"
472 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
475 self.glCanvas.Refresh()
477 def deselectTool(self):
478 self.rotateToolButton.setSelected(False)
479 self.scaleToolButton.setSelected(False)
480 self.mirrorToolButton.setSelected(False)
483 def updateModelTransform(self, f=0):
484 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
487 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
488 for obj in self.objectList:
491 obj.mesh.matrix = self.matrix
492 obj.mesh.processMatrix()
494 minV = self.objectList[0].mesh.getMinimum()
495 maxV = self.objectList[0].mesh.getMaximum()
496 objectsBoundaryCircleSize = self.objectList[0].mesh.boundaryCircleSize
497 for obj in self.objectList:
501 minV = numpy.minimum(minV, obj.mesh.getMinimum())
502 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
503 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.boundaryCircleSize)
505 self.objectsMaxV = maxV
506 self.objectsMinV = minV
507 self.objectsSize = self.objectsMaxV - self.objectsMinV
508 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
510 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
511 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
512 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
513 self.scaleXctrl.setValue(round(scaleX, 2))
514 self.scaleYctrl.setValue(round(scaleY, 2))
515 self.scaleZctrl.setValue(round(scaleZ, 2))
516 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
517 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
518 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
520 self.glCanvas.Refresh()
522 def updateProfileToControls(self):
523 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
524 self.updateModelTransform()
525 for obj in self.objectList:
526 obj.steepDirty = True
527 self.glCanvas.updateProfileToControls()
529 class PreviewGLCanvas(openglGui.glGuiPanel):
530 def __init__(self, parent):
531 super(PreviewGLCanvas, self).__init__(parent)
532 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
537 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
539 self.gcodeDisplayList = []
540 self.gcodeQuickDisplayList = []
541 self.gcodeDisplayListMade = 0
542 self.gcodeQuickDisplayListMade = 0
543 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]]
547 self.tempMatrix = None
550 def updateProfileToControls(self):
551 self.objColor[0] = profile.getPreferenceColour('model_colour')
552 self.objColor[1] = profile.getPreferenceColour('model_colour2')
553 self.objColor[2] = profile.getPreferenceColour('model_colour3')
554 self.objColor[3] = profile.getPreferenceColour('model_colour4')
556 def OnMouseMotion(self,e):
557 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
558 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
559 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
560 p0 -= self.viewTarget
561 p1 -= self.viewTarget
562 if not e.Dragging() or self.dragType != 'tool':
563 self.parent.tool.OnMouseMove(p0, p1)
568 if e.Dragging() and e.LeftIsDown():
569 if self.dragType == '':
570 #Define the drag type depending on the cursor position.
571 self.dragType = 'viewRotate'
572 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
573 if self.parent.tool.OnDragStart(p0, p1):
574 self.dragType = 'tool'
576 if self.dragType == 'viewRotate':
578 self.yaw += e.GetX() - self.oldX
579 self.pitch -= e.GetY() - self.oldY
585 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
586 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
587 elif self.dragType == 'tool':
588 self.parent.tool.OnDrag(p0, p1)
590 #Workaround for buggy ATI cards.
591 size = self.GetSizeTuple()
592 self.SetSize((size[0]+1, size[1]))
593 self.SetSize((size[0], size[1]))
596 if self.dragType != '':
597 if self.tempMatrix is not None:
598 self.parent.matrix *= self.tempMatrix
599 self.parent.updateModelTransform()
600 self.tempMatrix = None
601 for obj in self.parent.objectList:
602 obj.steepDirty = True
603 self.parent.tool.OnDragEnd()
605 if e.Dragging() and e.RightIsDown():
606 self.zoom += e.GetY() - self.oldY
614 def getObjectBoundaryCircle(self):
615 return self.parent.objectsBoundaryCircleSize
617 def getObjectSize(self):
618 return self.parent.objectsSize
620 def getObjectMatrix(self):
621 return self.parent.matrix
623 def getObjectCenterPos(self):
624 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
626 def OnMouseWheel(self,e):
627 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
635 opengl.InitGL(self, self.view3D, self.zoom)
637 glTranslate(0,0,-self.zoom)
638 glTranslate(self.zoom/20.0,0,0)
639 glRotate(-self.pitch, 1,0,0)
640 glRotate(self.yaw, 0,0,1)
642 if self.viewMode == "GCode" or self.viewMode == "Mixed":
643 n = self.parent.layerSelect.getValue()
644 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
645 self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
647 if self.parent.objectsMaxV is not None:
648 self.viewTarget = self.getObjectCenterPos()
649 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
651 self.viewport = glGetIntegerv(GL_VIEWPORT)
652 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
653 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
658 machineSize = self.parent.machineSize
660 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
661 if self.parent.gcodeDirty:
662 self.parent.gcodeDirty = False
663 self.gcodeDisplayListMade = 0
664 self.gcodeQuickDisplayListMade = 0
666 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
667 gcodeGenStartTime = time.time()
668 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeQuickDisplayListMade < len(self.parent.gcode.layerList):
669 if len(self.gcodeQuickDisplayList) == self.gcodeQuickDisplayListMade:
670 self.gcodeQuickDisplayList.append(glGenLists(1))
671 glNewList(self.gcodeQuickDisplayList[self.gcodeQuickDisplayListMade], GL_COMPILE)
672 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeQuickDisplayListMade], True)
674 self.gcodeQuickDisplayListMade += 1
675 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
676 if len(self.gcodeDisplayList) == self.gcodeDisplayListMade:
677 self.gcodeDisplayList.append(glGenLists(1))
678 glNewList(self.gcodeDisplayList[self.gcodeDisplayListMade], GL_COMPILE)
679 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade], False)
681 self.gcodeDisplayListMade += 1
682 wx.CallAfter(self.Refresh)
685 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
686 for obj in self.parent.objectList:
689 if obj.displayList is None:
690 obj.displayList = glGenLists(1)
691 obj.steepDisplayList = glGenLists(1)
692 obj.outlineDisplayList = glGenLists(1)
695 glNewList(obj.displayList, GL_COMPILE)
696 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
698 glNewList(obj.outlineDisplayList, GL_COMPILE)
699 opengl.DrawMeshOutline(obj.mesh)
702 if self.viewMode == "Mixed":
704 glColor3f(0.0,0.0,0.0)
705 self.drawModel(obj.displayList)
706 glColor3f(1.0,1.0,1.0)
707 glClear(GL_DEPTH_BUFFER_BIT)
711 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
713 if profile.getPreference('machine_center_is_zero') == 'True':
714 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
715 glEnable(GL_COLOR_MATERIAL)
716 glEnable(GL_LIGHTING)
717 drawQuickUpToLayer = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue() + 1)
718 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
720 for i in xrange(drawQuickUpToLayer - 1, -1, -1):
722 if i < self.parent.layerSelect.getValue():
723 c = 0.9 - (drawQuickUpToLayer - i) * 0.1
728 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
729 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
730 if self.gcodeDisplayListMade > i and drawUpToLayer - i < 15:
731 glCallList(self.gcodeDisplayList[i])
733 glCallList(self.gcodeQuickDisplayList[i])
735 glDisable(GL_LIGHTING)
736 glDisable(GL_COLOR_MATERIAL)
737 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
738 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
741 glColor3f(1.0,1.0,1.0)
743 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
744 for obj in self.parent.objectList:
748 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
749 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
750 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
751 #If we want transparent, then first render a solid black model to remove the printer size lines.
752 if self.viewMode != "Mixed":
754 glColor3f(0.0,0.0,0.0)
755 self.drawModel(obj.displayList)
756 glColor3f(1.0,1.0,1.0)
757 #After the black model is rendered, render the model again but now with lighting and no depth testing.
758 glDisable(GL_DEPTH_TEST)
759 glEnable(GL_LIGHTING)
761 glBlendFunc(GL_ONE, GL_ONE)
762 glEnable(GL_LIGHTING)
763 self.drawModel(obj.displayList)
764 glEnable(GL_DEPTH_TEST)
765 elif self.viewMode == "X-Ray":
766 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
767 glDisable(GL_LIGHTING)
768 glDisable(GL_DEPTH_TEST)
769 glEnable(GL_STENCIL_TEST)
770 glStencilFunc(GL_ALWAYS, 1, 1)
771 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
772 self.drawModel(obj.displayList)
773 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
775 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
776 glStencilFunc(GL_EQUAL, 0, 1)
778 self.drawModel(obj.displayList)
779 glStencilFunc(GL_EQUAL, 1, 1)
781 self.drawModel(obj.displayList)
785 for i in xrange(2, 15, 2):
786 glStencilFunc(GL_EQUAL, i, 0xFF);
787 glColor(float(i)/10, float(i)/10, float(i)/5)
789 glVertex3f(-1000,-1000,-1)
790 glVertex3f( 1000,-1000,-1)
791 glVertex3f( 1000, 1000,-1)
792 glVertex3f(-1000, 1000,-1)
794 for i in xrange(1, 15, 2):
795 glStencilFunc(GL_EQUAL, i, 0xFF);
796 glColor(float(i)/10, 0, 0)
798 glVertex3f(-1000,-1000,-1)
799 glVertex3f( 1000,-1000,-1)
800 glVertex3f( 1000, 1000,-1)
801 glVertex3f(-1000, 1000,-1)
805 glDisable(GL_STENCIL_TEST)
806 glEnable(GL_DEPTH_TEST)
808 #Fix the depth buffer
809 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
810 self.drawModel(obj.displayList)
811 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
812 elif self.viewMode == "Normal":
813 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
814 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
815 glEnable(GL_LIGHTING)
816 self.drawModel(obj.displayList)
818 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
819 glEnable(GL_DEPTH_TEST)
820 glDisable(GL_LIGHTING)
822 self.drawModel(obj.outlineDisplayList)
824 if self.drawSteepOverhang:
826 obj.steepDirty = False
827 glNewList(obj.steepDisplayList, GL_COMPILE)
828 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
830 glDisable(GL_LIGHTING)
832 self.drawModel(obj.steepDisplayList)
835 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
836 # glDisable(GL_LIGHTING)
837 # glDisable(GL_DEPTH_TEST)
838 # glDisable(GL_BLEND)
841 # for err in self.parent.errorList:
842 # glVertex3f(err[0].x, err[0].y, err[0].z)
843 # glVertex3f(err[1].x, err[1].y, err[1].z)
845 # glEnable(GL_DEPTH_TEST)
847 opengl.DrawMachine(machineSize)
849 #Draw the current selected tool
850 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
852 pos = self.getObjectCenterPos()
853 glTranslate(pos[0], pos[1], pos[2])
854 self.parent.tool.OnDraw()
857 def drawModel(self, displayList):
858 vMin = self.parent.objectsMinV
859 vMax = self.parent.objectsMaxV
862 offset = - vMin - (vMax - vMin) / 2
864 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
867 glTranslate(0, 0, self.parent.objectsSize[2]/2)
868 if self.tempMatrix is not None:
869 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
870 glMultMatrixf(tempMatrix)
871 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
872 glTranslate(offset[0], offset[1], -vMin[2])
873 glMultMatrixf(matrix)
874 glCallList(displayList)