1 from __future__ import absolute_import
2 from __future__ import division
11 from wx import glcanvas
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
18 from Cura.util import profile
19 from Cura.util import gcodeInterpreter
20 from Cura.util import meshLoader
21 from Cura.util import util3d
22 from Cura.util import sliceRun
24 from Cura.gui.util import opengl
25 from Cura.gui.util import previewTools
26 from Cura.gui.util import openglGui
28 class previewObject():
32 self.displayList = None
35 class previewPanel(wx.Panel):
36 def __init__(self, parent):
37 super(previewPanel, self).__init__(parent,-1)
39 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
40 self.SetMinSize((440,320))
45 self.objectsMinV = None
46 self.objectsMaxV = None
47 self.objectsBoundaryCircleSize = None
48 self.loadThread = None
49 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
50 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
52 self.glCanvas = PreviewGLCanvas(self)
53 #Create the popup window
54 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
55 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
56 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
57 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
58 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
59 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
60 self.warningPopup.SetSizer(self.warningPopup.sizer)
61 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
62 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
63 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
64 self.warningPopup.Fit()
65 self.warningPopup.Layout()
66 self.warningPopup.timer = wx.Timer(self)
67 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
69 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
70 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
71 parent.Bind(wx.EVT_MOVE, self.OnMove)
72 parent.Bind(wx.EVT_SIZE, self.OnMove)
74 sizer = wx.BoxSizer(wx.VERTICAL)
75 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
78 self.checkReloadFileTimer = wx.Timer(self)
79 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
80 self.checkReloadFileTimer.Start(1000)
83 self.rotateToolButton = openglGui.glRadioButton(self.glCanvas, 1, 'Rotate', (0,-1), group, self.OnToolSelect)
84 self.scaleToolButton = openglGui.glRadioButton(self.glCanvas, 2, 'Scale', (1,-1), group, self.OnToolSelect)
85 self.mirrorToolButton = openglGui.glRadioButton(self.glCanvas, 12, 'Mirror', (2,-1), group, self.OnToolSelect)
87 self.resetRotationButton = openglGui.glButton(self.glCanvas, 4, 'Reset rotation', (0,-2), self.OnRotateReset)
88 self.layFlatButton = openglGui.glButton(self.glCanvas, 5, 'Lay flat', (0,-3), self.OnLayFlat)
90 self.resetScaleButton = openglGui.glButton(self.glCanvas, 8, 'Scale reset', (1,-2), self.OnScaleReset)
91 self.scaleMaxButton = openglGui.glButton(self.glCanvas, 9, 'Scale to machine size', (1,-3), self.OnScaleMax)
93 self.mirrorXButton = openglGui.glButton(self.glCanvas, 12, 'Mirror X', (2,-2), lambda : self.OnMirror(0))
94 self.mirrorYButton = openglGui.glButton(self.glCanvas, 13, 'Mirror Y', (2,-3), lambda : self.OnMirror(1))
95 self.mirrorZButton = openglGui.glButton(self.glCanvas, 14, 'Mirror Z', (2,-4), lambda : self.OnMirror(2))
97 self.openFileButton = openglGui.glButton(self.glCanvas, 3, 'Load model', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
98 self.sliceButton = openglGui.glButton(self.glCanvas, 6, 'Prepare model', (1,0), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
99 self.printButton = openglGui.glButton(self.glCanvas, 7, 'Print model', (2,0), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
101 extruderCount = int(profile.getPreference('extruder_amount'))
102 if extruderCount > 1:
103 openglGui.glButton(self.glCanvas, 3, 'Load dual model', (1,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
104 if extruderCount > 2:
105 openglGui.glButton(self.glCanvas, 3, 'Load triple model', (2,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
106 if extruderCount > 3:
107 openglGui.glButton(self.glCanvas, 3, 'Load quad model', (3,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
109 self.scaleForm = openglGui.glFrame(self.glCanvas, (2, -3))
110 openglGui.glGuiLayoutGrid(self.scaleForm)
111 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
112 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
113 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
114 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
115 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
116 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
117 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
118 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
119 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
120 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
121 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
122 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
123 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
124 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
126 self.viewSelection = openglGui.glComboButton(self.glCanvas, 'View mode', [0,1,2,3,4], ['3D Model', 'Transparent', 'X-Ray', 'Overhang', 'Layers'], (-1,0), self.OnViewChange)
127 self.layerSelect = openglGui.glSlider(self.glCanvas, 0, 0, 100, (-1,-1), self.OnLayerNrChange)
131 self.returnToModelViewAndUpdateModel()
133 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
135 def returnToModelViewAndUpdateModel(self):
136 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
137 self.setViewMode('Normal')
138 self.updateModelTransform()
140 def OnToolSelect(self, 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 if self.gcode is not None:
453 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
456 def OnViewChange(self):
457 selection = self.viewSelection.getValue()
458 self.glCanvas.drawSteepOverhang = False
459 self.glCanvas.drawBorders = False
461 self.glCanvas.viewMode = "Normal"
463 self.glCanvas.viewMode = "Transparent"
465 self.glCanvas.viewMode = "X-Ray"
467 self.glCanvas.viewMode = "Normal"
468 self.glCanvas.drawSteepOverhang = True
470 self.layerSelect.setValue(self.layerSelect.getMaxValue())
471 self.glCanvas.viewMode = "GCode"
473 self.glCanvas.viewMode = "Mixed"
474 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
476 self.rotateToolButton.setSelected(False)
477 self.scaleToolButton.setSelected(False)
478 self.mirrorToolButton.setSelected(False)
479 self.OnToolSelect(False)
480 self.glCanvas.Refresh()
482 def updateModelTransform(self, f=0):
483 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
486 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
487 for obj in self.objectList:
490 obj.mesh.matrix = self.matrix
491 obj.mesh.processMatrix()
493 minV = self.objectList[0].mesh.getMinimum()
494 maxV = self.objectList[0].mesh.getMaximum()
495 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
496 for obj in self.objectList:
500 minV = numpy.minimum(minV, obj.mesh.getMinimum())
501 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
502 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
504 self.objectsMaxV = maxV
505 self.objectsMinV = minV
506 self.objectsSize = self.objectsMaxV - self.objectsMinV
507 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
509 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
510 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
511 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
512 self.scaleXctrl.setValue(round(scaleX, 2))
513 self.scaleYctrl.setValue(round(scaleY, 2))
514 self.scaleZctrl.setValue(round(scaleZ, 2))
515 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
516 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
517 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
519 self.glCanvas.Refresh()
521 def updateProfileToControls(self):
522 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
523 self.updateModelTransform()
524 for obj in self.objectList:
525 obj.steepDirty = True
526 self.glCanvas.updateProfileToControls()
528 class PreviewGLCanvas(openglGui.glGuiPanel):
529 def __init__(self, parent):
530 super(PreviewGLCanvas, self).__init__(parent)
531 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
536 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
538 self.gcodeDisplayList = None
539 self.gcodeDisplayListMade = None
540 self.gcodeDisplayListCount = 0
541 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]]
545 self.tempMatrix = None
548 def updateProfileToControls(self):
549 self.objColor[0] = profile.getPreferenceColour('model_colour')
550 self.objColor[1] = profile.getPreferenceColour('model_colour2')
551 self.objColor[2] = profile.getPreferenceColour('model_colour3')
552 self.objColor[3] = profile.getPreferenceColour('model_colour4')
554 def OnMouseMotion(self,e):
555 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
556 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
557 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
558 p0 -= self.viewTarget
559 p1 -= self.viewTarget
560 if not e.Dragging() or self.dragType != 'tool':
561 self.parent.tool.OnMouseMove(p0, p1)
566 if e.Dragging() and e.LeftIsDown():
567 if self.dragType == '':
568 #Define the drag type depending on the cursor position.
569 self.dragType = 'viewRotate'
570 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
571 if self.parent.tool.OnDragStart(p0, p1):
572 self.dragType = 'tool'
574 if self.dragType == 'viewRotate':
576 self.yaw += e.GetX() - self.oldX
577 self.pitch -= e.GetY() - self.oldY
583 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
584 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
585 elif self.dragType == 'tool':
586 self.parent.tool.OnDrag(p0, p1)
588 #Workaround for buggy ATI cards.
589 size = self.GetSizeTuple()
590 self.SetSize((size[0]+1, size[1]))
591 self.SetSize((size[0], size[1]))
594 if self.dragType != '':
595 if self.tempMatrix is not None:
596 self.parent.matrix *= self.tempMatrix
597 self.parent.updateModelTransform()
598 self.tempMatrix = None
599 for obj in self.parent.objectList:
600 obj.steepDirty = True
601 self.parent.tool.OnDragEnd()
603 if e.Dragging() and e.RightIsDown():
604 self.zoom += e.GetY() - self.oldY
612 def getObjectBoundaryCircle(self):
613 return self.parent.objectsBoundaryCircleSize
615 def getObjectSize(self):
616 return self.parent.objectsSize
618 def getObjectMatrix(self):
619 return self.parent.matrix
621 def getObjectCenterPos(self):
622 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
624 def OnMouseWheel(self,e):
625 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
633 opengl.InitGL(self, self.view3D, self.zoom)
635 glTranslate(0,0,-self.zoom)
636 glTranslate(self.zoom/20.0,0,0)
637 glRotate(-self.pitch, 1,0,0)
638 glRotate(self.yaw, 0,0,1)
640 if self.viewMode == "GCode" or self.viewMode == "Mixed":
641 if self.parent.gcode is not None and len(self.parent.gcode.layerList) > self.parent.layerSelect.getValue() and len(self.parent.gcode.layerList[self.parent.layerSelect.getValue()]) > 0:
642 self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSelect.getValue()][0].list[-1].z
644 if self.parent.objectsMaxV is not None:
645 self.viewTarget = self.getObjectCenterPos()
646 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
648 self.viewport = glGetIntegerv(GL_VIEWPORT)
649 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
650 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
655 machineSize = self.parent.machineSize
657 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
658 if self.parent.gcodeDirty:
659 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
660 if self.gcodeDisplayList is not None:
661 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
662 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
663 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
664 self.parent.gcodeDirty = False
665 self.gcodeDisplayListMade = 0
667 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
668 gcodeGenStartTime = time.time()
669 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
670 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
671 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
673 self.gcodeDisplayListMade += 1
674 wx.CallAfter(self.Refresh)
677 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
678 for obj in self.parent.objectList:
681 if obj.displayList is None:
682 obj.displayList = glGenLists(1)
683 obj.steepDisplayList = glGenLists(1)
684 obj.outlineDisplayList = glGenLists(1)
687 glNewList(obj.displayList, GL_COMPILE)
688 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
690 glNewList(obj.outlineDisplayList, GL_COMPILE)
691 opengl.DrawMeshOutline(obj.mesh)
694 if self.viewMode == "Mixed":
696 glColor3f(0.0,0.0,0.0)
697 self.drawModel(obj.displayList)
698 glColor3f(1.0,1.0,1.0)
699 glClear(GL_DEPTH_BUFFER_BIT)
703 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
705 if profile.getPreference('machine_center_is_zero') == 'True':
706 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
707 glEnable(GL_COLOR_MATERIAL)
708 glEnable(GL_LIGHTING)
709 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
710 starttime = time.time()
711 for i in xrange(drawUpToLayer - 1, -1, -1):
713 if i < self.parent.layerSelect.getValue():
714 c = 0.9 - (drawUpToLayer - i) * 0.1
719 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
720 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
721 glCallList(self.gcodeDisplayList + i)
722 if time.time() - starttime > 0.1:
725 glDisable(GL_LIGHTING)
726 glDisable(GL_COLOR_MATERIAL)
727 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
728 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
731 glColor3f(1.0,1.0,1.0)
733 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
734 for obj in self.parent.objectList:
738 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
739 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
740 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
741 #If we want transparent, then first render a solid black model to remove the printer size lines.
742 if self.viewMode != "Mixed":
744 glColor3f(0.0,0.0,0.0)
745 self.drawModel(obj.displayList)
746 glColor3f(1.0,1.0,1.0)
747 #After the black model is rendered, render the model again but now with lighting and no depth testing.
748 glDisable(GL_DEPTH_TEST)
749 glEnable(GL_LIGHTING)
751 glBlendFunc(GL_ONE, GL_ONE)
752 glEnable(GL_LIGHTING)
753 self.drawModel(obj.displayList)
754 glEnable(GL_DEPTH_TEST)
755 elif self.viewMode == "X-Ray":
756 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
757 glDisable(GL_LIGHTING)
758 glDisable(GL_DEPTH_TEST)
759 glEnable(GL_STENCIL_TEST)
760 glStencilFunc(GL_ALWAYS, 1, 1)
761 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
762 self.drawModel(obj.displayList)
763 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
765 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
766 glStencilFunc(GL_EQUAL, 0, 1)
768 self.drawModel(obj.displayList)
769 glStencilFunc(GL_EQUAL, 1, 1)
771 self.drawModel(obj.displayList)
775 for i in xrange(2, 15, 2):
776 glStencilFunc(GL_EQUAL, i, 0xFF);
777 glColor(float(i)/10, float(i)/10, float(i)/5)
779 glVertex3f(-1000,-1000,-1)
780 glVertex3f( 1000,-1000,-1)
781 glVertex3f( 1000, 1000,-1)
782 glVertex3f(-1000, 1000,-1)
784 for i in xrange(1, 15, 2):
785 glStencilFunc(GL_EQUAL, i, 0xFF);
786 glColor(float(i)/10, 0, 0)
788 glVertex3f(-1000,-1000,-1)
789 glVertex3f( 1000,-1000,-1)
790 glVertex3f( 1000, 1000,-1)
791 glVertex3f(-1000, 1000,-1)
795 glDisable(GL_STENCIL_TEST)
796 glEnable(GL_DEPTH_TEST)
798 #Fix the depth buffer
799 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
800 self.drawModel(obj.displayList)
801 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
802 elif self.viewMode == "Normal":
803 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
804 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
805 glEnable(GL_LIGHTING)
806 self.drawModel(obj.displayList)
808 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
809 glEnable(GL_DEPTH_TEST)
810 glDisable(GL_LIGHTING)
812 self.drawModel(obj.outlineDisplayList)
814 if self.drawSteepOverhang:
816 obj.steepDirty = False
817 glNewList(obj.steepDisplayList, GL_COMPILE)
818 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
820 glDisable(GL_LIGHTING)
822 self.drawModel(obj.steepDisplayList)
825 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
826 # glDisable(GL_LIGHTING)
827 # glDisable(GL_DEPTH_TEST)
828 # glDisable(GL_BLEND)
831 # for err in self.parent.errorList:
832 # glVertex3f(err[0].x, err[0].y, err[0].z)
833 # glVertex3f(err[1].x, err[1].y, err[1].z)
835 # glEnable(GL_DEPTH_TEST)
837 opengl.DrawMachine(machineSize)
839 #Draw the current selected tool
840 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
842 pos = self.getObjectCenterPos()
843 glTranslate(pos[0], pos[1], pos[2])
844 self.parent.tool.OnDraw()
847 def drawModel(self, displayList):
848 vMin = self.parent.objectsMinV
849 vMax = self.parent.objectsMaxV
850 offset = - vMin - (vMax - vMin) / 2
852 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
855 glTranslate(0, 0, self.parent.objectsSize[2]/2)
856 if self.tempMatrix is not None:
857 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
858 glMultMatrixf(tempMatrix)
859 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
860 glTranslate(offset[0], offset[1], -vMin[2])
861 glMultMatrixf(matrix)
862 glCallList(displayList)