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 toolbarUtil
26 from Cura.gui.util import previewTools
27 from Cura.gui.util import openglGui
29 class previewObject():
33 self.displayList = None
36 class previewPanel(wx.Panel):
37 def __init__(self, parent):
38 super(previewPanel, self).__init__(parent,-1)
40 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
41 self.SetMinSize((440,320))
44 self.glButtonList = []
48 self.objectsMinV = None
49 self.objectsMaxV = None
50 self.objectsBoundaryCircleSize = None
51 self.loadThread = None
52 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
53 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
55 self.glCanvas = PreviewGLCanvas(self)
56 #Create the popup window
57 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
58 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
59 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
60 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
61 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
62 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
63 self.warningPopup.SetSizer(self.warningPopup.sizer)
64 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
65 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
66 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
67 self.warningPopup.Fit()
68 self.warningPopup.Layout()
69 self.warningPopup.timer = wx.Timer(self)
70 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
72 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
73 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
74 parent.Bind(wx.EVT_MOVE, self.OnMove)
75 parent.Bind(wx.EVT_SIZE, self.OnMove)
77 self.toolbar = toolbarUtil.Toolbar(self)
80 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
81 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
82 self.toolbar.AddSeparator()
84 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
85 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
86 self.toolbar.AddSeparator()
89 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
90 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
91 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
92 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
93 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
94 self.toolbar.AddSeparator()
96 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
97 self.toolbar.AddControl(self.layerSpin)
98 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
102 sizer = wx.BoxSizer(wx.VERTICAL)
103 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
104 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
107 self.checkReloadFileTimer = wx.Timer(self)
108 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
109 self.checkReloadFileTimer.Start(1000)
111 self.infoToolButton = openglGui.glButton(self, 0, 0,0, self.OnInfoSelect)
112 self.rotateToolButton = openglGui.glButton(self, 1, 0,1, self.OnRotateSelect)
113 self.scaleToolButton = openglGui.glButton(self, 2, 0,2, self.OnScaleSelect)
115 self.resetRotationButton = openglGui.glButton(self, 4, 1,0, self.OnRotateReset)
116 self.layFlatButton = openglGui.glButton(self, 5, 2,0, self.OnLayFlat)
118 self.resetScaleButton = openglGui.glButton(self, 8, 1,0, self.OnScaleReset)
119 self.scaleMaxButton = openglGui.glButton(self, 9, 2,0, self.OnScaleMax)
121 self.infoToolButton.setSelected(True)
122 self.returnToModelViewAndUpdateModel()
124 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
125 self.tool = previewTools.toolInfo(self.glCanvas)
127 def returnToModelViewAndUpdateModel(self):
128 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
129 self.setViewMode('Normal')
130 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
131 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
132 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
133 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
134 self.updateModelTransform()
136 def OnInfoSelect(self):
137 self.infoToolButton.setSelected(True)
138 self.rotateToolButton.setSelected(False)
139 self.scaleToolButton.setSelected(False)
140 self.tool = previewTools.toolInfo(self.glCanvas)
141 self.returnToModelViewAndUpdateModel()
143 def OnRotateSelect(self):
144 self.infoToolButton.setSelected(False)
145 self.rotateToolButton.setSelected(True)
146 self.scaleToolButton.setSelected(False)
147 self.tool = previewTools.toolRotate(self.glCanvas)
148 self.returnToModelViewAndUpdateModel()
150 def OnScaleSelect(self):
151 self.infoToolButton.setSelected(False)
152 self.rotateToolButton.setSelected(False)
153 self.scaleToolButton.setSelected(True)
154 self.tool = previewTools.toolScale(self.glCanvas)
155 self.returnToModelViewAndUpdateModel()
157 def OnMove(self, e = None):
160 x, y = self.glCanvas.ClientToScreenXY(0, 0)
161 sx, sy = self.glCanvas.GetClientSizeTuple()
162 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
164 def OnScaleMax(self, onlyScaleDown = False):
165 if self.objectsMinV is None:
167 vMin = self.objectsMinV
168 vMax = self.objectsMaxV
170 if profile.getProfileSettingFloat('skirt_line_count') > 0:
171 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
172 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
173 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
174 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
175 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
176 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
177 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
178 if scale > 1.0 and onlyScaleDown:
180 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
181 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
182 self.setViewMode('Normal')
183 self.updateModelTransform()
185 def OnRotateReset(self):
186 x = numpy.linalg.norm(self.matrix[0].getA().flatten())
187 y = numpy.linalg.norm(self.matrix[1].getA().flatten())
188 z = numpy.linalg.norm(self.matrix[2].getA().flatten())
189 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
190 self.updateModelTransform()
192 def OnScaleReset(self):
193 x = 1/numpy.linalg.norm(self.matrix[0].getA().flatten())
194 y = 1/numpy.linalg.norm(self.matrix[1].getA().flatten())
195 z = 1/numpy.linalg.norm(self.matrix[2].getA().flatten())
196 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
197 self.updateModelTransform()
200 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
201 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
204 for v in transformedVertexes:
205 diff = v - minZvertex
206 len = math.sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])
209 dot = (diff[2] / len)
215 rad = -math.atan2(dotV[1], dotV[0])
216 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
217 rad = -math.asin(dotMin)
218 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
221 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
222 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
225 for v in transformedVertexes:
226 diff = v - minZvertex
227 len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
230 dot = (diff[2] / len)
237 rad = math.asin(dotMin)
239 rad = -math.asin(dotMin)
240 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
242 self.updateModelTransform()
245 self.glCanvas.yaw = 30
246 self.glCanvas.pitch = 60
247 self.glCanvas.zoom = 300
248 self.glCanvas.view3D = True
249 self.glCanvas.Refresh()
251 def OnTopClick(self):
252 self.glCanvas.view3D = False
253 self.glCanvas.zoom = 100
254 self.glCanvas.offsetX = 0
255 self.glCanvas.offsetY = 0
256 self.glCanvas.Refresh()
258 def OnLayerNrChange(self, e):
259 self.glCanvas.Refresh()
261 def setViewMode(self, mode):
263 self.normalViewButton.SetValue(True)
265 self.gcodeViewButton.SetValue(True)
266 self.glCanvas.viewMode = mode
267 wx.CallAfter(self.glCanvas.Refresh)
269 def loadModelFiles(self, filelist, showWarning = False):
270 while len(filelist) > len(self.objectList):
271 self.objectList.append(previewObject())
272 for idx in xrange(len(filelist), len(self.objectList)):
273 self.objectList[idx].mesh = None
274 self.objectList[idx].filename = None
275 for idx in xrange(0, len(filelist)):
276 obj = self.objectList[idx]
277 if obj.filename != filelist[idx]:
279 self.gcodeFileTime = None
280 self.logFileTime = None
281 obj.filename = filelist[idx]
283 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
284 #Do the STL file loading in a background thread so we don't block the UI.
285 if self.loadThread is not None and self.loadThread.isAlive():
286 self.loadThread.join()
287 self.loadThread = threading.Thread(target=self.doFileLoadThread)
288 self.loadThread.daemon = True
289 self.loadThread.start()
292 if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
293 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
295 def OnCheckReloadFile(self, e):
296 #Only show the reload popup when the window has focus, because the popup goes over other programs.
297 if self.GetParent().FindFocus() is None:
299 for obj in self.objectList:
300 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
301 self.checkReloadFileTimer.Stop()
302 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
304 def reloadModelFiles(self, filelist = None):
305 if filelist is not None:
306 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
307 for idx in xrange(0, len(filelist)):
308 if self.objectList[idx].filename != filelist[idx]:
312 for idx in xrange(0, len(self.objectList)):
313 filelist.append(self.objectList[idx].filename)
314 self.loadModelFiles(filelist)
317 def doFileLoadThread(self):
318 for obj in self.objectList:
319 if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
320 obj.fileTime = os.stat(obj.filename).st_mtime
321 mesh = meshLoader.loadMesh(obj.filename)
324 self.updateModelTransform()
325 self.OnScaleMax(True)
326 self.glCanvas.zoom = numpy.max(self.objectsSize) * 3.5
328 wx.CallAfter(self.updateToolbar)
329 wx.CallAfter(self.glCanvas.Refresh)
331 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
332 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
333 gcode = gcodeInterpreter.gcode()
334 gcode.progressCallback = self.loadProgress
335 gcode.load(self.gcodeFilename)
336 self.gcodeDirty = False
338 self.gcodeDirty = True
341 for line in open(self.gcodeFilename, "rt"):
342 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)
344 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
345 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
346 errorList.append([v1, v2])
347 self.errorList = errorList
349 wx.CallAfter(self.updateToolbar)
350 wx.CallAfter(self.glCanvas.Refresh)
351 elif not os.path.isfile(self.gcodeFilename):
353 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
355 def loadProgress(self, progress):
358 def OnResetAll(self, e = None):
359 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
360 profile.setPluginConfig([])
361 self.updateProfileToControls()
363 def ShowWarningPopup(self, text, callback = None):
364 self.warningPopup.text.SetLabel(text)
365 self.warningPopup.callback = callback
367 self.warningPopup.yesButton.Show(False)
368 self.warningPopup.noButton.SetLabel('ok')
370 self.warningPopup.yesButton.Show(True)
371 self.warningPopup.noButton.SetLabel('no')
372 self.warningPopup.Fit()
373 self.warningPopup.Layout()
375 self.warningPopup.Show(True)
376 self.warningPopup.timer.Start(5000)
378 def OnWarningPopup(self, e):
379 self.warningPopup.Show(False)
380 self.warningPopup.timer.Stop()
381 self.warningPopup.callback()
383 def OnHideWarning(self, e):
384 self.warningPopup.Show(False)
385 self.warningPopup.timer.Stop()
387 def updateToolbar(self):
388 self.gcodeViewButton.Show(self.gcode != None)
389 self.mixedViewButton.Show(self.gcode != None)
390 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
391 if self.gcode != None:
392 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
393 self.toolbar.Realize()
396 def OnViewChange(self):
397 if self.normalViewButton.GetValue():
398 self.glCanvas.viewMode = "Normal"
399 elif self.transparentViewButton.GetValue():
400 self.glCanvas.viewMode = "Transparent"
401 elif self.xrayViewButton.GetValue():
402 self.glCanvas.viewMode = "X-Ray"
403 elif self.gcodeViewButton.GetValue():
404 self.glCanvas.viewMode = "GCode"
405 elif self.mixedViewButton.GetValue():
406 self.glCanvas.viewMode = "Mixed"
407 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
408 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
410 self.glCanvas.Refresh()
412 def updateModelTransform(self, f=0):
413 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
416 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
417 for obj in self.objectList:
420 obj.mesh.matrix = self.matrix
421 obj.mesh.processMatrix()
423 minV = self.objectList[0].mesh.getMinimum()
424 maxV = self.objectList[0].mesh.getMaximum()
425 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
426 for obj in self.objectList:
430 minV = numpy.minimum(minV, obj.mesh.getMinimum())
431 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
432 objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
434 self.objectsMaxV = maxV
435 self.objectsMinV = minV
436 self.objectsSize = self.objectsMaxV - self.objectsMinV
437 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
439 self.glCanvas.Refresh()
441 def updateProfileToControls(self):
442 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
443 self.updateModelTransform()
444 self.glCanvas.updateProfileToControls()
446 class PreviewGLCanvas(glcanvas.GLCanvas):
447 def __init__(self, parent):
448 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
449 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
451 self.context = glcanvas.GLContext(self)
452 wx.EVT_PAINT(self, self.OnPaint)
453 wx.EVT_SIZE(self, self.OnSize)
454 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
455 wx.EVT_MOUSE_EVENTS(self, self.OnMouseEvents)
456 wx.EVT_MOTION(self, self.OnMouseMotion)
457 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
464 self.gcodeDisplayList = None
465 self.gcodeDisplayListMade = None
466 self.gcodeDisplayListCount = 0
467 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]]
471 self.tempMatrix = None
474 def updateProfileToControls(self):
475 self.objColor[0] = profile.getPreferenceColour('model_colour')
476 self.objColor[1] = profile.getPreferenceColour('model_colour2')
477 self.objColor[2] = profile.getPreferenceColour('model_colour3')
478 self.objColor[3] = profile.getPreferenceColour('model_colour4')
480 def OnMouseEvents(self,e):
481 if e.ButtonDown() and e.LeftIsDown():
482 for ctrl in self.parent.glButtonList:
483 if ctrl.OnMouseDown(e.GetX(), e.GetY()):
486 def OnMouseMotion(self,e):
488 for ctrl in self.parent.glButtonList:
489 if ctrl.OnMouseMotion(e.GetX(), e.GetY()):
491 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
492 p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
493 p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
494 if not e.Dragging() or self.dragType != 'tool':
495 self.parent.tool.OnMouseMove(p0, p1)
497 if e.Dragging() and e.LeftIsDown():
498 if self.dragType == '':
499 #Define the drag type depending on the cursor position.
500 self.dragType = 'viewRotate'
501 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
502 if self.parent.tool.OnDragStart(p0, p1):
503 self.dragType = 'tool'
505 if self.dragType == 'viewRotate':
507 self.yaw += e.GetX() - self.oldX
508 self.pitch -= e.GetY() - self.oldY
514 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
515 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
516 elif self.dragType == 'tool':
517 self.parent.tool.OnDrag(p0, p1)
519 #Workaround for buggy ATI cards.
520 size = self.GetSizeTuple()
521 self.SetSize((size[0]+1, size[1]))
522 self.SetSize((size[0], size[1]))
525 if self.dragType != '':
526 if self.tempMatrix is not None:
527 self.parent.matrix *= self.tempMatrix
528 self.parent.updateModelTransform()
529 self.tempMatrix = None
530 self.parent.tool.OnDragEnd()
534 if e.Dragging() and e.RightIsDown():
535 self.zoom += e.GetY() - self.oldY
543 def getObjectBoundaryCircle(self):
544 return self.parent.objectsBoundaryCircleSize
546 def getObjectSize(self):
547 return self.parent.objectsSize
549 def OnMouseWheel(self,e):
550 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
557 def OnEraseBackground(self,event):
558 #Workaround for windows background redraw flicker.
565 dc = wx.PaintDC(self)
566 self.SetCurrent(self.context)
567 opengl.InitGL(self, self.view3D, self.zoom)
569 glTranslate(0,0,-self.zoom)
570 glRotate(-self.pitch, 1,0,0)
571 glRotate(self.yaw, 0,0,1)
572 if self.viewMode == "GCode" or self.viewMode == "Mixed":
573 if self.parent.gcode is not None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:
574 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
576 if self.parent.objectsMaxV is not None:
577 glTranslate(0,0,-self.parent.objectsSize[2] / 2)
579 glTranslate(self.offsetX, self.offsetY, 0)
581 self.viewport = glGetIntegerv(GL_VIEWPORT)
582 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
583 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
585 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
591 machineSize = self.parent.machineSize
593 if self.parent.gcode is not None and self.parent.gcodeDirty:
594 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
595 if self.gcodeDisplayList is not None:
596 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
597 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
598 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
599 self.parent.gcodeDirty = False
600 self.gcodeDisplayListMade = 0
602 if self.parent.gcode is not None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
603 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
604 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
606 self.gcodeDisplayListMade += 1
607 wx.CallAfter(self.Refresh)
610 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
611 for obj in self.parent.objectList:
614 if obj.displayList is None:
615 obj.displayList = glGenLists(1)
616 obj.steepDisplayList = glGenLists(1)
617 obj.outlineDisplayList = glGenLists(1)
620 glNewList(obj.displayList, GL_COMPILE)
621 opengl.DrawMesh(obj.mesh)
623 glNewList(obj.steepDisplayList, GL_COMPILE)
624 opengl.DrawMeshSteep(obj.mesh, 60)
626 glNewList(obj.outlineDisplayList, GL_COMPILE)
627 opengl.DrawMeshOutline(obj.mesh)
630 if self.viewMode == "Mixed":
632 glColor3f(0.0,0.0,0.0)
633 self.drawModel(obj.displayList)
634 glColor3f(1.0,1.0,1.0)
635 glClear(GL_DEPTH_BUFFER_BIT)
639 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
641 if profile.getPreference('machine_center_is_zero') == 'True':
642 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
643 glEnable(GL_COLOR_MATERIAL)
644 glEnable(GL_LIGHTING)
645 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
646 starttime = time.time()
647 for i in xrange(drawUpToLayer - 1, -1, -1):
649 if i < self.parent.layerSpin.GetValue():
650 c = 0.9 - (drawUpToLayer - i) * 0.1
655 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
656 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
657 glCallList(self.gcodeDisplayList + i)
658 if time.time() - starttime > 0.1:
661 glDisable(GL_LIGHTING)
662 glDisable(GL_COLOR_MATERIAL)
663 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
664 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
667 glColor3f(1.0,1.0,1.0)
669 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
670 for obj in self.parent.objectList:
674 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
675 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
676 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
677 #If we want transparent, then first render a solid black model to remove the printer size lines.
678 if self.viewMode != "Mixed":
680 glColor3f(0.0,0.0,0.0)
681 self.drawModel(obj.displayList)
682 glColor3f(1.0,1.0,1.0)
683 #After the black model is rendered, render the model again but now with lighting and no depth testing.
684 glDisable(GL_DEPTH_TEST)
685 glEnable(GL_LIGHTING)
687 glBlendFunc(GL_ONE, GL_ONE)
688 glEnable(GL_LIGHTING)
689 self.drawModel(obj.displayList)
690 glEnable(GL_DEPTH_TEST)
691 elif self.viewMode == "X-Ray":
692 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
693 glDisable(GL_LIGHTING)
694 glDisable(GL_DEPTH_TEST)
695 glEnable(GL_STENCIL_TEST)
696 glStencilFunc(GL_ALWAYS, 1, 1)
697 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
698 self.drawModel(obj.displayList)
699 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
701 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
702 glStencilFunc(GL_EQUAL, 0, 1)
704 self.drawModel(obj.displayList)
705 glStencilFunc(GL_EQUAL, 1, 1)
707 self.drawModel(obj.displayList)
711 for i in xrange(2, 15, 2):
712 glStencilFunc(GL_EQUAL, i, 0xFF);
713 glColor(float(i)/10, float(i)/10, float(i)/5)
715 glVertex3f(-1000,-1000,-1)
716 glVertex3f( 1000,-1000,-1)
717 glVertex3f( 1000, 1000,-1)
718 glVertex3f(-1000, 1000,-1)
720 for i in xrange(1, 15, 2):
721 glStencilFunc(GL_EQUAL, i, 0xFF);
722 glColor(float(i)/10, 0, 0)
724 glVertex3f(-1000,-1000,-1)
725 glVertex3f( 1000,-1000,-1)
726 glVertex3f( 1000, 1000,-1)
727 glVertex3f(-1000, 1000,-1)
731 glDisable(GL_STENCIL_TEST)
732 glEnable(GL_DEPTH_TEST)
734 #Fix the depth buffer
735 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
736 self.drawModel(obj.displayList)
737 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
738 elif self.viewMode == "Normal":
739 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
740 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
741 glEnable(GL_LIGHTING)
742 self.drawModel(obj.displayList)
744 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
745 glEnable(GL_DEPTH_TEST)
746 glDisable(GL_LIGHTING)
748 self.drawModel(obj.outlineDisplayList)
750 if self.drawSteepOverhang:
751 glDisable(GL_LIGHTING)
753 self.drawModel(obj.steepDisplayList)
756 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
757 # glDisable(GL_LIGHTING)
758 # glDisable(GL_DEPTH_TEST)
759 # glDisable(GL_BLEND)
762 # for err in self.parent.errorList:
763 # glVertex3f(err[0].x, err[0].y, err[0].z)
764 # glVertex3f(err[1].x, err[1].y, err[1].z)
766 # glEnable(GL_DEPTH_TEST)
768 opengl.DrawMachine(machineSize)
770 #Draw the current selected tool
771 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
773 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2]/2)
774 self.parent.tool.OnDraw()
782 glDisable(GL_DEPTH_TEST)
784 glDisable(GL_LIGHTING)
785 glColor4ub(255,255,255,255)
787 glMatrixMode(GL_PROJECTION)
789 size = self.GetSize()
790 glOrtho(0, size.GetWidth()-1, size.GetHeight()-1, 0, -1000.0, 1000.0)
791 glMatrixMode(GL_MODELVIEW)
794 for glButton in self.parent.glButtonList:
797 def drawModel(self, displayList):
798 vMin = self.parent.objectsMinV
799 vMax = self.parent.objectsMaxV
800 offset = - vMin - (vMax - vMin) / 2
802 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
805 glTranslate(0, 0, self.parent.objectsSize[2]/2)
806 if self.tempMatrix is not None:
807 tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
808 glMultMatrixf(tempMatrix)
809 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
810 glTranslate(offset[0], offset[1], -vMin[2])
811 glMultMatrixf(matrix)
812 glCallList(displayList)