1 from __future__ import division
3 import sys, math, threading, re, time, os
6 from wx import glcanvas
10 OpenGL.ERROR_CHECKING = False
11 from OpenGL.GLU import *
12 from OpenGL.GL import *
15 print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
18 from gui import opengl
19 from gui import toolbarUtil
21 from util import profile
22 from util import gcodeInterpreter
23 from util import meshLoader
24 from util import util3d
25 from util import sliceRun
27 class previewObject():
31 self.displayList = None
34 class previewPanel(wx.Panel):
35 def __init__(self, parent):
36 super(previewPanel, self).__init__(parent,-1)
38 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
39 self.SetMinSize((440,320))
44 self.objectsMinV = None
45 self.objectsMaxV = None
46 self.objectsBounderyCircleSize = None
47 self.loadThread = None
48 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
49 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
51 self.glCanvas = PreviewGLCanvas(self)
52 #Create the popup window
53 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
54 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
55 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
56 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
57 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
58 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
59 self.warningPopup.SetSizer(self.warningPopup.sizer)
60 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
61 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
62 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
63 self.warningPopup.Fit()
64 self.warningPopup.Layout()
65 self.warningPopup.timer = wx.Timer(self)
66 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
68 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
69 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
70 parent.Bind(wx.EVT_MOVE, self.OnMove)
71 parent.Bind(wx.EVT_SIZE, self.OnMove)
73 self.toolbar = toolbarUtil.Toolbar(self)
76 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
77 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
78 self.toolbar.AddSeparator()
80 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
81 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
82 self.toolbar.AddSeparator()
85 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
86 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
87 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
88 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
89 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
90 self.toolbar.AddSeparator()
92 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
93 self.toolbar.AddControl(self.layerSpin)
94 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
95 self.toolbar.AddSeparator()
96 self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)
97 self.toolbar.AddControl(self.toolbarInfo)
99 self.toolbar2 = toolbarUtil.Toolbar(self)
102 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.returnToModelViewAndUpdateModel)
103 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)
104 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)
105 self.toolbar2.AddSeparator()
108 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.returnToModelViewAndUpdateModel)
109 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)
110 self.toolbar2.AddSeparator()
113 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
114 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
115 self.toolbar2.AddControl(self.scale)
116 self.scale.Bind(wx.EVT_TEXT, self.OnScale)
117 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
119 self.toolbar2.AddSeparator()
122 #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
123 #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
124 #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
125 #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
126 #self.toolbar2.AddSeparator()
129 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
130 self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)
131 self.rotate.SetRange(0, 360)
132 self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
133 self.toolbar2.AddControl(self.rotate)
135 self.toolbar2.Realize()
138 sizer = wx.BoxSizer(wx.VERTICAL)
139 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
140 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
141 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
144 def returnToModelViewAndUpdateModel(self):
145 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
146 self.setViewMode('Normal')
147 self.updateModelTransform()
149 def OnMove(self, e = None):
152 x, y = self.glCanvas.ClientToScreenXY(0, 0)
153 sx, sy = self.glCanvas.GetClientSizeTuple()
154 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
156 def OnMulXAddClick(self, e):
157 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
158 self.glCanvas.Refresh()
160 def OnMulXSubClick(self, e):
161 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
162 self.glCanvas.Refresh()
164 def OnMulYAddClick(self, e):
165 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
166 self.glCanvas.Refresh()
168 def OnMulYSubClick(self, e):
169 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
170 self.glCanvas.Refresh()
172 def OnScaleReset(self, e):
173 self.scale.SetValue('1.0')
176 def OnScale(self, e):
178 if self.scale.GetValue() != '':
179 scale = self.scale.GetValue()
180 profile.putProfileSetting('model_scale', scale)
181 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
182 self.setViewMode('Normal')
183 self.glCanvas.Refresh()
185 if self.objectsMaxV != None:
186 size = (self.objectsMaxV - self.objectsMinV) * float(scale)
187 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
189 def OnScaleMax(self, e = None, onlyScaleDown = False):
190 if self.objectsMinV == None:
192 vMin = self.objectsMinV
193 vMax = self.objectsMaxV
195 if profile.getProfileSettingFloat('skirt_line_count') > 0:
196 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
197 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
198 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
199 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
200 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
201 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
202 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
203 if scale > 1.0 and onlyScaleDown:
205 self.scale.SetValue(str(scale))
206 profile.putProfileSetting('model_scale', self.scale.GetValue())
207 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
208 self.setViewMode('Normal')
209 self.glCanvas.Refresh()
211 def OnRotateReset(self, e):
212 self.rotate.SetValue(0)
215 def OnRotate(self, e):
216 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
217 self.returnToModelViewAndUpdateModel()
220 self.glCanvas.yaw = 30
221 self.glCanvas.pitch = 60
222 self.glCanvas.zoom = 300
223 self.glCanvas.view3D = True
224 self.glCanvas.Refresh()
226 def OnTopClick(self):
227 self.glCanvas.view3D = False
228 self.glCanvas.zoom = 100
229 self.glCanvas.offsetX = 0
230 self.glCanvas.offsetY = 0
231 self.glCanvas.Refresh()
233 def OnLayerNrChange(self, e):
234 self.glCanvas.Refresh()
236 def setViewMode(self, mode):
238 self.normalViewButton.SetValue(True)
240 self.gcodeViewButton.SetValue(True)
241 self.glCanvas.viewMode = mode
242 wx.CallAfter(self.glCanvas.Refresh)
244 def loadModelFiles(self, filelist, showWarning = False):
245 while len(filelist) > len(self.objectList):
246 self.objectList.append(previewObject())
247 for idx in xrange(len(filelist), len(self.objectList)):
248 self.objectList[idx].mesh = None
249 self.objectList[idx].filename = None
250 for idx in xrange(0, len(filelist)):
251 obj = self.objectList[idx]
252 if obj.filename != filelist[idx]:
254 self.gcodeFileTime = None
255 self.logFileTime = None
256 obj.filename = filelist[idx]
258 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
259 #Do the STL file loading in a background thread so we don't block the UI.
260 if self.loadThread != None and self.loadThread.isAlive():
261 self.loadThread.join()
262 self.loadThread = threading.Thread(target=self.doFileLoadThread)
263 self.loadThread.daemon = True
264 self.loadThread.start()
267 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:
268 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
270 def loadReModelFiles(self, filelist):
271 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
272 for idx in xrange(0, len(filelist)):
273 if self.objectList[idx].filename != filelist[idx]:
275 self.loadModelFiles(filelist)
278 def doFileLoadThread(self):
279 for obj in self.objectList:
280 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
281 obj.ileTime = os.stat(obj.filename).st_mtime
282 mesh = meshLoader.loadMesh(obj.filename)
285 self.updateModelTransform()
286 self.OnScaleMax(None, True)
287 scale = profile.getProfileSettingFloat('model_scale')
288 size = (self.objectsMaxV - self.objectsMinV) * scale
289 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
290 self.glCanvas.zoom = numpy.max(size) * 2.5
292 wx.CallAfter(self.updateToolbar)
293 wx.CallAfter(self.glCanvas.Refresh)
295 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
296 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
297 gcode = gcodeInterpreter.gcode()
298 gcode.progressCallback = self.loadProgress
299 gcode.load(self.gcodeFilename)
300 self.gcodeDirty = False
302 self.gcodeDirty = True
305 for line in open(self.gcodeFilename, "rt"):
306 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)
308 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
309 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
310 errorList.append([v1, v2])
311 self.errorList = errorList
313 wx.CallAfter(self.updateToolbar)
314 wx.CallAfter(self.glCanvas.Refresh)
315 elif not os.path.isfile(self.gcodeFilename):
318 def loadProgress(self, progress):
321 def OnResetAll(self, e = None):
322 profile.putProfileSetting('model_scale', '1.0')
323 profile.putProfileSetting('model_rotate_base', '0')
324 profile.putProfileSetting('flip_x', 'False')
325 profile.putProfileSetting('flip_y', 'False')
326 profile.putProfileSetting('flip_z', 'False')
327 profile.putProfileSetting('swap_xz', 'False')
328 profile.putProfileSetting('swap_yz', 'False')
329 profile.setPluginConfig([])
330 self.GetParent().updateProfileToControls()
332 def ShowWarningPopup(self, text, callback = None):
333 self.warningPopup.text.SetLabel(text)
334 self.warningPopup.callback = callback
336 self.warningPopup.yesButton.Show(False)
337 self.warningPopup.noButton.SetLabel('ok')
339 self.warningPopup.yesButton.Show(True)
340 self.warningPopup.noButton.SetLabel('no')
341 self.warningPopup.Fit()
342 self.warningPopup.Layout()
344 self.warningPopup.Show(True)
345 self.warningPopup.timer.Start(5000)
347 def OnWarningPopup(self, e):
348 self.warningPopup.Show(False)
349 self.warningPopup.timer.Stop()
350 self.warningPopup.callback()
352 def OnHideWarning(self, e):
353 self.warningPopup.Show(False)
354 self.warningPopup.timer.Stop()
356 def updateToolbar(self):
357 self.gcodeViewButton.Show(self.gcode != None)
358 self.mixedViewButton.Show(self.gcode != None)
359 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
360 if self.gcode != None:
361 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
362 self.toolbar.Realize()
365 def OnViewChange(self):
366 if self.normalViewButton.GetValue():
367 self.glCanvas.viewMode = "Normal"
368 elif self.transparentViewButton.GetValue():
369 self.glCanvas.viewMode = "Transparent"
370 elif self.xrayViewButton.GetValue():
371 self.glCanvas.viewMode = "X-Ray"
372 elif self.gcodeViewButton.GetValue():
373 self.glCanvas.viewMode = "GCode"
374 elif self.mixedViewButton.GetValue():
375 self.glCanvas.viewMode = "Mixed"
376 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
377 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
379 self.glCanvas.Refresh()
381 def updateModelTransform(self, f=0):
382 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
385 rotate = profile.getProfileSettingFloat('model_rotate_base')
386 mirrorX = profile.getProfileSetting('flip_x') == 'True'
387 mirrorY = profile.getProfileSetting('flip_y') == 'True'
388 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
389 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
390 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
392 for obj in self.objectList:
395 obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
397 minV = self.objectList[0].mesh.getMinimum()
398 maxV = self.objectList[0].mesh.getMaximum()
399 objectsBounderyCircleSize = self.objectList[0].mesh.bounderyCircleSize
400 for obj in self.objectList:
404 obj.mesh.getMinimumZ()
405 minV = numpy.minimum(minV, obj.mesh.getMinimum())
406 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
407 objectsBounderyCircleSize = max(objectsBounderyCircleSize, obj.mesh.bounderyCircleSize)
409 self.objectsMaxV = maxV
410 self.objectsMinV = minV
411 self.objectsBounderyCircleSize = objectsBounderyCircleSize
412 for obj in self.objectList:
416 obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
417 #for v in obj.mesh.vertexes:
419 # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
420 # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
421 obj.mesh.getMinimumZ()
424 scale = profile.getProfileSettingFloat('model_scale')
425 size = (self.objectsMaxV - self.objectsMinV) * scale
426 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
428 self.glCanvas.Refresh()
430 def updateProfileToControls(self):
431 self.scale.SetValue(profile.getProfileSetting('model_scale'))
432 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
433 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
434 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
435 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
436 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
437 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
438 self.updateModelTransform()
439 self.glCanvas.updateProfileToControls()
441 class PreviewGLCanvas(glcanvas.GLCanvas):
442 def __init__(self, parent):
443 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
444 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
446 self.context = glcanvas.GLContext(self)
447 wx.EVT_PAINT(self, self.OnPaint)
448 wx.EVT_SIZE(self, self.OnSize)
449 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
450 wx.EVT_MOTION(self, self.OnMouseMotion)
451 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
458 self.gcodeDisplayList = None
459 self.gcodeDisplayListMade = None
460 self.gcodeDisplayListCount = 0
461 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]]
467 def updateProfileToControls(self):
468 self.objColor[0] = profile.getPreferenceColour('model_colour')
469 self.objColor[1] = profile.getPreferenceColour('model_colour2')
470 self.objColor[2] = profile.getPreferenceColour('model_colour3')
471 self.objColor[3] = profile.getPreferenceColour('model_colour4')
473 def OnMouseMotion(self,e):
476 if self.parent.objectsMaxV != None:
477 radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
479 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
480 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
481 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
482 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
483 if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
484 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
486 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
488 if e.Dragging() and e.LeftIsDown():
489 if self.dragType == '':
490 #Define the drag type depending on the cursor position.
491 if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
492 self.dragType = 'modelRotate'
493 self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
495 self.dragType = 'viewRotate'
497 if self.dragType == 'viewRotate':
499 self.yaw += e.GetX() - self.oldX
500 self.pitch -= e.GetY() - self.oldY
506 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
507 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
508 elif self.dragType == 'modelRotate':
509 angle = math.atan2(cursorZ0[0], cursorZ0[1])
510 diff = self.dragStart - angle
511 self.tempRotate = diff * 180 / math.pi
512 rot = profile.getProfileSettingFloat('model_rotate_base')
513 self.tempRotate = round((self.tempRotate + rot) / 15) * 15 - rot
514 #Workaround for buggy ATI cards.
515 size = self.GetSizeTuple()
516 self.SetSize((size[0]+1, size[1]))
517 self.SetSize((size[0], size[1]))
520 if self.tempRotate != 0:
521 newRotation = profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate
522 while newRotation >= 360:
524 while newRotation < 0:
526 profile.putProfileSetting('model_rotate_base', newRotation)
527 self.parent.rotate.SetValue(newRotation)
528 self.parent.updateModelTransform()
532 if e.Dragging() and e.RightIsDown():
533 self.zoom += e.GetY() - self.oldY
545 def OnMouseWheel(self,e):
546 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
553 def OnEraseBackground(self,event):
554 #Workaround for windows background redraw flicker.
561 dc = wx.PaintDC(self)
562 if not hasOpenGLlibs:
564 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
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 != 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 != None:
577 glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 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 != None and self.parent.gcodeDirty:
594 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
595 if self.gcodeDisplayList != 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 != 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 == None:
615 obj.displayList = glGenLists(1)
616 obj.steepDisplayList = glGenLists(1)
619 glNewList(obj.displayList, GL_COMPILE)
620 opengl.DrawMesh(obj.mesh)
622 glNewList(obj.steepDisplayList, GL_COMPILE)
623 opengl.DrawMeshSteep(obj.mesh, 60)
626 if self.viewMode == "Mixed":
628 glColor3f(0.0,0.0,0.0)
630 glColor3f(1.0,1.0,1.0)
631 glClear(GL_DEPTH_BUFFER_BIT)
635 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
636 glEnable(GL_COLOR_MATERIAL)
637 glEnable(GL_LIGHTING)
638 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
639 starttime = time.time()
640 for i in xrange(drawUpToLayer - 1, -1, -1):
642 if i < self.parent.layerSpin.GetValue():
643 c = 0.9 - (drawUpToLayer - i) * 0.1
648 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
649 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
650 glCallList(self.gcodeDisplayList + i)
651 if time.time() - starttime > 0.1:
654 glDisable(GL_LIGHTING)
655 glDisable(GL_COLOR_MATERIAL)
656 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
657 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
659 glColor3f(1.0,1.0,1.0)
661 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
662 for obj in self.parent.objectList:
666 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
667 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
668 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
669 #If we want transparent, then first render a solid black model to remove the printer size lines.
670 if self.viewMode != "Mixed":
672 glColor3f(0.0,0.0,0.0)
674 glColor3f(1.0,1.0,1.0)
675 #After the black model is rendered, render the model again but now with lighting and no depth testing.
676 glDisable(GL_DEPTH_TEST)
677 glEnable(GL_LIGHTING)
679 glBlendFunc(GL_ONE, GL_ONE)
680 glEnable(GL_LIGHTING)
682 glEnable(GL_DEPTH_TEST)
683 elif self.viewMode == "X-Ray":
684 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
685 glDisable(GL_LIGHTING)
686 glDisable(GL_DEPTH_TEST)
687 glEnable(GL_STENCIL_TEST)
688 glStencilFunc(GL_ALWAYS, 1, 1)
689 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
691 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
693 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
694 glStencilFunc(GL_EQUAL, 0, 1)
697 glStencilFunc(GL_EQUAL, 1, 1)
703 for i in xrange(2, 15, 2):
704 glStencilFunc(GL_EQUAL, i, 0xFF);
705 glColor(float(i)/10, float(i)/10, float(i)/5)
707 glVertex3f(-1000,-1000,-1)
708 glVertex3f( 1000,-1000,-1)
709 glVertex3f( 1000, 1000,-1)
710 glVertex3f(-1000, 1000,-1)
712 for i in xrange(1, 15, 2):
713 glStencilFunc(GL_EQUAL, i, 0xFF);
714 glColor(float(i)/10, 0, 0)
716 glVertex3f(-1000,-1000,-1)
717 glVertex3f( 1000,-1000,-1)
718 glVertex3f( 1000, 1000,-1)
719 glVertex3f(-1000, 1000,-1)
723 glDisable(GL_STENCIL_TEST)
724 glEnable(GL_DEPTH_TEST)
726 #Fix the depth buffer
727 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
729 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
730 elif self.viewMode == "Normal":
731 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
732 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
733 glEnable(GL_LIGHTING)
736 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
737 glEnable(GL_DEPTH_TEST)
738 glDisable(GL_LIGHTING)
741 modelScale = profile.getProfileSettingFloat('model_scale')
742 glScalef(modelScale, modelScale, modelScale)
743 opengl.DrawMeshOutline(obj.mesh)
746 if self.drawSteepOverhang:
747 glDisable(GL_LIGHTING)
750 modelScale = profile.getProfileSettingFloat('model_scale')
751 glScalef(modelScale, modelScale, modelScale)
752 glCallList(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)
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)
769 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
771 #Draw the rotate circle
772 if self.parent.objectsMaxV != None:
773 glDisable(GL_LIGHTING)
774 glDisable(GL_CULL_FACE)
776 glRotate(self.tempRotate + profile.getProfileSettingFloat('model_rotate_base'), 0, 0, 1)
777 radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
778 glScalef(radius, radius, 1)
779 glBegin(GL_TRIANGLE_STRIP)
780 for i in xrange(0, 64+1):
781 f = i if i < 64/2 else 64 - i
782 glColor4ub(255,int(f*255/(64/2)),0,255)
783 glVertex3f(1.1 * math.cos(i/32.0*math.pi), 1.1 * math.sin(i/32.0*math.pi),0.1)
784 glColor4ub( 0,128,0,255)
785 glVertex3f(1.3 * math.cos(i/32.0*math.pi), 1.3 * math.sin(i/32.0*math.pi),0.1)
787 glBegin(GL_TRIANGLES)
788 glColor4ub(0,0,0,192)
789 glVertex3f(1, 0.1,0.15)
790 glVertex3f(1,-0.1,0.15)
791 glVertex3f(1.4,0,0.15)
793 glEnable(GL_CULL_FACE)
797 opengl.DrawMachine(machineSize)
801 def drawModel(self, obj):
802 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
803 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
804 modelScale = profile.getProfileSettingFloat('model_scale')
805 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
807 glRotate(self.tempRotate, 0, 0, 1)
808 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
809 for mx in xrange(0, multiX):
810 for my in xrange(0, multiY):
812 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
813 glScalef(modelScale, modelScale, modelScale)
814 glCallList(obj.displayList)