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', (0,1), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
104 if extruderCount > 2:
105 openglGui.glButton(self.glCanvas, 3, 'Load triple model', (0,2), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
106 if extruderCount > 3:
107 openglGui.glButton(self.glCanvas, 3, 'Load quad model', (0,3), 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,-2), 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, resetView = True):
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())
158 self.returnToModelViewAndUpdateModel()
160 def OnScaleEntry(self, value, axis):
165 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
166 scale = value / scale
169 if self.scaleUniform.getValue():
170 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
172 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
173 matrix[axis][axis] = scale
174 self.matrix *= numpy.matrix(matrix, numpy.float64)
175 self.updateModelTransform()
177 def OnScaleEntryMM(self, value, axis):
182 scale = self.objectsSize[axis]
183 scale = value / scale
186 if self.scaleUniform.getValue():
187 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
189 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
190 matrix[axis][axis] = scale
191 self.matrix *= numpy.matrix(matrix, numpy.float64)
192 self.updateModelTransform()
194 def OnMirror(self, axis):
195 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
196 matrix[axis][axis] = -1
197 self.matrix *= numpy.matrix(matrix, numpy.float64)
198 for obj in self.objectList:
200 obj.steepDirty = True
201 self.updateModelTransform()
203 def OnMove(self, e = None):
206 x, y = self.glCanvas.ClientToScreenXY(0, 0)
207 sx, sy = self.glCanvas.GetClientSizeTuple()
208 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
210 def OnScaleMax(self):
211 if self.objectsMinV is None:
213 vMin = self.objectsMinV
214 vMax = self.objectsMaxV
216 if profile.getProfileSettingFloat('skirt_line_count') > 0:
217 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
218 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
219 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
220 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
221 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
222 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
223 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
224 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
225 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
226 self.setViewMode('Normal')
227 self.updateModelTransform()
229 def OnRotateReset(self):
230 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
231 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
232 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
233 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
234 for obj in self.objectList:
235 obj.steepDirty = True
236 self.updateModelTransform()
238 def OnScaleReset(self):
239 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
240 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
241 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
242 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
243 for obj in self.objectList:
244 obj.steepDirty = True
245 self.updateModelTransform()
248 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
249 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
252 for v in transformedVertexes:
253 diff = v - minZvertex
254 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
257 dot = (diff[2] / len)
263 rad = -math.atan2(dotV[1], dotV[0])
264 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
265 rad = -math.asin(dotMin)
266 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
269 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
270 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
273 for v in transformedVertexes:
274 diff = v - minZvertex
275 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
278 dot = (diff[2] / len)
285 rad = math.asin(dotMin)
287 rad = -math.asin(dotMin)
288 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
290 for obj in self.objectList:
291 obj.steepDirty = True
292 self.updateModelTransform()
295 self.glCanvas.yaw = 30
296 self.glCanvas.pitch = 60
297 self.glCanvas.zoom = 300
298 self.glCanvas.view3D = True
299 self.glCanvas.Refresh()
301 def OnTopClick(self):
302 self.glCanvas.view3D = False
303 self.glCanvas.zoom = 100
304 self.glCanvas.offsetX = 0
305 self.glCanvas.offsetY = 0
306 self.glCanvas.Refresh()
308 def OnLayerNrChange(self):
309 self.glCanvas.Refresh()
311 def setViewMode(self, mode):
313 self.viewSelection.setValue(0)
315 self.viewSelection.setValue(4)
316 wx.CallAfter(self.glCanvas.Refresh)
318 def loadModelFiles(self, filelist, showWarning = False):
319 while len(filelist) > len(self.objectList):
320 self.objectList.append(previewObject())
321 for idx in xrange(len(filelist), len(self.objectList)):
322 self.objectList[idx].mesh = None
323 self.objectList[idx].filename = None
324 for idx in xrange(0, len(filelist)):
325 obj = self.objectList[idx]
326 if obj.filename != filelist[idx]:
328 self.gcodeFileTime = None
329 self.logFileTime = None
330 obj.filename = filelist[idx]
332 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
333 #Do the STL file loading in a background thread so we don't block the UI.
334 if self.loadThread is not None and self.loadThread.isAlive():
335 self.abortLoading = True
336 self.loadThread.join()
337 self.abortLoading = False
338 self.loadThread = threading.Thread(target=self.doFileLoadThread)
339 self.loadThread.daemon = True
340 self.loadThread.start()
343 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
344 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
346 def OnCheckReloadFile(self, e):
347 #Only show the reload popup when the window has focus, because the popup goes over other programs.
348 if self.GetParent().FindFocus() is None:
350 for obj in self.objectList:
351 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
352 self.checkReloadFileTimer.Stop()
353 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
354 if wx.TheClipboard.Open():
355 data = wx.TextDataObject()
356 if wx.TheClipboard.GetData(data):
357 data = data.GetText()
358 if re.match('^http://.*/.*$', data):
359 if data.endswith(tuple(meshLoader.supportedExtensions())):
360 #Got an url on the clipboard with a model file.
362 wx.TheClipboard.Close()
364 def reloadModelFiles(self, filelist = None):
365 if filelist is not None:
366 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
367 for idx in xrange(0, len(filelist)):
368 if self.objectList[idx].filename != filelist[idx]:
372 for idx in xrange(0, len(self.objectList)):
373 filelist.append(self.objectList[idx].filename)
374 self.loadModelFiles(filelist)
377 def doFileLoadThread(self):
378 for obj in self.objectList:
379 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
380 obj.fileTime = os.stat(obj.filename).st_mtime
381 mesh = meshLoader.loadMesh(obj.filename)
384 obj.steepDirty = True
385 self.updateModelTransform()
386 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
388 wx.CallAfter(self.updateToolbar)
389 wx.CallAfter(self.glCanvas.Refresh)
391 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
392 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
393 self.gcodeDirty = True
394 self.gcode = gcodeInterpreter.gcode()
395 self.gcode.progressCallback = self.loadProgress
396 self.gcode.load(self.gcodeFilename)
399 for line in open(self.gcodeFilename, "rt"):
400 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)
402 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
403 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
404 errorList.append([v1, v2])
405 self.errorList = errorList
407 wx.CallAfter(self.updateToolbar)
408 wx.CallAfter(self.glCanvas.Refresh)
409 elif not os.path.isfile(self.gcodeFilename):
411 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
413 def loadProgress(self, progress):
414 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
415 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
416 self.layerSelect.setValue(self.layerSelect.getMaxValue())
418 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
419 return self.abortLoading
421 def OnResetAll(self, e = None):
422 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
423 profile.setPluginConfig([])
424 self.updateProfileToControls()
426 def ShowWarningPopup(self, text, callback = None):
427 self.warningPopup.text.SetLabel(text)
428 self.warningPopup.callback = callback
430 self.warningPopup.yesButton.Show(False)
431 self.warningPopup.noButton.SetLabel('ok')
433 self.warningPopup.yesButton.Show(True)
434 self.warningPopup.noButton.SetLabel('no')
435 self.warningPopup.Fit()
436 self.warningPopup.Layout()
438 self.warningPopup.Show(True)
439 self.warningPopup.timer.Start(5000)
441 def OnWarningPopup(self, e):
442 self.warningPopup.Show(False)
443 self.warningPopup.timer.Stop()
444 self.warningPopup.callback()
446 def OnHideWarning(self, e):
447 self.warningPopup.Show(False)
448 self.warningPopup.timer.Stop()
450 def updateToolbar(self):
451 self.printButton.setDisabled(self.gcode is None)
452 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
453 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
454 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
455 if self.gcode is not None:
456 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
459 def OnViewChange(self):
460 selection = self.viewSelection.getValue()
461 self.glCanvas.drawSteepOverhang = False
462 self.glCanvas.drawBorders = False
464 self.glCanvas.viewMode = "Normal"
466 self.glCanvas.viewMode = "Transparent"
468 self.glCanvas.viewMode = "X-Ray"
470 self.glCanvas.viewMode = "Normal"
471 self.glCanvas.drawSteepOverhang = True
473 self.layerSelect.setValue(self.layerSelect.getMaxValue())
474 self.glCanvas.viewMode = "GCode"
476 self.glCanvas.viewMode = "Mixed"
477 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
479 self.rotateToolButton.setSelected(False)
480 self.scaleToolButton.setSelected(False)
481 self.mirrorToolButton.setSelected(False)
482 self.OnToolSelect(False)
483 self.glCanvas.Refresh()
485 def updateModelTransform(self, f=0):
486 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
489 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
490 for obj in self.objectList:
493 obj.mesh.matrix = self.matrix
494 obj.mesh.processMatrix()
496 minV = self.objectList[0].mesh.getMinimum()
497 maxV = self.objectList[0].mesh.getMaximum()
498 objectsBoundaryCircleSize = self.objectList[0].mesh.boundaryCircleSize
499 for obj in self.objectList:
503 minV = numpy.minimum(minV, obj.mesh.getMinimum())
504 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
505 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.boundaryCircleSize)
507 self.objectsMaxV = maxV
508 self.objectsMinV = minV
509 self.objectsSize = self.objectsMaxV - self.objectsMinV
510 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
512 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
513 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
514 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
515 self.scaleXctrl.setValue(round(scaleX, 2))
516 self.scaleYctrl.setValue(round(scaleY, 2))
517 self.scaleZctrl.setValue(round(scaleZ, 2))
518 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
519 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
520 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
522 self.glCanvas.Refresh()
524 def updateProfileToControls(self):
525 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
526 self.updateModelTransform()
527 for obj in self.objectList:
528 obj.steepDirty = True
529 self.glCanvas.updateProfileToControls()
531 class PreviewGLCanvas(openglGui.glGuiPanel):
532 def __init__(self, parent):
533 super(PreviewGLCanvas, self).__init__(parent)
534 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
539 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
541 self.gcodeDisplayList = []
542 self.gcodeQuickDisplayList = []
543 self.gcodeDisplayListMade = 0
544 self.gcodeQuickDisplayListMade = 0
545 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]]
549 self.tempMatrix = None
552 def updateProfileToControls(self):
553 self.objColor[0] = profile.getPreferenceColour('model_colour')
554 self.objColor[1] = profile.getPreferenceColour('model_colour2')
555 self.objColor[2] = profile.getPreferenceColour('model_colour3')
556 self.objColor[3] = profile.getPreferenceColour('model_colour4')
558 def OnMouseMotion(self,e):
559 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
560 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
561 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
562 p0 -= self.viewTarget
563 p1 -= self.viewTarget
564 if not e.Dragging() or self.dragType != 'tool':
565 self.parent.tool.OnMouseMove(p0, p1)
570 if e.Dragging() and e.LeftIsDown():
571 if self.dragType == '':
572 #Define the drag type depending on the cursor position.
573 self.dragType = 'viewRotate'
574 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
575 if self.parent.tool.OnDragStart(p0, p1):
576 self.dragType = 'tool'
578 if self.dragType == 'viewRotate':
580 self.yaw += e.GetX() - self.oldX
581 self.pitch -= e.GetY() - self.oldY
587 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
588 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
589 elif self.dragType == 'tool':
590 self.parent.tool.OnDrag(p0, p1)
592 #Workaround for buggy ATI cards.
593 size = self.GetSizeTuple()
594 self.SetSize((size[0]+1, size[1]))
595 self.SetSize((size[0], size[1]))
598 if self.dragType != '':
599 if self.tempMatrix is not None:
600 self.parent.matrix *= self.tempMatrix
601 self.parent.updateModelTransform()
602 self.tempMatrix = None
603 for obj in self.parent.objectList:
604 obj.steepDirty = True
605 self.parent.tool.OnDragEnd()
607 if e.Dragging() and e.RightIsDown():
608 self.zoom += e.GetY() - self.oldY
616 def getObjectBoundaryCircle(self):
617 return self.parent.objectsBoundaryCircleSize
619 def getObjectSize(self):
620 return self.parent.objectsSize
622 def getObjectMatrix(self):
623 return self.parent.matrix
625 def getObjectCenterPos(self):
626 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
628 def OnMouseWheel(self,e):
629 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
637 opengl.InitGL(self, self.view3D, self.zoom)
639 glTranslate(0,0,-self.zoom)
640 glTranslate(self.zoom/20.0,0,0)
641 glRotate(-self.pitch, 1,0,0)
642 glRotate(self.yaw, 0,0,1)
644 if self.viewMode == "GCode" or self.viewMode == "Mixed":
645 n = self.parent.layerSelect.getValue()
646 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
647 self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
649 if self.parent.objectsMaxV is not None:
650 self.viewTarget = self.getObjectCenterPos()
651 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
653 self.viewport = glGetIntegerv(GL_VIEWPORT)
654 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
655 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
660 machineSize = self.parent.machineSize
662 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
663 if self.parent.gcodeDirty:
664 self.parent.gcodeDirty = False
665 self.gcodeDisplayListMade = 0
666 self.gcodeQuickDisplayListMade = 0
668 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
669 gcodeGenStartTime = time.time()
670 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeQuickDisplayListMade < len(self.parent.gcode.layerList):
671 if len(self.gcodeQuickDisplayList) == self.gcodeQuickDisplayListMade:
672 self.gcodeQuickDisplayList.append(glGenLists(1))
673 glNewList(self.gcodeQuickDisplayList[self.gcodeQuickDisplayListMade], GL_COMPILE)
674 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeQuickDisplayListMade], True)
676 self.gcodeQuickDisplayListMade += 1
677 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
678 if len(self.gcodeDisplayList) == self.gcodeDisplayListMade:
679 self.gcodeDisplayList.append(glGenLists(1))
680 glNewList(self.gcodeDisplayList[self.gcodeDisplayListMade], GL_COMPILE)
681 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade], False)
683 self.gcodeDisplayListMade += 1
684 wx.CallAfter(self.Refresh)
687 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
688 for obj in self.parent.objectList:
691 if obj.displayList is None:
692 obj.displayList = glGenLists(1)
693 obj.steepDisplayList = glGenLists(1)
694 obj.outlineDisplayList = glGenLists(1)
697 glNewList(obj.displayList, GL_COMPILE)
698 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
700 glNewList(obj.outlineDisplayList, GL_COMPILE)
701 opengl.DrawMeshOutline(obj.mesh)
704 if self.viewMode == "Mixed":
706 glColor3f(0.0,0.0,0.0)
707 self.drawModel(obj.displayList)
708 glColor3f(1.0,1.0,1.0)
709 glClear(GL_DEPTH_BUFFER_BIT)
713 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
715 if profile.getPreference('machine_center_is_zero') == 'True':
716 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
717 glEnable(GL_COLOR_MATERIAL)
718 glEnable(GL_LIGHTING)
719 drawUpToLayer = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue() + 1)
720 starttime = time.time()
721 for i in xrange(drawUpToLayer - 1, -1, -1):
723 if i < self.parent.layerSelect.getValue():
724 c = 0.9 - (drawUpToLayer - i) * 0.1
729 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
730 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
731 if self.gcodeDisplayListMade > i and drawUpToLayer - i < 15:
732 glCallList(self.gcodeDisplayList[i])
734 glCallList(self.gcodeQuickDisplayList[i])
736 glDisable(GL_LIGHTING)
737 glDisable(GL_COLOR_MATERIAL)
738 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
739 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
742 glColor3f(1.0,1.0,1.0)
744 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
745 for obj in self.parent.objectList:
749 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
750 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
751 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
752 #If we want transparent, then first render a solid black model to remove the printer size lines.
753 if self.viewMode != "Mixed":
755 glColor3f(0.0,0.0,0.0)
756 self.drawModel(obj.displayList)
757 glColor3f(1.0,1.0,1.0)
758 #After the black model is rendered, render the model again but now with lighting and no depth testing.
759 glDisable(GL_DEPTH_TEST)
760 glEnable(GL_LIGHTING)
762 glBlendFunc(GL_ONE, GL_ONE)
763 glEnable(GL_LIGHTING)
764 self.drawModel(obj.displayList)
765 glEnable(GL_DEPTH_TEST)
766 elif self.viewMode == "X-Ray":
767 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
768 glDisable(GL_LIGHTING)
769 glDisable(GL_DEPTH_TEST)
770 glEnable(GL_STENCIL_TEST)
771 glStencilFunc(GL_ALWAYS, 1, 1)
772 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
773 self.drawModel(obj.displayList)
774 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
776 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
777 glStencilFunc(GL_EQUAL, 0, 1)
779 self.drawModel(obj.displayList)
780 glStencilFunc(GL_EQUAL, 1, 1)
782 self.drawModel(obj.displayList)
786 for i in xrange(2, 15, 2):
787 glStencilFunc(GL_EQUAL, i, 0xFF);
788 glColor(float(i)/10, float(i)/10, float(i)/5)
790 glVertex3f(-1000,-1000,-1)
791 glVertex3f( 1000,-1000,-1)
792 glVertex3f( 1000, 1000,-1)
793 glVertex3f(-1000, 1000,-1)
795 for i in xrange(1, 15, 2):
796 glStencilFunc(GL_EQUAL, i, 0xFF);
797 glColor(float(i)/10, 0, 0)
799 glVertex3f(-1000,-1000,-1)
800 glVertex3f( 1000,-1000,-1)
801 glVertex3f( 1000, 1000,-1)
802 glVertex3f(-1000, 1000,-1)
806 glDisable(GL_STENCIL_TEST)
807 glEnable(GL_DEPTH_TEST)
809 #Fix the depth buffer
810 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
811 self.drawModel(obj.displayList)
812 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
813 elif self.viewMode == "Normal":
814 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
815 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
816 glEnable(GL_LIGHTING)
817 self.drawModel(obj.displayList)
819 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
820 glEnable(GL_DEPTH_TEST)
821 glDisable(GL_LIGHTING)
823 self.drawModel(obj.outlineDisplayList)
825 if self.drawSteepOverhang:
827 obj.steepDirty = False
828 glNewList(obj.steepDisplayList, GL_COMPILE)
829 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
831 glDisable(GL_LIGHTING)
833 self.drawModel(obj.steepDisplayList)
836 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
837 # glDisable(GL_LIGHTING)
838 # glDisable(GL_DEPTH_TEST)
839 # glDisable(GL_BLEND)
842 # for err in self.parent.errorList:
843 # glVertex3f(err[0].x, err[0].y, err[0].z)
844 # glVertex3f(err[1].x, err[1].y, err[1].z)
846 # glEnable(GL_DEPTH_TEST)
848 opengl.DrawMachine(machineSize)
850 #Draw the current selected tool
851 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
853 pos = self.getObjectCenterPos()
854 glTranslate(pos[0], pos[1], pos[2])
855 self.parent.tool.OnDraw()
858 def drawModel(self, displayList):
859 vMin = self.parent.objectsMinV
860 vMax = self.parent.objectsMaxV
861 offset = - vMin - (vMax - vMin) / 2
863 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
866 glTranslate(0, 0, self.parent.objectsSize[2]/2)
867 if self.tempMatrix is not None:
868 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
869 glMultMatrixf(tempMatrix)
870 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
871 glTranslate(offset[0], offset[1], -vMin[2])
872 glMultMatrixf(matrix)
873 glCallList(displayList)