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):
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())
157 self.returnToModelViewAndUpdateModel()
159 def OnScaleEntry(self, value, axis):
164 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
165 scale = value / scale
168 if self.scaleUniform.getValue():
169 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
171 matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
172 matrix[axis][axis] = scale
173 self.matrix *= numpy.matrix(matrix, numpy.float64)
174 self.updateModelTransform()
176 def OnScaleEntryMM(self, value, axis):
181 scale = self.objectsSize[axis]
182 scale = value / scale
185 if self.scaleUniform.getValue():
186 matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
188 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
189 matrix[axis][axis] = scale
190 self.matrix *= numpy.matrix(matrix, numpy.float64)
191 self.updateModelTransform()
193 def OnMirror(self, axis):
194 matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
195 matrix[axis][axis] = -1
196 self.matrix *= numpy.matrix(matrix, numpy.float64)
197 for obj in self.objectList:
199 obj.steepDirty = True
200 self.updateModelTransform()
202 def OnMove(self, e = None):
205 x, y = self.glCanvas.ClientToScreenXY(0, 0)
206 sx, sy = self.glCanvas.GetClientSizeTuple()
207 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
209 def OnScaleMax(self, onlyScaleDown = False):
210 if self.objectsMinV is None:
212 vMin = self.objectsMinV
213 vMax = self.objectsMaxV
215 if profile.getProfileSettingFloat('skirt_line_count') > 0:
216 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
217 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
218 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
219 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
220 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
221 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
222 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
223 if scale > 1.0 and onlyScaleDown:
225 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
226 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
227 self.setViewMode('Normal')
228 self.updateModelTransform()
230 def OnRotateReset(self):
231 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
232 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
233 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
234 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
235 for obj in self.objectList:
236 obj.steepDirty = True
237 self.updateModelTransform()
239 def OnScaleReset(self):
240 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
241 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
242 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
243 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
244 for obj in self.objectList:
245 obj.steepDirty = True
246 self.updateModelTransform()
249 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
250 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
253 for v in transformedVertexes:
254 diff = v - minZvertex
255 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
258 dot = (diff[2] / len)
264 rad = -math.atan2(dotV[1], dotV[0])
265 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
266 rad = -math.asin(dotMin)
267 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
270 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
271 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
274 for v in transformedVertexes:
275 diff = v - minZvertex
276 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
279 dot = (diff[2] / len)
286 rad = math.asin(dotMin)
288 rad = -math.asin(dotMin)
289 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
291 for obj in self.objectList:
292 obj.steepDirty = True
293 self.updateModelTransform()
296 self.glCanvas.yaw = 30
297 self.glCanvas.pitch = 60
298 self.glCanvas.zoom = 300
299 self.glCanvas.view3D = True
300 self.glCanvas.Refresh()
302 def OnTopClick(self):
303 self.glCanvas.view3D = False
304 self.glCanvas.zoom = 100
305 self.glCanvas.offsetX = 0
306 self.glCanvas.offsetY = 0
307 self.glCanvas.Refresh()
309 def OnLayerNrChange(self):
310 self.glCanvas.Refresh()
312 def setViewMode(self, mode):
314 self.viewSelection.setValue(0)
316 self.viewSelection.setValue(5)
317 wx.CallAfter(self.glCanvas.Refresh)
319 def loadModelFiles(self, filelist, showWarning = False):
320 while len(filelist) > len(self.objectList):
321 self.objectList.append(previewObject())
322 for idx in xrange(len(filelist), len(self.objectList)):
323 self.objectList[idx].mesh = None
324 self.objectList[idx].filename = None
325 for idx in xrange(0, len(filelist)):
326 obj = self.objectList[idx]
327 if obj.filename != filelist[idx]:
329 self.gcodeFileTime = None
330 self.logFileTime = None
331 obj.filename = filelist[idx]
333 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
334 #Do the STL file loading in a background thread so we don't block the UI.
335 if self.loadThread is not None and self.loadThread.isAlive():
336 self.abortLoading = True
337 self.loadThread.join()
338 self.abortLoading = False
339 self.loadThread = threading.Thread(target=self.doFileLoadThread)
340 self.loadThread.daemon = True
341 self.loadThread.start()
344 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
345 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
347 def OnCheckReloadFile(self, e):
348 #Only show the reload popup when the window has focus, because the popup goes over other programs.
349 if self.GetParent().FindFocus() is None:
351 for obj in self.objectList:
352 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
353 self.checkReloadFileTimer.Stop()
354 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
355 if wx.TheClipboard.Open():
356 data = wx.TextDataObject()
357 if wx.TheClipboard.GetData(data):
358 data = data.GetText()
359 if re.match('^http://.*/.*$', data):
360 if data.endswith(tuple(meshLoader.supportedExtensions())):
361 #Got an url on the clipboard with a model file.
363 wx.TheClipboard.Close()
365 def reloadModelFiles(self, filelist = None):
366 if filelist is not None:
367 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
368 for idx in xrange(0, len(filelist)):
369 if self.objectList[idx].filename != filelist[idx]:
373 for idx in xrange(0, len(self.objectList)):
374 filelist.append(self.objectList[idx].filename)
375 self.loadModelFiles(filelist)
378 def doFileLoadThread(self):
379 for obj in self.objectList:
380 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
381 obj.fileTime = os.stat(obj.filename).st_mtime
382 mesh = meshLoader.loadMesh(obj.filename)
385 obj.steepDirty = True
386 self.updateModelTransform()
387 self.OnScaleMax(True)
388 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
390 wx.CallAfter(self.updateToolbar)
391 wx.CallAfter(self.glCanvas.Refresh)
393 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
394 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
395 self.gcodeDirty = True
396 self.gcode = gcodeInterpreter.gcode()
397 self.gcode.progressCallback = self.loadProgress
398 self.gcode.load(self.gcodeFilename)
401 for line in open(self.gcodeFilename, "rt"):
402 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)
404 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
405 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
406 errorList.append([v1, v2])
407 self.errorList = errorList
409 wx.CallAfter(self.updateToolbar)
410 wx.CallAfter(self.glCanvas.Refresh)
411 elif not os.path.isfile(self.gcodeFilename):
413 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
415 def loadProgress(self, progress):
416 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
417 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
418 self.layerSelect.setValue(self.layerSelect.getMaxValue())
420 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
421 return self.abortLoading
423 def OnResetAll(self, e = None):
424 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
425 profile.setPluginConfig([])
426 self.updateProfileToControls()
428 def ShowWarningPopup(self, text, callback = None):
429 self.warningPopup.text.SetLabel(text)
430 self.warningPopup.callback = callback
432 self.warningPopup.yesButton.Show(False)
433 self.warningPopup.noButton.SetLabel('ok')
435 self.warningPopup.yesButton.Show(True)
436 self.warningPopup.noButton.SetLabel('no')
437 self.warningPopup.Fit()
438 self.warningPopup.Layout()
440 self.warningPopup.Show(True)
441 self.warningPopup.timer.Start(5000)
443 def OnWarningPopup(self, e):
444 self.warningPopup.Show(False)
445 self.warningPopup.timer.Stop()
446 self.warningPopup.callback()
448 def OnHideWarning(self, e):
449 self.warningPopup.Show(False)
450 self.warningPopup.timer.Stop()
452 def updateToolbar(self):
453 self.printButton.setDisabled(self.gcode is None)
454 if self.gcode is not None:
455 self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
458 def OnViewChange(self):
459 selection = self.viewSelection.getValue()
460 self.glCanvas.drawSteepOverhang = False
461 self.glCanvas.drawBorders = False
463 self.glCanvas.viewMode = "Normal"
465 self.glCanvas.viewMode = "Transparent"
467 self.glCanvas.viewMode = "X-Ray"
469 self.glCanvas.viewMode = "Normal"
470 self.glCanvas.drawSteepOverhang = True
472 self.layerSelect.setValue(self.layerSelect.getMaxValue())
473 self.glCanvas.viewMode = "GCode"
475 self.glCanvas.viewMode = "Mixed"
476 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
478 self.glCanvas.Refresh()
480 def updateModelTransform(self, f=0):
481 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
484 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
485 for obj in self.objectList:
488 obj.mesh.matrix = self.matrix
489 obj.mesh.processMatrix()
491 minV = self.objectList[0].mesh.getMinimum()
492 maxV = self.objectList[0].mesh.getMaximum()
493 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
494 for obj in self.objectList:
498 minV = numpy.minimum(minV, obj.mesh.getMinimum())
499 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
500 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
502 self.objectsMaxV = maxV
503 self.objectsMinV = minV
504 self.objectsSize = self.objectsMaxV - self.objectsMinV
505 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
507 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
508 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
509 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
510 self.scaleXctrl.setValue(round(scaleX, 2))
511 self.scaleYctrl.setValue(round(scaleY, 2))
512 self.scaleZctrl.setValue(round(scaleZ, 2))
513 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
514 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
515 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
517 self.glCanvas.Refresh()
519 def updateProfileToControls(self):
520 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
521 self.updateModelTransform()
522 for obj in self.objectList:
523 obj.steepDirty = True
524 self.glCanvas.updateProfileToControls()
526 class PreviewGLCanvas(openglGui.glGuiPanel):
527 def __init__(self, parent):
528 super(PreviewGLCanvas, self).__init__(parent)
529 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
534 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
536 self.gcodeDisplayList = None
537 self.gcodeDisplayListMade = None
538 self.gcodeDisplayListCount = 0
539 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]]
543 self.tempMatrix = None
546 def updateProfileToControls(self):
547 self.objColor[0] = profile.getPreferenceColour('model_colour')
548 self.objColor[1] = profile.getPreferenceColour('model_colour2')
549 self.objColor[2] = profile.getPreferenceColour('model_colour3')
550 self.objColor[3] = profile.getPreferenceColour('model_colour4')
552 def OnMouseMotion(self,e):
553 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
554 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
555 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
556 p0 -= self.viewTarget
557 p1 -= self.viewTarget
558 if not e.Dragging() or self.dragType != 'tool':
559 self.parent.tool.OnMouseMove(p0, p1)
564 if e.Dragging() and e.LeftIsDown():
565 if self.dragType == '':
566 #Define the drag type depending on the cursor position.
567 self.dragType = 'viewRotate'
568 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
569 if self.parent.tool.OnDragStart(p0, p1):
570 self.dragType = 'tool'
572 if self.dragType == 'viewRotate':
574 self.yaw += e.GetX() - self.oldX
575 self.pitch -= e.GetY() - self.oldY
581 self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
582 self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
583 elif self.dragType == 'tool':
584 self.parent.tool.OnDrag(p0, p1)
586 #Workaround for buggy ATI cards.
587 size = self.GetSizeTuple()
588 self.SetSize((size[0]+1, size[1]))
589 self.SetSize((size[0], size[1]))
592 if self.dragType != '':
593 if self.tempMatrix is not None:
594 self.parent.matrix *= self.tempMatrix
595 self.parent.updateModelTransform()
596 self.tempMatrix = None
597 for obj in self.parent.objectList:
598 obj.steepDirty = True
599 self.parent.tool.OnDragEnd()
601 if e.Dragging() and e.RightIsDown():
602 self.zoom += e.GetY() - self.oldY
610 def getObjectBoundaryCircle(self):
611 return self.parent.objectsBoundaryCircleSize
613 def getObjectSize(self):
614 return self.parent.objectsSize
616 def getObjectMatrix(self):
617 return self.parent.matrix
619 def getObjectCenterPos(self):
620 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
622 def OnMouseWheel(self,e):
623 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
631 opengl.InitGL(self, self.view3D, self.zoom)
633 glTranslate(0,0,-self.zoom)
634 glTranslate(self.zoom/20.0,0,0)
635 glRotate(-self.pitch, 1,0,0)
636 glRotate(self.yaw, 0,0,1)
638 if self.viewMode == "GCode" or self.viewMode == "Mixed":
639 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:
640 self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSelect.getValue()][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 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
658 if self.gcodeDisplayList is not None:
659 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
660 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
661 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
662 self.parent.gcodeDirty = False
663 self.gcodeDisplayListMade = 0
665 if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
666 gcodeGenStartTime = time.time()
667 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
668 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
669 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
671 self.gcodeDisplayListMade += 1
672 wx.CallAfter(self.Refresh)
675 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
676 for obj in self.parent.objectList:
679 if obj.displayList is None:
680 obj.displayList = glGenLists(1)
681 obj.steepDisplayList = glGenLists(1)
682 obj.outlineDisplayList = glGenLists(1)
685 glNewList(obj.displayList, GL_COMPILE)
686 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
688 glNewList(obj.outlineDisplayList, GL_COMPILE)
689 opengl.DrawMeshOutline(obj.mesh)
692 if self.viewMode == "Mixed":
694 glColor3f(0.0,0.0,0.0)
695 self.drawModel(obj.displayList)
696 glColor3f(1.0,1.0,1.0)
697 glClear(GL_DEPTH_BUFFER_BIT)
701 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
703 if profile.getPreference('machine_center_is_zero') == 'True':
704 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
705 glEnable(GL_COLOR_MATERIAL)
706 glEnable(GL_LIGHTING)
707 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
708 starttime = time.time()
709 for i in xrange(drawUpToLayer - 1, -1, -1):
711 if i < self.parent.layerSelect.getValue():
712 c = 0.9 - (drawUpToLayer - i) * 0.1
717 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
718 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
719 glCallList(self.gcodeDisplayList + i)
720 if time.time() - starttime > 0.1:
723 glDisable(GL_LIGHTING)
724 glDisable(GL_COLOR_MATERIAL)
725 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
726 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
729 glColor3f(1.0,1.0,1.0)
731 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
732 for obj in self.parent.objectList:
736 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
737 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
738 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
739 #If we want transparent, then first render a solid black model to remove the printer size lines.
740 if self.viewMode != "Mixed":
742 glColor3f(0.0,0.0,0.0)
743 self.drawModel(obj.displayList)
744 glColor3f(1.0,1.0,1.0)
745 #After the black model is rendered, render the model again but now with lighting and no depth testing.
746 glDisable(GL_DEPTH_TEST)
747 glEnable(GL_LIGHTING)
749 glBlendFunc(GL_ONE, GL_ONE)
750 glEnable(GL_LIGHTING)
751 self.drawModel(obj.displayList)
752 glEnable(GL_DEPTH_TEST)
753 elif self.viewMode == "X-Ray":
754 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
755 glDisable(GL_LIGHTING)
756 glDisable(GL_DEPTH_TEST)
757 glEnable(GL_STENCIL_TEST)
758 glStencilFunc(GL_ALWAYS, 1, 1)
759 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
760 self.drawModel(obj.displayList)
761 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
763 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
764 glStencilFunc(GL_EQUAL, 0, 1)
766 self.drawModel(obj.displayList)
767 glStencilFunc(GL_EQUAL, 1, 1)
769 self.drawModel(obj.displayList)
773 for i in xrange(2, 15, 2):
774 glStencilFunc(GL_EQUAL, i, 0xFF);
775 glColor(float(i)/10, float(i)/10, float(i)/5)
777 glVertex3f(-1000,-1000,-1)
778 glVertex3f( 1000,-1000,-1)
779 glVertex3f( 1000, 1000,-1)
780 glVertex3f(-1000, 1000,-1)
782 for i in xrange(1, 15, 2):
783 glStencilFunc(GL_EQUAL, i, 0xFF);
784 glColor(float(i)/10, 0, 0)
786 glVertex3f(-1000,-1000,-1)
787 glVertex3f( 1000,-1000,-1)
788 glVertex3f( 1000, 1000,-1)
789 glVertex3f(-1000, 1000,-1)
793 glDisable(GL_STENCIL_TEST)
794 glEnable(GL_DEPTH_TEST)
796 #Fix the depth buffer
797 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
798 self.drawModel(obj.displayList)
799 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
800 elif self.viewMode == "Normal":
801 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
802 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
803 glEnable(GL_LIGHTING)
804 self.drawModel(obj.displayList)
806 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
807 glEnable(GL_DEPTH_TEST)
808 glDisable(GL_LIGHTING)
810 self.drawModel(obj.outlineDisplayList)
812 if self.drawSteepOverhang:
814 obj.steepDirty = False
815 glNewList(obj.steepDisplayList, GL_COMPILE)
816 opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
818 glDisable(GL_LIGHTING)
820 self.drawModel(obj.steepDisplayList)
823 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
824 # glDisable(GL_LIGHTING)
825 # glDisable(GL_DEPTH_TEST)
826 # glDisable(GL_BLEND)
829 # for err in self.parent.errorList:
830 # glVertex3f(err[0].x, err[0].y, err[0].z)
831 # glVertex3f(err[1].x, err[1].y, err[1].z)
833 # glEnable(GL_DEPTH_TEST)
835 opengl.DrawMachine(machineSize)
837 #Draw the current selected tool
838 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
840 pos = self.getObjectCenterPos()
841 glTranslate(pos[0], pos[1], pos[2])
842 self.parent.tool.OnDraw()
845 def drawModel(self, displayList):
846 vMin = self.parent.objectsMinV
847 vMax = self.parent.objectsMaxV
848 offset = - vMin - (vMax - vMin) / 2
850 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
853 glTranslate(0, 0, self.parent.objectsSize[2]/2)
854 if self.tempMatrix is not None:
855 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
856 glMultMatrixf(tempMatrix)
857 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
858 glTranslate(offset[0], offset[1], -vMin[2])
859 glMultMatrixf(matrix)
860 glCallList(displayList)