1 from __future__ import absolute_import
2 from __future__ import division
11 from wx import glcanvas
15 OpenGL.ERROR_CHECKING = False
16 from OpenGL.GLU import *
17 from OpenGL.GL import *
20 print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
23 from Cura.gui.util import opengl
24 from Cura.gui.util import toolbarUtil
26 from Cura.util import profile
27 from Cura.util import gcodeInterpreter
28 from Cura.util import meshLoader
29 from Cura.util import util3d
30 from Cura.util import sliceRun
32 class previewObject():
36 self.displayList = None
39 class previewPanel(wx.Panel):
40 def __init__(self, parent):
41 super(previewPanel, self).__init__(parent,-1)
43 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
44 self.SetMinSize((440,320))
49 self.objectsMinV = None
50 self.objectsMaxV = None
51 self.objectsBounderyCircleSize = None
52 self.loadThread = None
53 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
54 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
56 self.glCanvas = PreviewGLCanvas(self)
57 #Create the popup window
58 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
59 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
60 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
61 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
62 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
63 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
64 self.warningPopup.SetSizer(self.warningPopup.sizer)
65 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
66 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
67 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
68 self.warningPopup.Fit()
69 self.warningPopup.Layout()
70 self.warningPopup.timer = wx.Timer(self)
71 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
73 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
74 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
75 parent.Bind(wx.EVT_MOVE, self.OnMove)
76 parent.Bind(wx.EVT_SIZE, self.OnMove)
78 self.toolbar = toolbarUtil.Toolbar(self)
81 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
82 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
83 self.toolbar.AddSeparator()
85 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
86 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
87 self.toolbar.AddSeparator()
90 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
91 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
92 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
93 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
94 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
95 self.toolbar.AddSeparator()
97 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
98 self.toolbar.AddControl(self.layerSpin)
99 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
100 self.toolbar.AddSeparator()
101 self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)
102 self.toolbar.AddControl(self.toolbarInfo)
104 self.toolbar2 = toolbarUtil.Toolbar(self)
107 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.returnToModelViewAndUpdateModel)
108 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)
109 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)
110 self.toolbar2.AddSeparator()
113 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.returnToModelViewAndUpdateModel)
114 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)
115 self.toolbar2.AddSeparator()
118 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
119 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
120 self.toolbar2.AddControl(self.scale)
121 self.scale.Bind(wx.EVT_TEXT, self.OnScale)
122 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
124 self.toolbar2.AddSeparator()
127 #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
128 #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
129 #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
130 #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
131 #self.toolbar2.AddSeparator()
134 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
135 self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)
136 self.rotate.SetRange(0, 360)
137 self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
138 self.toolbar2.AddControl(self.rotate)
140 self.toolbar2.Realize()
143 sizer = wx.BoxSizer(wx.VERTICAL)
144 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
145 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
146 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
149 self.checkReloadFileTimer = wx.Timer(self)
150 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
151 self.checkReloadFileTimer.Start(1000)
153 def returnToModelViewAndUpdateModel(self):
154 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
155 self.setViewMode('Normal')
156 self.updateModelTransform()
158 def OnMove(self, e = None):
161 x, y = self.glCanvas.ClientToScreenXY(0, 0)
162 sx, sy = self.glCanvas.GetClientSizeTuple()
163 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
165 def OnMulXAddClick(self, e):
166 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
167 self.glCanvas.Refresh()
169 def OnMulXSubClick(self, e):
170 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
171 self.glCanvas.Refresh()
173 def OnMulYAddClick(self, e):
174 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
175 self.glCanvas.Refresh()
177 def OnMulYSubClick(self, e):
178 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
179 self.glCanvas.Refresh()
181 def OnScaleReset(self, e):
182 self.scale.SetValue('1.0')
185 def OnScale(self, e):
187 if self.scale.GetValue() != '':
188 scale = self.scale.GetValue()
189 profile.putProfileSetting('model_scale', scale)
190 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
191 self.setViewMode('Normal')
192 self.glCanvas.Refresh()
194 if self.objectsMaxV != None:
195 size = (self.objectsMaxV - self.objectsMinV) * float(scale)
196 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
198 def OnScaleMax(self, e = None, onlyScaleDown = False):
199 if self.objectsMinV == None:
201 vMin = self.objectsMinV
202 vMax = self.objectsMaxV
204 if profile.getProfileSettingFloat('skirt_line_count') > 0:
205 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
206 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
207 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
208 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
209 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
210 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
211 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
212 if scale > 1.0 and onlyScaleDown:
214 self.scale.SetValue(str(scale))
215 profile.putProfileSetting('model_scale', self.scale.GetValue())
216 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
217 self.setViewMode('Normal')
218 self.glCanvas.Refresh()
220 def OnRotateReset(self, e):
221 self.rotate.SetValue(0)
224 def OnRotate(self, e):
225 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
226 self.returnToModelViewAndUpdateModel()
229 self.glCanvas.yaw = 30
230 self.glCanvas.pitch = 60
231 self.glCanvas.zoom = 300
232 self.glCanvas.view3D = True
233 self.glCanvas.Refresh()
235 def OnTopClick(self):
236 self.glCanvas.view3D = False
237 self.glCanvas.zoom = 100
238 self.glCanvas.offsetX = 0
239 self.glCanvas.offsetY = 0
240 self.glCanvas.Refresh()
242 def OnLayerNrChange(self, e):
243 self.glCanvas.Refresh()
245 def setViewMode(self, mode):
247 self.normalViewButton.SetValue(True)
249 self.gcodeViewButton.SetValue(True)
250 self.glCanvas.viewMode = mode
251 wx.CallAfter(self.glCanvas.Refresh)
253 def loadModelFiles(self, filelist, showWarning = False):
254 while len(filelist) > len(self.objectList):
255 self.objectList.append(previewObject())
256 for idx in xrange(len(filelist), len(self.objectList)):
257 self.objectList[idx].mesh = None
258 self.objectList[idx].filename = None
259 for idx in xrange(0, len(filelist)):
260 obj = self.objectList[idx]
261 if obj.filename != filelist[idx]:
263 self.gcodeFileTime = None
264 self.logFileTime = None
265 obj.filename = filelist[idx]
267 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
268 #Do the STL file loading in a background thread so we don't block the UI.
269 if self.loadThread != None and self.loadThread.isAlive():
270 self.loadThread.join()
271 self.loadThread = threading.Thread(target=self.doFileLoadThread)
272 self.loadThread.daemon = True
273 self.loadThread.start()
276 if profile.getProfileSettingFloat('model_scale') != 1.0 or profile.getProfileSettingFloat('model_rotate_base') != 0 or profile.getProfileSetting('flip_x') != 'False' or profile.getProfileSetting('flip_y') != 'False' or profile.getProfileSetting('flip_z') != 'False' or profile.getProfileSetting('swap_xz') != 'False' or profile.getProfileSetting('swap_yz') != 'False' or len(profile.getPluginConfig()) > 0:
277 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
279 def OnCheckReloadFile(self, e):
280 #Only show the reload popup when the window has focus, because the popup goes over other programs.
281 if self.GetParent().FindFocus() is None:
283 for obj in self.objectList:
284 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
285 self.checkReloadFileTimer.Stop()
286 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
288 def reloadModelFiles(self, filelist = None):
289 if filelist is not None:
290 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
291 for idx in xrange(0, len(filelist)):
292 if self.objectList[idx].filename != filelist[idx]:
296 for idx in xrange(0, len(self.objectList)):
297 filelist.append(self.objectList[idx].filename)
298 self.loadModelFiles(filelist)
301 def doFileLoadThread(self):
302 for obj in self.objectList:
303 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
304 obj.fileTime = os.stat(obj.filename).st_mtime
305 mesh = meshLoader.loadMesh(obj.filename)
308 self.updateModelTransform()
309 self.OnScaleMax(None, True)
310 scale = profile.getProfileSettingFloat('model_scale')
311 size = (self.objectsMaxV - self.objectsMinV) * scale
312 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
313 self.glCanvas.zoom = numpy.max(size) * 2.5
315 wx.CallAfter(self.updateToolbar)
316 wx.CallAfter(self.glCanvas.Refresh)
318 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
319 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
320 gcode = gcodeInterpreter.gcode()
321 gcode.progressCallback = self.loadProgress
322 gcode.load(self.gcodeFilename)
323 self.gcodeDirty = False
325 self.gcodeDirty = True
328 for line in open(self.gcodeFilename, "rt"):
329 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)
331 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
332 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
333 errorList.append([v1, v2])
334 self.errorList = errorList
336 wx.CallAfter(self.updateToolbar)
337 wx.CallAfter(self.glCanvas.Refresh)
338 elif not os.path.isfile(self.gcodeFilename):
340 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
342 def loadProgress(self, progress):
345 def OnResetAll(self, e = None):
346 profile.putProfileSetting('model_scale', '1.0')
347 profile.putProfileSetting('model_rotate_base', '0')
348 profile.putProfileSetting('flip_x', 'False')
349 profile.putProfileSetting('flip_y', 'False')
350 profile.putProfileSetting('flip_z', 'False')
351 profile.putProfileSetting('swap_xz', 'False')
352 profile.putProfileSetting('swap_yz', 'False')
353 profile.setPluginConfig([])
354 self.GetParent().updateProfileToControls()
356 def ShowWarningPopup(self, text, callback = None):
357 self.warningPopup.text.SetLabel(text)
358 self.warningPopup.callback = callback
360 self.warningPopup.yesButton.Show(False)
361 self.warningPopup.noButton.SetLabel('ok')
363 self.warningPopup.yesButton.Show(True)
364 self.warningPopup.noButton.SetLabel('no')
365 self.warningPopup.Fit()
366 self.warningPopup.Layout()
368 self.warningPopup.Show(True)
369 self.warningPopup.timer.Start(5000)
371 def OnWarningPopup(self, e):
372 self.warningPopup.Show(False)
373 self.warningPopup.timer.Stop()
374 self.warningPopup.callback()
376 def OnHideWarning(self, e):
377 self.warningPopup.Show(False)
378 self.warningPopup.timer.Stop()
380 def updateToolbar(self):
381 self.gcodeViewButton.Show(self.gcode != None)
382 self.mixedViewButton.Show(self.gcode != None)
383 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
384 if self.gcode != None:
385 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
386 self.toolbar.Realize()
389 def OnViewChange(self):
390 if self.normalViewButton.GetValue():
391 self.glCanvas.viewMode = "Normal"
392 elif self.transparentViewButton.GetValue():
393 self.glCanvas.viewMode = "Transparent"
394 elif self.xrayViewButton.GetValue():
395 self.glCanvas.viewMode = "X-Ray"
396 elif self.gcodeViewButton.GetValue():
397 self.glCanvas.viewMode = "GCode"
398 elif self.mixedViewButton.GetValue():
399 self.glCanvas.viewMode = "Mixed"
400 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
401 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
403 self.glCanvas.Refresh()
405 def updateModelTransform(self, f=0):
406 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
409 rotate = profile.getProfileSettingFloat('model_rotate_base')
410 mirrorX = profile.getProfileSetting('flip_x') == 'True'
411 mirrorY = profile.getProfileSetting('flip_y') == 'True'
412 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
413 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
414 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
416 for obj in self.objectList:
419 obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
421 minV = self.objectList[0].mesh.getMinimum()
422 maxV = self.objectList[0].mesh.getMaximum()
423 objectsBounderyCircleSize = self.objectList[0].mesh.bounderyCircleSize
424 for obj in self.objectList:
428 obj.mesh.getMinimumZ()
429 minV = numpy.minimum(minV, obj.mesh.getMinimum())
430 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
431 objectsBounderyCircleSize = max(objectsBounderyCircleSize, obj.mesh.bounderyCircleSize)
433 self.objectsMaxV = maxV
434 self.objectsMinV = minV
435 self.objectsBounderyCircleSize = objectsBounderyCircleSize
436 for obj in self.objectList:
440 obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
441 #for v in obj.mesh.vertexes:
443 # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
444 # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
445 obj.mesh.getMinimumZ()
448 scale = profile.getProfileSettingFloat('model_scale')
449 size = (self.objectsMaxV - self.objectsMinV) * scale
450 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
452 self.glCanvas.Refresh()
454 def updateProfileToControls(self):
455 self.scale.SetValue(profile.getProfileSetting('model_scale'))
456 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
457 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
458 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
459 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
460 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
461 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
462 self.updateModelTransform()
463 self.glCanvas.updateProfileToControls()
465 class PreviewGLCanvas(glcanvas.GLCanvas):
466 def __init__(self, parent):
467 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
468 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
470 self.context = glcanvas.GLContext(self)
471 wx.EVT_PAINT(self, self.OnPaint)
472 wx.EVT_SIZE(self, self.OnSize)
473 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
474 wx.EVT_MOTION(self, self.OnMouseMotion)
475 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
482 self.gcodeDisplayList = None
483 self.gcodeDisplayListMade = None
484 self.gcodeDisplayListCount = 0
485 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]]
492 def updateProfileToControls(self):
493 self.objColor[0] = profile.getPreferenceColour('model_colour')
494 self.objColor[1] = profile.getPreferenceColour('model_colour2')
495 self.objColor[2] = profile.getPreferenceColour('model_colour3')
496 self.objColor[3] = profile.getPreferenceColour('model_colour4')
498 def OnMouseMotion(self,e):
501 if self.parent.objectsMaxV is not None and self.viewport is not None:
502 radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
504 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
505 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
506 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
507 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
508 if radius * 1.1 <= cursorXY <= radius * 1.3:
509 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
511 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
513 if e.Dragging() and e.LeftIsDown():
514 if self.dragType == '':
515 #Define the drag type depending on the cursor position.
516 if radius * 1.1 <= cursorXY <= radius * 1.3 and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
517 self.dragType = 'modelRotate'
518 self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
520 self.dragType = 'viewRotate'
522 if self.dragType == 'viewRotate':
524 self.yaw += e.GetX() - self.oldX
525 self.pitch -= e.GetY() - self.oldY
531 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
532 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
533 elif self.dragType == 'modelRotate':
534 angle = math.atan2(cursorZ0[0], cursorZ0[1])
535 diff = self.dragStart - angle
536 self.tempRotate = diff * 180 / math.pi
537 rot = profile.getProfileSettingFloat('model_rotate_base')
538 self.tempRotate = round((self.tempRotate + rot) / 15) * 15 - rot
539 #Workaround for buggy ATI cards.
540 size = self.GetSizeTuple()
541 self.SetSize((size[0]+1, size[1]))
542 self.SetSize((size[0], size[1]))
545 if self.tempRotate != 0:
546 newRotation = profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate
547 while newRotation >= 360:
549 while newRotation < 0:
551 profile.putProfileSetting('model_rotate_base', newRotation)
552 self.parent.rotate.SetValue(newRotation)
553 self.parent.updateModelTransform()
557 if e.Dragging() and e.RightIsDown():
558 self.zoom += e.GetY() - self.oldY
570 def OnMouseWheel(self,e):
571 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
578 def OnEraseBackground(self,event):
579 #Workaround for windows background redraw flicker.
586 dc = wx.PaintDC(self)
587 if not hasOpenGLlibs:
589 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
591 self.SetCurrent(self.context)
592 opengl.InitGL(self, self.view3D, self.zoom)
594 glTranslate(0,0,-self.zoom)
595 glRotate(-self.pitch, 1,0,0)
596 glRotate(self.yaw, 0,0,1)
597 if self.viewMode == "GCode" or self.viewMode == "Mixed":
598 if self.parent.gcode != None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:
599 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
601 if self.parent.objectsMaxV != None:
602 glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
604 glTranslate(self.offsetX, self.offsetY, 0)
606 self.viewport = glGetIntegerv(GL_VIEWPORT);
607 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
608 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
610 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
616 machineSize = self.parent.machineSize
618 if self.parent.gcode != None and self.parent.gcodeDirty:
619 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
620 if self.gcodeDisplayList != None:
621 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
622 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
623 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
624 self.parent.gcodeDirty = False
625 self.gcodeDisplayListMade = 0
627 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
628 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
629 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
631 self.gcodeDisplayListMade += 1
632 wx.CallAfter(self.Refresh)
635 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
636 for obj in self.parent.objectList:
639 if obj.displayList == None:
640 obj.displayList = glGenLists(1)
641 obj.steepDisplayList = glGenLists(1)
644 glNewList(obj.displayList, GL_COMPILE)
645 opengl.DrawMesh(obj.mesh)
647 glNewList(obj.steepDisplayList, GL_COMPILE)
648 opengl.DrawMeshSteep(obj.mesh, 60)
651 if self.viewMode == "Mixed":
653 glColor3f(0.0,0.0,0.0)
655 glColor3f(1.0,1.0,1.0)
656 glClear(GL_DEPTH_BUFFER_BIT)
660 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
662 if profile.getPreference('machine_center_is_zero') == 'True':
663 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
664 glEnable(GL_COLOR_MATERIAL)
665 glEnable(GL_LIGHTING)
666 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
667 starttime = time.time()
668 for i in xrange(drawUpToLayer - 1, -1, -1):
670 if i < self.parent.layerSpin.GetValue():
671 c = 0.9 - (drawUpToLayer - i) * 0.1
676 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
677 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
678 glCallList(self.gcodeDisplayList + i)
679 if time.time() - starttime > 0.1:
682 glDisable(GL_LIGHTING)
683 glDisable(GL_COLOR_MATERIAL)
684 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
685 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
688 glColor3f(1.0,1.0,1.0)
690 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
691 for obj in self.parent.objectList:
695 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
696 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
697 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
698 #If we want transparent, then first render a solid black model to remove the printer size lines.
699 if self.viewMode != "Mixed":
701 glColor3f(0.0,0.0,0.0)
703 glColor3f(1.0,1.0,1.0)
704 #After the black model is rendered, render the model again but now with lighting and no depth testing.
705 glDisable(GL_DEPTH_TEST)
706 glEnable(GL_LIGHTING)
708 glBlendFunc(GL_ONE, GL_ONE)
709 glEnable(GL_LIGHTING)
711 glEnable(GL_DEPTH_TEST)
712 elif self.viewMode == "X-Ray":
713 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
714 glDisable(GL_LIGHTING)
715 glDisable(GL_DEPTH_TEST)
716 glEnable(GL_STENCIL_TEST)
717 glStencilFunc(GL_ALWAYS, 1, 1)
718 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
720 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
722 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
723 glStencilFunc(GL_EQUAL, 0, 1)
726 glStencilFunc(GL_EQUAL, 1, 1)
732 for i in xrange(2, 15, 2):
733 glStencilFunc(GL_EQUAL, i, 0xFF);
734 glColor(float(i)/10, float(i)/10, float(i)/5)
736 glVertex3f(-1000,-1000,-1)
737 glVertex3f( 1000,-1000,-1)
738 glVertex3f( 1000, 1000,-1)
739 glVertex3f(-1000, 1000,-1)
741 for i in xrange(1, 15, 2):
742 glStencilFunc(GL_EQUAL, i, 0xFF);
743 glColor(float(i)/10, 0, 0)
745 glVertex3f(-1000,-1000,-1)
746 glVertex3f( 1000,-1000,-1)
747 glVertex3f( 1000, 1000,-1)
748 glVertex3f(-1000, 1000,-1)
752 glDisable(GL_STENCIL_TEST)
753 glEnable(GL_DEPTH_TEST)
755 #Fix the depth buffer
756 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
758 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
759 elif self.viewMode == "Normal":
760 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
761 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
762 glEnable(GL_LIGHTING)
765 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
766 glEnable(GL_DEPTH_TEST)
767 glDisable(GL_LIGHTING)
770 modelScale = profile.getProfileSettingFloat('model_scale')
771 glScalef(modelScale, modelScale, modelScale)
772 opengl.DrawMeshOutline(obj.mesh)
775 if self.drawSteepOverhang:
776 glDisable(GL_LIGHTING)
779 modelScale = profile.getProfileSettingFloat('model_scale')
780 glScalef(modelScale, modelScale, modelScale)
781 glCallList(obj.steepDisplayList)
785 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
786 glDisable(GL_LIGHTING)
787 glDisable(GL_DEPTH_TEST)
791 for err in self.parent.errorList:
792 glVertex3f(err[0].x, err[0].y, err[0].z)
793 glVertex3f(err[1].x, err[1].y, err[1].z)
795 glEnable(GL_DEPTH_TEST)
798 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
800 #Draw the rotate circle
801 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
802 glDisable(GL_LIGHTING)
803 glDisable(GL_CULL_FACE)
805 glRotate(self.tempRotate + profile.getProfileSettingFloat('model_rotate_base'), 0, 0, 1)
806 radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
807 glScalef(radius, radius, 1)
808 glBegin(GL_TRIANGLE_STRIP)
809 for i in xrange(0, 64+1):
810 f = i if i < 64/2 else 64 - i
811 glColor4ub(255,int(f*255/(64/2)),0,255)
812 glVertex3f(1.1 * math.cos(i/32.0*math.pi), 1.1 * math.sin(i/32.0*math.pi),0.1)
813 glColor4ub( 0,128,0,255)
814 glVertex3f(1.3 * math.cos(i/32.0*math.pi), 1.3 * math.sin(i/32.0*math.pi),0.1)
816 glBegin(GL_TRIANGLES)
817 glColor4ub(0,0,0,192)
818 glVertex3f(1, 0.1,0.15)
819 glVertex3f(1,-0.1,0.15)
820 glVertex3f(1.4,0,0.15)
822 glEnable(GL_CULL_FACE)
826 opengl.DrawMachine(machineSize)
830 def drawModel(self, obj):
831 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
832 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
833 modelScale = profile.getProfileSettingFloat('model_scale')
834 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
836 glRotate(self.tempRotate, 0, 0, 1)
837 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
838 for mx in xrange(0, multiX):
839 for my in xrange(0, multiY):
841 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
842 glScalef(modelScale, modelScale, modelScale)
843 glCallList(obj.displayList)