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 rotation', (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, 'Scale reset', (1,-2), self.OnScaleReset)
91 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 17, 'Scale to machine size', (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]
326 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
327 #Do the STL file loading in a background thread so we don't block the UI.
328 if self.loadThread is not None and self.loadThread.isAlive():
329 self.abortLoading = True
330 self.loadThread.join()
331 self.abortLoading = False
332 self.loadThread = threading.Thread(target=self.doFileLoadThread)
333 self.loadThread.daemon = True
334 self.loadThread.start()
337 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
338 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
340 def OnCheckReloadFile(self, e):
341 #Only show the reload popup when the window has focus, because the popup goes over other programs.
342 if self.GetParent().FindFocus() is None:
344 for obj in self.objectList:
345 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
346 self.checkReloadFileTimer.Stop()
347 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
348 if wx.TheClipboard.Open():
349 data = wx.TextDataObject()
350 if wx.TheClipboard.GetData(data):
351 data = data.GetText()
352 if re.match('^http://.*/.*$', data):
353 if data.endswith(tuple(meshLoader.supportedExtensions())):
354 #Got an url on the clipboard with a model file.
356 wx.TheClipboard.Close()
358 def reloadModelFiles(self, filelist = None):
359 if filelist is not None:
360 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
361 for idx in xrange(0, len(filelist)):
362 if self.objectList[idx].filename != filelist[idx]:
366 for idx in xrange(0, len(self.objectList)):
367 filelist.append(self.objectList[idx].filename)
368 self.loadModelFiles(filelist)
371 def doFileLoadThread(self):
372 for obj in self.objectList:
373 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
374 obj.fileTime = os.stat(obj.filename).st_mtime
375 mesh = meshLoader.loadMesh(obj.filename)
378 obj.steepDirty = True
379 self.updateModelTransform()
380 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
382 wx.CallAfter(self.updateToolbar)
383 wx.CallAfter(self.glCanvas.Refresh)
385 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
386 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
387 self.gcodeDirty = True
388 self.gcode = gcodeInterpreter.gcode()
389 self.gcode.progressCallback = self.loadProgress
390 self.gcode.load(self.gcodeFilename)
393 for line in open(self.gcodeFilename, "rt"):
394 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)
396 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
397 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
398 errorList.append([v1, v2])
399 self.errorList = errorList
401 wx.CallAfter(self.updateToolbar)
402 wx.CallAfter(self.glCanvas.Refresh)
403 elif not os.path.isfile(self.gcodeFilename):
405 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
407 def loadProgress(self, progress):
408 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
409 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
410 self.layerSelect.setValue(self.layerSelect.getMaxValue())
412 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
413 return self.abortLoading
415 def OnResetAll(self, e = None):
416 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
417 profile.setPluginConfig([])
418 self.updateProfileToControls()
420 def ShowWarningPopup(self, text, callback = None):
421 self.warningPopup.text.SetLabel(text)
422 self.warningPopup.callback = callback
424 self.warningPopup.yesButton.Show(False)
425 self.warningPopup.noButton.SetLabel('ok')
427 self.warningPopup.yesButton.Show(True)
428 self.warningPopup.noButton.SetLabel('no')
429 self.warningPopup.Fit()
430 self.warningPopup.Layout()
432 self.warningPopup.Show(True)
433 self.warningPopup.timer.Start(5000)
435 def OnWarningPopup(self, e):
436 self.warningPopup.Show(False)
437 self.warningPopup.timer.Stop()
438 self.warningPopup.callback()
440 def OnHideWarning(self, e):
441 self.warningPopup.Show(False)
442 self.warningPopup.timer.Stop()
444 def updateToolbar(self):
445 self.printButton.setDisabled(self.gcode is None)
446 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
447 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
448 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
449 if self.gcode is not None:
450 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
453 def OnViewChange(self):
454 selection = self.viewSelection.getValue()
455 self.glCanvas.drawSteepOverhang = False
456 self.glCanvas.drawBorders = False
458 self.glCanvas.viewMode = "Normal"
460 self.glCanvas.viewMode = "Transparent"
462 self.glCanvas.viewMode = "X-Ray"
464 self.glCanvas.viewMode = "Normal"
465 self.glCanvas.drawSteepOverhang = True
467 self.layerSelect.setValue(self.layerSelect.getMaxValue())
468 self.glCanvas.viewMode = "GCode"
470 self.glCanvas.viewMode = "Mixed"
471 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
473 self.rotateToolButton.setSelected(False)
474 self.scaleToolButton.setSelected(False)
475 self.mirrorToolButton.setSelected(False)
476 self.glCanvas.Refresh()
478 def updateModelTransform(self, f=0):
479 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
482 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
483 for obj in self.objectList:
486 obj.mesh.matrix = self.matrix
487 obj.mesh.processMatrix()
489 minV = self.objectList[0].mesh.getMinimum()
490 maxV = self.objectList[0].mesh.getMaximum()
491 objectsBoundaryCircleSize = self.objectList[0].mesh.boundaryCircleSize
492 for obj in self.objectList:
496 minV = numpy.minimum(minV, obj.mesh.getMinimum())
497 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
498 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.boundaryCircleSize)
500 self.objectsMaxV = maxV
501 self.objectsMinV = minV
502 self.objectsSize = self.objectsMaxV - self.objectsMinV
503 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
505 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
506 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
507 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
508 self.scaleXctrl.setValue(round(scaleX, 2))
509 self.scaleYctrl.setValue(round(scaleY, 2))
510 self.scaleZctrl.setValue(round(scaleZ, 2))
511 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
512 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
513 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
515 self.glCanvas.Refresh()
517 def updateProfileToControls(self):
518 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
519 self.updateModelTransform()
520 for obj in self.objectList:
521 obj.steepDirty = True
522 self.glCanvas.updateProfileToControls()
524 class PreviewGLCanvas(openglGui.glGuiPanel):
525 def __init__(self, parent):
526 super(PreviewGLCanvas, self).__init__(parent)
527 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
532 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
534 self.gcodeDisplayList = []
535 self.gcodeQuickDisplayList = []
536 self.gcodeDisplayListMade = 0
537 self.gcodeQuickDisplayListMade = 0
538 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]]
542 self.tempMatrix = None
545 def updateProfileToControls(self):
546 self.objColor[0] = profile.getPreferenceColour('model_colour')
547 self.objColor[1] = profile.getPreferenceColour('model_colour2')
548 self.objColor[2] = profile.getPreferenceColour('model_colour3')
549 self.objColor[3] = profile.getPreferenceColour('model_colour4')
551 def OnMouseMotion(self,e):
552 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
553 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
554 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
555 p0 -= self.viewTarget
556 p1 -= self.viewTarget
557 if not e.Dragging() or self.dragType != 'tool':
558 self.parent.tool.OnMouseMove(p0, p1)
563 if e.Dragging() and e.LeftIsDown():
564 if self.dragType == '':
565 #Define the drag type depending on the cursor position.
566 self.dragType = 'viewRotate'
567 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
568 if self.parent.tool.OnDragStart(p0, p1):
569 self.dragType = 'tool'
571 if self.dragType == 'viewRotate':
573 self.yaw += e.GetX() - self.oldX
574 self.pitch -= e.GetY() - self.oldY
580 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
581 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
582 elif self.dragType == 'tool':
583 self.parent.tool.OnDrag(p0, p1)
585 #Workaround for buggy ATI cards.
586 size = self.GetSizeTuple()
587 self.SetSize((size[0]+1, size[1]))
588 self.SetSize((size[0], size[1]))
591 if self.dragType != '':
592 if self.tempMatrix is not None:
593 self.parent.matrix *= self.tempMatrix
594 self.parent.updateModelTransform()
595 self.tempMatrix = None
596 for obj in self.parent.objectList:
597 obj.steepDirty = True
598 self.parent.tool.OnDragEnd()
600 if e.Dragging() and e.RightIsDown():
601 self.zoom += e.GetY() - self.oldY
609 def getObjectBoundaryCircle(self):
610 return self.parent.objectsBoundaryCircleSize
612 def getObjectSize(self):
613 return self.parent.objectsSize
615 def getObjectMatrix(self):
616 return self.parent.matrix
618 def getObjectCenterPos(self):
619 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
621 def OnMouseWheel(self,e):
622 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
630 opengl.InitGL(self, self.view3D, self.zoom)
632 glTranslate(0,0,-self.zoom)
633 glTranslate(self.zoom/20.0,0,0)
634 glRotate(-self.pitch, 1,0,0)
635 glRotate(self.yaw, 0,0,1)
637 if self.viewMode == "GCode" or self.viewMode == "Mixed":
638 n = self.parent.layerSelect.getValue()
639 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
640 self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
642 if self.parent.objectsMaxV is not None:
643 self.viewTarget = self.getObjectCenterPos()
644 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
646 self.viewport = glGetIntegerv(GL_VIEWPORT)
647 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
648 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
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)