1 from __future__ import division
\r
3 import sys, math, threading, re, time, os
\r
6 from wx import glcanvas
\r
10 OpenGL.ERROR_CHECKING = False
\r
11 from OpenGL.GLU import *
\r
12 from OpenGL.GL import *
\r
13 hasOpenGLlibs = True
\r
15 print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
\r
16 hasOpenGLlibs = False
\r
18 from gui import opengl
\r
19 from gui import toolbarUtil
\r
21 from util import profile
\r
22 from util import gcodeInterpreter
\r
23 from util import meshLoader
\r
24 from util import util3d
\r
25 from util import sliceRun
\r
27 class previewObject():
\r
30 self.filename = None
\r
31 self.displayList = None
\r
34 class previewPanel(wx.Panel):
\r
35 def __init__(self, parent):
\r
36 super(previewPanel, self).__init__(parent,-1)
\r
38 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
\r
39 self.SetMinSize((440,320))
\r
41 self.objectList = []
\r
44 self.objectsMinV = None
\r
45 self.objectsMaxV = None
\r
46 self.loadThread = None
\r
47 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
\r
48 self.machineCenter = util3d.Vector3(float(profile.getProfileSetting('machine_center_x')), float(profile.getProfileSetting('machine_center_y')), 0)
\r
50 self.glCanvas = PreviewGLCanvas(self)
\r
51 #Create the popup window
\r
52 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
\r
53 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
\r
54 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
\r
55 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
\r
56 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
\r
57 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
\r
58 self.warningPopup.SetSizer(self.warningPopup.sizer)
\r
59 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
\r
60 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
\r
61 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
\r
62 self.warningPopup.Fit()
\r
63 self.warningPopup.Layout()
\r
64 self.warningPopup.timer = wx.Timer(self)
\r
65 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
\r
67 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
\r
68 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
\r
69 parent.Bind(wx.EVT_MOVE, self.OnMove)
\r
70 parent.Bind(wx.EVT_SIZE, self.OnMove)
\r
72 self.toolbar = toolbarUtil.Toolbar(self)
\r
75 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
\r
76 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
\r
77 self.toolbar.AddSeparator()
\r
79 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
\r
80 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show steep overhang', callback=self.OnViewChange)
\r
81 self.toolbar.AddSeparator()
\r
84 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
\r
85 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
\r
86 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
\r
87 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
\r
88 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
\r
89 self.toolbar.AddSeparator()
\r
91 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
\r
92 self.toolbar.AddControl(self.layerSpin)
\r
93 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
\r
95 self.toolbar2 = toolbarUtil.Toolbar(self)
\r
98 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.updateModelTransform)
\r
99 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.updateModelTransform)
\r
100 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.updateModelTransform)
\r
101 self.toolbar2.AddSeparator()
\r
104 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.updateModelTransform)
\r
105 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.updateModelTransform)
\r
106 self.toolbar2.AddSeparator()
\r
109 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
\r
110 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
\r
111 self.toolbar2.AddControl(self.scale)
\r
112 self.scale.Bind(wx.EVT_TEXT, self.OnScale)
\r
113 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
\r
115 self.toolbar2.AddSeparator()
\r
118 #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
\r
119 #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
\r
120 #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
\r
121 #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
\r
122 #self.toolbar2.AddSeparator()
\r
125 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
\r
126 self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)
\r
127 self.rotate.SetRange(0, 360)
\r
128 self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
\r
129 self.toolbar2.AddControl(self.rotate)
\r
131 self.toolbar2.Realize()
\r
132 self.OnViewChange()
\r
134 sizer = wx.BoxSizer(wx.VERTICAL)
\r
135 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
\r
136 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
\r
137 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
\r
138 self.SetSizer(sizer)
\r
140 def OnMove(self, e = None):
\r
143 x, y = self.glCanvas.ClientToScreenXY(0, 0)
\r
144 sx, sy = self.glCanvas.GetClientSizeTuple()
\r
145 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
\r
147 def OnMulXAddClick(self, e):
\r
148 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
\r
149 self.glCanvas.Refresh()
\r
151 def OnMulXSubClick(self, e):
\r
152 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
\r
153 self.glCanvas.Refresh()
\r
155 def OnMulYAddClick(self, e):
\r
156 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
\r
157 self.glCanvas.Refresh()
\r
159 def OnMulYSubClick(self, e):
\r
160 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
\r
161 self.glCanvas.Refresh()
\r
163 def OnScaleReset(self, e):
\r
164 self.scale.SetValue('1.0')
\r
167 def OnScale(self, e):
\r
169 if self.scale.GetValue() != '':
\r
170 scale = self.scale.GetValue()
\r
171 profile.putProfileSetting('model_scale', scale)
\r
172 self.glCanvas.Refresh()
\r
174 def OnScaleMax(self, e = None, onlyScaleDown = False):
\r
175 if self.objectsMinV == None:
\r
177 vMin = self.objectsMinV
\r
178 vMax = self.objectsMaxV
\r
180 if profile.getProfileSettingFloat('skirt_line_count') > 0:
\r
181 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
\r
182 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
\r
183 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
\r
184 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
\r
185 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
\r
186 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
\r
187 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
\r
188 if scale > 1.0 and onlyScaleDown:
\r
190 self.scale.SetValue(str(scale))
\r
191 profile.putProfileSetting('model_scale', self.scale.GetValue())
\r
192 self.glCanvas.Refresh()
\r
194 def OnRotateReset(self, e):
\r
195 self.rotate.SetValue(0)
\r
196 self.OnRotate(None)
\r
198 def OnRotate(self, e):
\r
199 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
\r
200 self.updateModelTransform()
\r
202 def On3DClick(self):
\r
203 self.glCanvas.yaw = 30
\r
204 self.glCanvas.pitch = 60
\r
205 self.glCanvas.zoom = 300
\r
206 self.glCanvas.view3D = True
\r
207 self.glCanvas.Refresh()
\r
209 def OnTopClick(self):
\r
210 self.glCanvas.view3D = False
\r
211 self.glCanvas.zoom = 100
\r
212 self.glCanvas.offsetX = 0
\r
213 self.glCanvas.offsetY = 0
\r
214 self.glCanvas.Refresh()
\r
216 def OnLayerNrChange(self, e):
\r
217 self.glCanvas.Refresh()
\r
219 def updateCenterX(self):
\r
220 self.machineCenter.x = profile.getProfileSettingFloat('machine_center_x')
\r
221 self.glCanvas.Refresh()
\r
223 def updateCenterY(self):
\r
224 self.machineCenter.y = profile.getProfileSettingFloat('machine_center_y')
\r
225 self.glCanvas.Refresh()
\r
227 def setViewMode(self, mode):
\r
228 if mode == "Normal":
\r
229 self.normalViewButton.SetValue(True)
\r
230 if mode == "GCode":
\r
231 self.gcodeViewButton.SetValue(True)
\r
232 self.glCanvas.viewMode = mode
\r
233 wx.CallAfter(self.glCanvas.Refresh)
\r
235 def loadModelFiles(self, filelist, showWarning = False):
\r
236 while len(filelist) > len(self.objectList):
\r
237 self.objectList.append(previewObject())
\r
238 for idx in xrange(len(filelist), len(self.objectList)):
\r
239 self.objectList[idx].mesh = None
\r
240 self.objectList[idx].filename = None
\r
241 for idx in xrange(0, len(filelist)):
\r
242 obj = self.objectList[idx]
\r
243 if obj.filename != filelist[idx]:
\r
244 obj.fileTime = None
\r
245 self.gcodeFileTime = None
\r
246 self.logFileTime = None
\r
247 obj.filename = filelist[idx]
\r
249 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
\r
250 #Do the STL file loading in a background thread so we don't block the UI.
\r
251 if self.loadThread != None and self.loadThread.isAlive():
\r
252 self.loadThread.join()
\r
253 self.loadThread = threading.Thread(target=self.doFileLoadThread)
\r
254 self.loadThread.daemon = True
\r
255 self.loadThread.start()
\r
258 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:
\r
259 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
\r
261 def loadReModelFiles(self, filelist):
\r
262 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
\r
263 for idx in xrange(0, len(filelist)):
\r
264 if self.objectList[idx].filename != filelist[idx]:
\r
266 self.loadModelFiles(filelist)
\r
269 def doFileLoadThread(self):
\r
270 for obj in self.objectList:
\r
271 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
\r
272 obj.ileTime = os.stat(obj.filename).st_mtime
\r
273 mesh = meshLoader.loadMesh(obj.filename)
\r
276 self.updateModelTransform()
\r
277 scale = profile.getProfileSettingFloat('model_scale')
\r
278 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
279 self.OnScaleMax(None, True)
\r
280 self.glCanvas.zoom = numpy.max(size) * 2.5
\r
281 self.errorList = []
\r
282 wx.CallAfter(self.updateToolbar)
\r
283 wx.CallAfter(self.glCanvas.Refresh)
\r
285 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
\r
286 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
\r
287 gcode = gcodeInterpreter.gcode()
\r
288 gcode.progressCallback = self.loadProgress
\r
289 gcode.load(self.gcodeFilename)
\r
290 self.gcodeDirty = False
\r
292 self.gcodeDirty = True
\r
295 for line in open(self.gcodeFilename, "rt"):
\r
296 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)
\r
298 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
\r
299 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
\r
300 errorList.append([v1, v2])
\r
301 self.errorList = errorList
\r
303 wx.CallAfter(self.updateToolbar)
\r
304 wx.CallAfter(self.glCanvas.Refresh)
\r
305 elif not os.path.isfile(self.gcodeFilename):
\r
308 def loadProgress(self, progress):
\r
311 def OnResetAll(self, e = None):
\r
312 profile.putProfileSetting('model_scale', '1.0')
\r
313 profile.putProfileSetting('model_rotate_base', '0')
\r
314 profile.putProfileSetting('flip_x', 'False')
\r
315 profile.putProfileSetting('flip_y', 'False')
\r
316 profile.putProfileSetting('flip_z', 'False')
\r
317 profile.putProfileSetting('swap_xz', 'False')
\r
318 profile.putProfileSetting('swap_yz', 'False')
\r
319 profile.setPluginConfig([])
\r
320 self.GetParent().updateProfileToControls()
\r
322 def ShowWarningPopup(self, text, callback = None):
\r
323 self.warningPopup.text.SetLabel(text)
\r
324 self.warningPopup.callback = callback
\r
325 if callback == None:
\r
326 self.warningPopup.yesButton.Show(False)
\r
327 self.warningPopup.noButton.SetLabel('ok')
\r
329 self.warningPopup.yesButton.Show(True)
\r
330 self.warningPopup.noButton.SetLabel('no')
\r
331 self.warningPopup.Fit()
\r
332 self.warningPopup.Layout()
\r
334 self.warningPopup.Show(True)
\r
335 self.warningPopup.timer.Start(5000)
\r
337 def OnWarningPopup(self, e):
\r
338 self.warningPopup.Show(False)
\r
339 self.warningPopup.timer.Stop()
\r
340 self.warningPopup.callback()
\r
342 def OnHideWarning(self, e):
\r
343 self.warningPopup.Show(False)
\r
344 self.warningPopup.timer.Stop()
\r
346 def updateToolbar(self):
\r
347 self.gcodeViewButton.Show(self.gcode != None)
\r
348 self.mixedViewButton.Show(self.gcode != None)
\r
349 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
\r
350 if self.gcode != None:
\r
351 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
\r
352 self.toolbar.Realize()
\r
355 def OnViewChange(self):
\r
356 if self.normalViewButton.GetValue():
\r
357 self.glCanvas.viewMode = "Normal"
\r
358 elif self.transparentViewButton.GetValue():
\r
359 self.glCanvas.viewMode = "Transparent"
\r
360 elif self.xrayViewButton.GetValue():
\r
361 self.glCanvas.viewMode = "X-Ray"
\r
362 elif self.gcodeViewButton.GetValue():
\r
363 self.glCanvas.viewMode = "GCode"
\r
364 elif self.mixedViewButton.GetValue():
\r
365 self.glCanvas.viewMode = "Mixed"
\r
366 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
\r
367 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
\r
368 self.updateToolbar()
\r
369 self.glCanvas.Refresh()
\r
371 def updateModelTransform(self, f=0):
\r
372 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
\r
375 rotate = profile.getProfileSettingFloat('model_rotate_base')
\r
376 mirrorX = profile.getProfileSetting('flip_x') == 'True'
\r
377 mirrorY = profile.getProfileSetting('flip_y') == 'True'
\r
378 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
\r
379 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
\r
380 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
\r
382 for obj in self.objectList:
\r
383 if obj.mesh == None:
\r
385 obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
\r
387 minV = self.objectList[0].mesh.getMinimum()
\r
388 maxV = self.objectList[0].mesh.getMaximum()
\r
389 for obj in self.objectList:
\r
390 if obj.mesh == None:
\r
393 obj.mesh.getMinimumZ()
\r
394 minV = numpy.minimum(minV, obj.mesh.getMinimum())
\r
395 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
\r
397 self.objectsMaxV = maxV
\r
398 self.objectsMinV = minV
\r
399 for obj in self.objectList:
\r
400 if obj.mesh == None:
\r
403 obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
\r
404 #for v in obj.mesh.vertexes:
\r
406 # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
\r
407 # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
\r
408 obj.mesh.getMinimumZ()
\r
410 self.glCanvas.Refresh()
\r
412 def updateProfileToControls(self):
\r
413 self.scale.SetValue(profile.getProfileSetting('model_scale'))
\r
414 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
\r
415 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
\r
416 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
\r
417 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
\r
418 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
\r
419 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
\r
420 self.updateModelTransform()
\r
421 self.glCanvas.updateProfileToControls()
\r
423 class PreviewGLCanvas(glcanvas.GLCanvas):
\r
424 def __init__(self, parent):
\r
425 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
\r
426 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
\r
427 self.parent = parent
\r
428 self.context = glcanvas.GLContext(self)
\r
429 wx.EVT_PAINT(self, self.OnPaint)
\r
430 wx.EVT_SIZE(self, self.OnSize)
\r
431 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
\r
432 wx.EVT_MOTION(self, self.OnMouseMotion)
\r
433 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
\r
440 self.gcodeDisplayList = None
\r
441 self.gcodeDisplayListMade = None
\r
442 self.gcodeDisplayListCount = 0
\r
443 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]]
\r
447 self.tempRotate = 0
\r
449 def updateProfileToControls(self):
\r
450 self.objColor[0] = profile.getPreferenceColour('model_colour')
\r
451 self.objColor[1] = profile.getPreferenceColour('model_colour2')
\r
452 self.objColor[2] = profile.getPreferenceColour('model_colour3')
\r
453 self.objColor[3] = profile.getPreferenceColour('model_colour4')
\r
455 def OnMouseMotion(self,e):
\r
458 if self.parent.objectsMaxV != None:
\r
459 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
460 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
462 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
\r
463 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
\r
464 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
\r
465 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
\r
466 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
467 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
\r
469 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
\r
471 if e.Dragging() and e.LeftIsDown():
\r
472 if self.dragType == '':
\r
473 #Define the drag type depending on the cursor position.
\r
474 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
475 self.dragType = 'modelRotate'
\r
476 self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
\r
478 self.dragType = 'viewRotate'
\r
480 if self.dragType == 'viewRotate':
\r
482 self.yaw += e.GetX() - self.oldX
\r
483 self.pitch -= e.GetY() - self.oldY
\r
484 if self.pitch > 170:
\r
486 if self.pitch < 10:
\r
489 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
\r
490 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
\r
491 elif self.dragType == 'modelRotate':
\r
492 angle = math.atan2(cursorZ0[0], cursorZ0[1])
\r
493 diff = self.dragStart - angle
\r
494 self.tempRotate = diff * 180 / math.pi
\r
495 #Workaround for buggy ATI cards.
\r
496 size = self.GetSizeTuple()
\r
497 self.SetSize((size[0]+1, size[1]))
\r
498 self.SetSize((size[0], size[1]))
\r
501 if self.tempRotate != 0:
\r
502 profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)
\r
503 self.parent.updateModelTransform()
\r
504 self.tempRotate = 0
\r
507 if e.Dragging() and e.RightIsDown():
\r
508 self.zoom += e.GetY() - self.oldY
\r
511 if self.zoom > 500:
\r
514 self.oldX = e.GetX()
\r
515 self.oldY = e.GetY()
\r
520 def OnMouseWheel(self,e):
\r
521 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
\r
522 if self.zoom < 1.0:
\r
524 if self.zoom > 500:
\r
528 def OnEraseBackground(self,event):
\r
529 #Workaround for windows background redraw flicker.
\r
532 def OnSize(self,e):
\r
535 def OnPaint(self,e):
\r
536 dc = wx.PaintDC(self)
\r
537 if not hasOpenGLlibs:
\r
539 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
\r
541 self.SetCurrent(self.context)
\r
542 opengl.InitGL(self, self.view3D, self.zoom)
\r
544 glTranslate(0,0,-self.zoom)
\r
545 glRotate(-self.pitch, 1,0,0)
\r
546 glRotate(self.yaw, 0,0,1)
\r
547 if self.viewMode == "GCode" or self.viewMode == "Mixed":
\r
548 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:
\r
549 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
\r
551 if self.parent.objectsMaxV != None:
\r
552 glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
\r
554 glTranslate(self.offsetX, self.offsetY, 0)
\r
556 self.viewport = glGetIntegerv(GL_VIEWPORT);
\r
557 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
\r
558 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
\r
560 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
\r
566 machineSize = self.parent.machineSize
\r
568 if self.parent.gcode != None and self.parent.gcodeDirty:
\r
569 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
\r
570 if self.gcodeDisplayList != None:
\r
571 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
\r
572 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
\r
573 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
\r
574 self.parent.gcodeDirty = False
\r
575 self.gcodeDisplayListMade = 0
\r
577 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
\r
578 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
\r
579 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
\r
581 self.gcodeDisplayListMade += 1
\r
582 wx.CallAfter(self.Refresh)
\r
585 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
586 for obj in self.parent.objectList:
\r
587 if obj.mesh == None:
\r
589 if obj.displayList == None:
\r
590 obj.displayList = glGenLists(1)
\r
591 obj.steepDisplayList = glGenLists(1)
\r
594 glNewList(obj.displayList, GL_COMPILE)
\r
595 opengl.DrawMesh(obj.mesh)
\r
597 glNewList(obj.steepDisplayList, GL_COMPILE)
\r
598 opengl.DrawMeshSteep(obj.mesh, 60)
\r
601 if self.viewMode == "Mixed":
\r
602 glDisable(GL_BLEND)
\r
603 glColor3f(0.0,0.0,0.0)
\r
604 self.drawModel(obj)
\r
605 glColor3f(1.0,1.0,1.0)
\r
606 glClear(GL_DEPTH_BUFFER_BIT)
\r
610 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
\r
611 glEnable(GL_COLOR_MATERIAL)
\r
612 glEnable(GL_LIGHTING)
\r
613 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
\r
614 starttime = time.time()
\r
615 for i in xrange(drawUpToLayer - 1, -1, -1):
\r
617 if i < self.parent.layerSpin.GetValue():
\r
618 c = 0.9 - (drawUpToLayer - i) * 0.1
\r
623 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
\r
624 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
\r
625 glCallList(self.gcodeDisplayList + i)
\r
626 if time.time() - starttime > 0.1:
\r
629 glDisable(GL_LIGHTING)
\r
630 glDisable(GL_COLOR_MATERIAL)
\r
631 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
\r
632 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
\r
634 glColor3f(1.0,1.0,1.0)
\r
636 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
637 for obj in self.parent.objectList:
\r
638 if obj.mesh == None:
\r
641 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
\r
642 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
\r
643 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
\r
644 #If we want transparent, then first render a solid black model to remove the printer size lines.
\r
645 if self.viewMode != "Mixed":
\r
646 glDisable(GL_BLEND)
\r
647 glColor3f(0.0,0.0,0.0)
\r
648 self.drawModel(obj)
\r
649 glColor3f(1.0,1.0,1.0)
\r
650 #After the black model is rendered, render the model again but now with lighting and no depth testing.
\r
651 glDisable(GL_DEPTH_TEST)
\r
652 glEnable(GL_LIGHTING)
\r
654 glBlendFunc(GL_ONE, GL_ONE)
\r
655 glEnable(GL_LIGHTING)
\r
656 self.drawModel(obj)
\r
657 glEnable(GL_DEPTH_TEST)
\r
658 elif self.viewMode == "X-Ray":
\r
659 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
660 glDisable(GL_LIGHTING)
\r
661 glDisable(GL_DEPTH_TEST)
\r
662 glEnable(GL_STENCIL_TEST)
\r
663 glStencilFunc(GL_ALWAYS, 1, 1)
\r
664 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
\r
665 self.drawModel(obj)
\r
666 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
\r
668 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
669 glStencilFunc(GL_EQUAL, 0, 1)
\r
671 self.drawModel(obj)
\r
672 glStencilFunc(GL_EQUAL, 1, 1)
\r
674 self.drawModel(obj)
\r
678 for i in xrange(2, 15, 2):
\r
679 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
680 glColor(float(i)/10, float(i)/10, float(i)/5)
\r
682 glVertex3f(-1000,-1000,-1)
\r
683 glVertex3f( 1000,-1000,-1)
\r
684 glVertex3f( 1000, 1000,-1)
\r
685 glVertex3f(-1000, 1000,-1)
\r
687 for i in xrange(1, 15, 2):
\r
688 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
689 glColor(float(i)/10, 0, 0)
\r
691 glVertex3f(-1000,-1000,-1)
\r
692 glVertex3f( 1000,-1000,-1)
\r
693 glVertex3f( 1000, 1000,-1)
\r
694 glVertex3f(-1000, 1000,-1)
\r
698 glDisable(GL_STENCIL_TEST)
\r
699 glEnable(GL_DEPTH_TEST)
\r
701 #Fix the depth buffer
\r
702 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
703 self.drawModel(obj)
\r
704 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
705 elif self.viewMode == "Normal":
\r
706 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
\r
707 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
\r
708 glEnable(GL_LIGHTING)
\r
709 self.drawModel(obj)
\r
711 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
\r
712 glEnable(GL_DEPTH_TEST)
\r
713 glDisable(GL_LIGHTING)
\r
716 modelScale = profile.getProfileSettingFloat('model_scale')
\r
717 glScalef(modelScale, modelScale, modelScale)
\r
718 opengl.DrawMeshOutline(obj.mesh)
\r
721 if self.drawSteepOverhang:
\r
722 glDisable(GL_LIGHTING)
\r
725 modelScale = profile.getProfileSettingFloat('model_scale')
\r
726 glScalef(modelScale, modelScale, modelScale)
\r
727 glCallList(obj.steepDisplayList)
\r
731 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
\r
732 glDisable(GL_LIGHTING)
\r
733 glDisable(GL_DEPTH_TEST)
\r
734 glDisable(GL_BLEND)
\r
737 for err in self.parent.errorList:
\r
738 glVertex3f(err[0].x, err[0].y, err[0].z)
\r
739 glVertex3f(err[1].x, err[1].y, err[1].z)
\r
741 glEnable(GL_DEPTH_TEST)
\r
743 opengl.DrawMachine(machineSize)
\r
746 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
748 #Draw the rotate circle
\r
749 if self.parent.objectsMaxV != None and False:
\r
750 glDisable(GL_LIGHTING)
\r
751 glDisable(GL_CULL_FACE)
\r
753 glBegin(GL_TRIANGLE_STRIP)
\r
754 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
755 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
756 for i in xrange(0, 64+1):
\r
757 f = i if i < 64/2 else 64 - i
\r
758 glColor4ub(255,int(f*255/(64/2)),0,128)
\r
759 glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)
\r
760 glColor4ub( 0,128,0,128)
\r
761 glVertex3f((sizeXY * 0.7 + 3) * math.cos(i/32.0*math.pi), (sizeXY * 0.7 + 3) * math.sin(i/32.0*math.pi),0.1)
\r
763 glEnable(GL_CULL_FACE)
\r
769 def drawModel(self, obj):
\r
770 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
\r
771 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
\r
772 modelScale = profile.getProfileSettingFloat('model_scale')
\r
773 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
\r
775 glRotate(self.tempRotate, 0, 0, 1)
\r
776 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
\r
777 for mx in xrange(0, multiX):
\r
778 for my in xrange(0, multiY):
\r
780 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
\r
781 glScalef(modelScale, modelScale, modelScale)
\r
782 glCallList(obj.displayList)
\r