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
94 self.toolbar.AddSeparator()
\r
95 self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)
\r
96 self.toolbar.AddControl(self.toolbarInfo)
\r
98 self.toolbar2 = toolbarUtil.Toolbar(self)
\r
101 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.updateModelTransform)
\r
102 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.updateModelTransform)
\r
103 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.updateModelTransform)
\r
104 self.toolbar2.AddSeparator()
\r
107 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.updateModelTransform)
\r
108 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.updateModelTransform)
\r
109 self.toolbar2.AddSeparator()
\r
112 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
\r
113 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
\r
114 self.toolbar2.AddControl(self.scale)
\r
115 self.scale.Bind(wx.EVT_TEXT, self.OnScale)
\r
116 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
\r
118 self.toolbar2.AddSeparator()
\r
121 #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
\r
122 #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
\r
123 #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
\r
124 #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
\r
125 #self.toolbar2.AddSeparator()
\r
128 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
\r
129 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
130 self.rotate.SetRange(0, 360)
\r
131 self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
\r
132 self.toolbar2.AddControl(self.rotate)
\r
134 self.toolbar2.Realize()
\r
135 self.OnViewChange()
\r
137 sizer = wx.BoxSizer(wx.VERTICAL)
\r
138 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
\r
139 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
\r
140 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
\r
141 self.SetSizer(sizer)
\r
143 def OnMove(self, e = None):
\r
146 x, y = self.glCanvas.ClientToScreenXY(0, 0)
\r
147 sx, sy = self.glCanvas.GetClientSizeTuple()
\r
148 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
\r
150 def OnMulXAddClick(self, e):
\r
151 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
\r
152 self.glCanvas.Refresh()
\r
154 def OnMulXSubClick(self, e):
\r
155 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
\r
156 self.glCanvas.Refresh()
\r
158 def OnMulYAddClick(self, e):
\r
159 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
\r
160 self.glCanvas.Refresh()
\r
162 def OnMulYSubClick(self, e):
\r
163 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
\r
164 self.glCanvas.Refresh()
\r
166 def OnScaleReset(self, e):
\r
167 self.scale.SetValue('1.0')
\r
170 def OnScale(self, e):
\r
172 if self.scale.GetValue() != '':
\r
173 scale = self.scale.GetValue()
\r
174 profile.putProfileSetting('model_scale', scale)
\r
175 self.glCanvas.Refresh()
\r
177 if self.objectsMaxV != None:
\r
178 size = (self.objectsMaxV - self.objectsMinV) * float(scale)
\r
179 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
181 def OnScaleMax(self, e = None, onlyScaleDown = False):
\r
182 if self.objectsMinV == None:
\r
184 vMin = self.objectsMinV
\r
185 vMax = self.objectsMaxV
\r
187 if profile.getProfileSettingFloat('skirt_line_count') > 0:
\r
188 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
\r
189 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
\r
190 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
\r
191 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
\r
192 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
\r
193 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
\r
194 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
\r
195 if scale > 1.0 and onlyScaleDown:
\r
197 self.scale.SetValue(str(scale))
\r
198 profile.putProfileSetting('model_scale', self.scale.GetValue())
\r
199 self.glCanvas.Refresh()
\r
201 def OnRotateReset(self, e):
\r
202 self.rotate.SetValue(0)
\r
203 self.OnRotate(None)
\r
205 def OnRotate(self, e):
\r
206 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
\r
207 self.updateModelTransform()
\r
209 def On3DClick(self):
\r
210 self.glCanvas.yaw = 30
\r
211 self.glCanvas.pitch = 60
\r
212 self.glCanvas.zoom = 300
\r
213 self.glCanvas.view3D = True
\r
214 self.glCanvas.Refresh()
\r
216 def OnTopClick(self):
\r
217 self.glCanvas.view3D = False
\r
218 self.glCanvas.zoom = 100
\r
219 self.glCanvas.offsetX = 0
\r
220 self.glCanvas.offsetY = 0
\r
221 self.glCanvas.Refresh()
\r
223 def OnLayerNrChange(self, e):
\r
224 self.glCanvas.Refresh()
\r
226 def updateCenterX(self):
\r
227 self.machineCenter.x = profile.getProfileSettingFloat('machine_center_x')
\r
228 self.glCanvas.Refresh()
\r
230 def updateCenterY(self):
\r
231 self.machineCenter.y = profile.getProfileSettingFloat('machine_center_y')
\r
232 self.glCanvas.Refresh()
\r
234 def setViewMode(self, mode):
\r
235 if mode == "Normal":
\r
236 self.normalViewButton.SetValue(True)
\r
237 if mode == "GCode":
\r
238 self.gcodeViewButton.SetValue(True)
\r
239 self.glCanvas.viewMode = mode
\r
240 wx.CallAfter(self.glCanvas.Refresh)
\r
242 def loadModelFiles(self, filelist, showWarning = False):
\r
243 while len(filelist) > len(self.objectList):
\r
244 self.objectList.append(previewObject())
\r
245 for idx in xrange(len(filelist), len(self.objectList)):
\r
246 self.objectList[idx].mesh = None
\r
247 self.objectList[idx].filename = None
\r
248 for idx in xrange(0, len(filelist)):
\r
249 obj = self.objectList[idx]
\r
250 if obj.filename != filelist[idx]:
\r
251 obj.fileTime = None
\r
252 self.gcodeFileTime = None
\r
253 self.logFileTime = None
\r
254 obj.filename = filelist[idx]
\r
256 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
\r
257 #Do the STL file loading in a background thread so we don't block the UI.
\r
258 if self.loadThread != None and self.loadThread.isAlive():
\r
259 self.loadThread.join()
\r
260 self.loadThread = threading.Thread(target=self.doFileLoadThread)
\r
261 self.loadThread.daemon = True
\r
262 self.loadThread.start()
\r
265 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
266 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
\r
268 def loadReModelFiles(self, filelist):
\r
269 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
\r
270 for idx in xrange(0, len(filelist)):
\r
271 if self.objectList[idx].filename != filelist[idx]:
\r
273 self.loadModelFiles(filelist)
\r
276 def doFileLoadThread(self):
\r
277 for obj in self.objectList:
\r
278 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
\r
279 obj.ileTime = os.stat(obj.filename).st_mtime
\r
280 mesh = meshLoader.loadMesh(obj.filename)
\r
283 self.updateModelTransform()
\r
284 self.OnScaleMax(None, True)
\r
285 scale = profile.getProfileSettingFloat('model_scale')
\r
286 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
287 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
288 self.glCanvas.zoom = numpy.max(size) * 2.5
\r
289 self.errorList = []
\r
290 wx.CallAfter(self.updateToolbar)
\r
291 wx.CallAfter(self.glCanvas.Refresh)
\r
293 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
\r
294 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
\r
295 gcode = gcodeInterpreter.gcode()
\r
296 gcode.progressCallback = self.loadProgress
\r
297 gcode.load(self.gcodeFilename)
\r
298 self.gcodeDirty = False
\r
300 self.gcodeDirty = True
\r
303 for line in open(self.gcodeFilename, "rt"):
\r
304 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
306 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
\r
307 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
\r
308 errorList.append([v1, v2])
\r
309 self.errorList = errorList
\r
311 wx.CallAfter(self.updateToolbar)
\r
312 wx.CallAfter(self.glCanvas.Refresh)
\r
313 elif not os.path.isfile(self.gcodeFilename):
\r
316 def loadProgress(self, progress):
\r
319 def OnResetAll(self, e = None):
\r
320 profile.putProfileSetting('model_scale', '1.0')
\r
321 profile.putProfileSetting('model_rotate_base', '0')
\r
322 profile.putProfileSetting('flip_x', 'False')
\r
323 profile.putProfileSetting('flip_y', 'False')
\r
324 profile.putProfileSetting('flip_z', 'False')
\r
325 profile.putProfileSetting('swap_xz', 'False')
\r
326 profile.putProfileSetting('swap_yz', 'False')
\r
327 profile.setPluginConfig([])
\r
328 self.GetParent().updateProfileToControls()
\r
330 def ShowWarningPopup(self, text, callback = None):
\r
331 self.warningPopup.text.SetLabel(text)
\r
332 self.warningPopup.callback = callback
\r
333 if callback == None:
\r
334 self.warningPopup.yesButton.Show(False)
\r
335 self.warningPopup.noButton.SetLabel('ok')
\r
337 self.warningPopup.yesButton.Show(True)
\r
338 self.warningPopup.noButton.SetLabel('no')
\r
339 self.warningPopup.Fit()
\r
340 self.warningPopup.Layout()
\r
342 self.warningPopup.Show(True)
\r
343 self.warningPopup.timer.Start(5000)
\r
345 def OnWarningPopup(self, e):
\r
346 self.warningPopup.Show(False)
\r
347 self.warningPopup.timer.Stop()
\r
348 self.warningPopup.callback()
\r
350 def OnHideWarning(self, e):
\r
351 self.warningPopup.Show(False)
\r
352 self.warningPopup.timer.Stop()
\r
354 def updateToolbar(self):
\r
355 self.gcodeViewButton.Show(self.gcode != None)
\r
356 self.mixedViewButton.Show(self.gcode != None)
\r
357 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
\r
358 if self.gcode != None:
\r
359 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
\r
360 self.toolbar.Realize()
\r
363 def OnViewChange(self):
\r
364 if self.normalViewButton.GetValue():
\r
365 self.glCanvas.viewMode = "Normal"
\r
366 elif self.transparentViewButton.GetValue():
\r
367 self.glCanvas.viewMode = "Transparent"
\r
368 elif self.xrayViewButton.GetValue():
\r
369 self.glCanvas.viewMode = "X-Ray"
\r
370 elif self.gcodeViewButton.GetValue():
\r
371 self.glCanvas.viewMode = "GCode"
\r
372 elif self.mixedViewButton.GetValue():
\r
373 self.glCanvas.viewMode = "Mixed"
\r
374 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
\r
375 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
\r
376 self.updateToolbar()
\r
377 self.glCanvas.Refresh()
\r
379 def updateModelTransform(self, f=0):
\r
380 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
\r
383 rotate = profile.getProfileSettingFloat('model_rotate_base')
\r
384 mirrorX = profile.getProfileSetting('flip_x') == 'True'
\r
385 mirrorY = profile.getProfileSetting('flip_y') == 'True'
\r
386 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
\r
387 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
\r
388 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
\r
390 for obj in self.objectList:
\r
391 if obj.mesh == None:
\r
393 obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
\r
395 minV = self.objectList[0].mesh.getMinimum()
\r
396 maxV = self.objectList[0].mesh.getMaximum()
\r
397 for obj in self.objectList:
\r
398 if obj.mesh == None:
\r
401 obj.mesh.getMinimumZ()
\r
402 minV = numpy.minimum(minV, obj.mesh.getMinimum())
\r
403 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
\r
405 self.objectsMaxV = maxV
\r
406 self.objectsMinV = minV
\r
407 for obj in self.objectList:
\r
408 if obj.mesh == None:
\r
411 obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
\r
412 #for v in obj.mesh.vertexes:
\r
414 # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
\r
415 # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
\r
416 obj.mesh.getMinimumZ()
\r
419 scale = profile.getProfileSettingFloat('model_scale')
\r
420 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
421 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
423 self.glCanvas.Refresh()
\r
425 def updateProfileToControls(self):
\r
426 self.scale.SetValue(profile.getProfileSetting('model_scale'))
\r
427 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
\r
428 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
\r
429 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
\r
430 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
\r
431 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
\r
432 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
\r
433 self.updateModelTransform()
\r
434 self.glCanvas.updateProfileToControls()
\r
436 class PreviewGLCanvas(glcanvas.GLCanvas):
\r
437 def __init__(self, parent):
\r
438 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
\r
439 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
\r
440 self.parent = parent
\r
441 self.context = glcanvas.GLContext(self)
\r
442 wx.EVT_PAINT(self, self.OnPaint)
\r
443 wx.EVT_SIZE(self, self.OnSize)
\r
444 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
\r
445 wx.EVT_MOTION(self, self.OnMouseMotion)
\r
446 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
\r
453 self.gcodeDisplayList = None
\r
454 self.gcodeDisplayListMade = None
\r
455 self.gcodeDisplayListCount = 0
\r
456 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
460 self.tempRotate = 0
\r
462 def updateProfileToControls(self):
\r
463 self.objColor[0] = profile.getPreferenceColour('model_colour')
\r
464 self.objColor[1] = profile.getPreferenceColour('model_colour2')
\r
465 self.objColor[2] = profile.getPreferenceColour('model_colour3')
\r
466 self.objColor[3] = profile.getPreferenceColour('model_colour4')
\r
468 def OnMouseMotion(self,e):
\r
471 if self.parent.objectsMaxV != None:
\r
472 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
473 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
475 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
\r
476 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
\r
477 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
\r
478 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
\r
479 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
480 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
\r
482 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
\r
484 if e.Dragging() and e.LeftIsDown():
\r
485 if self.dragType == '':
\r
486 #Define the drag type depending on the cursor position.
\r
487 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
488 self.dragType = 'modelRotate'
\r
489 self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
\r
491 self.dragType = 'viewRotate'
\r
493 if self.dragType == 'viewRotate':
\r
495 self.yaw += e.GetX() - self.oldX
\r
496 self.pitch -= e.GetY() - self.oldY
\r
497 if self.pitch > 170:
\r
499 if self.pitch < 10:
\r
502 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
\r
503 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
\r
504 elif self.dragType == 'modelRotate':
\r
505 angle = math.atan2(cursorZ0[0], cursorZ0[1])
\r
506 diff = self.dragStart - angle
\r
507 self.tempRotate = diff * 180 / math.pi
\r
508 #Workaround for buggy ATI cards.
\r
509 size = self.GetSizeTuple()
\r
510 self.SetSize((size[0]+1, size[1]))
\r
511 self.SetSize((size[0], size[1]))
\r
514 if self.tempRotate != 0:
\r
515 profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)
\r
516 self.parent.updateModelTransform()
\r
517 self.tempRotate = 0
\r
520 if e.Dragging() and e.RightIsDown():
\r
521 self.zoom += e.GetY() - self.oldY
\r
524 if self.zoom > 500:
\r
527 self.oldX = e.GetX()
\r
528 self.oldY = e.GetY()
\r
533 def OnMouseWheel(self,e):
\r
534 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
\r
535 if self.zoom < 1.0:
\r
537 if self.zoom > 500:
\r
541 def OnEraseBackground(self,event):
\r
542 #Workaround for windows background redraw flicker.
\r
545 def OnSize(self,e):
\r
548 def OnPaint(self,e):
\r
549 dc = wx.PaintDC(self)
\r
550 if not hasOpenGLlibs:
\r
552 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
\r
554 self.SetCurrent(self.context)
\r
555 opengl.InitGL(self, self.view3D, self.zoom)
\r
557 glTranslate(0,0,-self.zoom)
\r
558 glRotate(-self.pitch, 1,0,0)
\r
559 glRotate(self.yaw, 0,0,1)
\r
560 if self.viewMode == "GCode" or self.viewMode == "Mixed":
\r
561 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
562 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
\r
564 if self.parent.objectsMaxV != None:
\r
565 glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
\r
567 glTranslate(self.offsetX, self.offsetY, 0)
\r
569 self.viewport = glGetIntegerv(GL_VIEWPORT);
\r
570 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
\r
571 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
\r
573 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
\r
579 machineSize = self.parent.machineSize
\r
581 if self.parent.gcode != None and self.parent.gcodeDirty:
\r
582 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
\r
583 if self.gcodeDisplayList != None:
\r
584 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
\r
585 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
\r
586 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
\r
587 self.parent.gcodeDirty = False
\r
588 self.gcodeDisplayListMade = 0
\r
590 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
\r
591 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
\r
592 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
\r
594 self.gcodeDisplayListMade += 1
\r
595 wx.CallAfter(self.Refresh)
\r
598 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
599 for obj in self.parent.objectList:
\r
600 if obj.mesh == None:
\r
602 if obj.displayList == None:
\r
603 obj.displayList = glGenLists(1)
\r
604 obj.steepDisplayList = glGenLists(1)
\r
607 glNewList(obj.displayList, GL_COMPILE)
\r
608 opengl.DrawMesh(obj.mesh)
\r
610 glNewList(obj.steepDisplayList, GL_COMPILE)
\r
611 opengl.DrawMeshSteep(obj.mesh, 60)
\r
614 if self.viewMode == "Mixed":
\r
615 glDisable(GL_BLEND)
\r
616 glColor3f(0.0,0.0,0.0)
\r
617 self.drawModel(obj)
\r
618 glColor3f(1.0,1.0,1.0)
\r
619 glClear(GL_DEPTH_BUFFER_BIT)
\r
623 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
\r
624 glEnable(GL_COLOR_MATERIAL)
\r
625 glEnable(GL_LIGHTING)
\r
626 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
\r
627 starttime = time.time()
\r
628 for i in xrange(drawUpToLayer - 1, -1, -1):
\r
630 if i < self.parent.layerSpin.GetValue():
\r
631 c = 0.9 - (drawUpToLayer - i) * 0.1
\r
636 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
\r
637 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
\r
638 glCallList(self.gcodeDisplayList + i)
\r
639 if time.time() - starttime > 0.1:
\r
642 glDisable(GL_LIGHTING)
\r
643 glDisable(GL_COLOR_MATERIAL)
\r
644 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
\r
645 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
\r
647 glColor3f(1.0,1.0,1.0)
\r
649 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
650 for obj in self.parent.objectList:
\r
651 if obj.mesh == None:
\r
654 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
\r
655 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
\r
656 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
\r
657 #If we want transparent, then first render a solid black model to remove the printer size lines.
\r
658 if self.viewMode != "Mixed":
\r
659 glDisable(GL_BLEND)
\r
660 glColor3f(0.0,0.0,0.0)
\r
661 self.drawModel(obj)
\r
662 glColor3f(1.0,1.0,1.0)
\r
663 #After the black model is rendered, render the model again but now with lighting and no depth testing.
\r
664 glDisable(GL_DEPTH_TEST)
\r
665 glEnable(GL_LIGHTING)
\r
667 glBlendFunc(GL_ONE, GL_ONE)
\r
668 glEnable(GL_LIGHTING)
\r
669 self.drawModel(obj)
\r
670 glEnable(GL_DEPTH_TEST)
\r
671 elif self.viewMode == "X-Ray":
\r
672 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
673 glDisable(GL_LIGHTING)
\r
674 glDisable(GL_DEPTH_TEST)
\r
675 glEnable(GL_STENCIL_TEST)
\r
676 glStencilFunc(GL_ALWAYS, 1, 1)
\r
677 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
\r
678 self.drawModel(obj)
\r
679 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
\r
681 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
682 glStencilFunc(GL_EQUAL, 0, 1)
\r
684 self.drawModel(obj)
\r
685 glStencilFunc(GL_EQUAL, 1, 1)
\r
687 self.drawModel(obj)
\r
691 for i in xrange(2, 15, 2):
\r
692 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
693 glColor(float(i)/10, float(i)/10, float(i)/5)
\r
695 glVertex3f(-1000,-1000,-1)
\r
696 glVertex3f( 1000,-1000,-1)
\r
697 glVertex3f( 1000, 1000,-1)
\r
698 glVertex3f(-1000, 1000,-1)
\r
700 for i in xrange(1, 15, 2):
\r
701 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
702 glColor(float(i)/10, 0, 0)
\r
704 glVertex3f(-1000,-1000,-1)
\r
705 glVertex3f( 1000,-1000,-1)
\r
706 glVertex3f( 1000, 1000,-1)
\r
707 glVertex3f(-1000, 1000,-1)
\r
711 glDisable(GL_STENCIL_TEST)
\r
712 glEnable(GL_DEPTH_TEST)
\r
714 #Fix the depth buffer
\r
715 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
716 self.drawModel(obj)
\r
717 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
718 elif self.viewMode == "Normal":
\r
719 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
\r
720 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
\r
721 glEnable(GL_LIGHTING)
\r
722 self.drawModel(obj)
\r
724 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
\r
725 glEnable(GL_DEPTH_TEST)
\r
726 glDisable(GL_LIGHTING)
\r
729 modelScale = profile.getProfileSettingFloat('model_scale')
\r
730 glScalef(modelScale, modelScale, modelScale)
\r
731 opengl.DrawMeshOutline(obj.mesh)
\r
734 if self.drawSteepOverhang:
\r
735 glDisable(GL_LIGHTING)
\r
738 modelScale = profile.getProfileSettingFloat('model_scale')
\r
739 glScalef(modelScale, modelScale, modelScale)
\r
740 glCallList(obj.steepDisplayList)
\r
744 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
\r
745 glDisable(GL_LIGHTING)
\r
746 glDisable(GL_DEPTH_TEST)
\r
747 glDisable(GL_BLEND)
\r
750 for err in self.parent.errorList:
\r
751 glVertex3f(err[0].x, err[0].y, err[0].z)
\r
752 glVertex3f(err[1].x, err[1].y, err[1].z)
\r
754 glEnable(GL_DEPTH_TEST)
\r
756 opengl.DrawMachine(machineSize)
\r
759 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
761 #Draw the rotate circle
\r
762 if self.parent.objectsMaxV != None and False:
\r
763 glDisable(GL_LIGHTING)
\r
764 glDisable(GL_CULL_FACE)
\r
766 glBegin(GL_TRIANGLE_STRIP)
\r
767 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
768 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
769 for i in xrange(0, 64+1):
\r
770 f = i if i < 64/2 else 64 - i
\r
771 glColor4ub(255,int(f*255/(64/2)),0,128)
\r
772 glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)
\r
773 glColor4ub( 0,128,0,128)
\r
774 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
776 glEnable(GL_CULL_FACE)
\r
782 def drawModel(self, obj):
\r
783 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
\r
784 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
\r
785 modelScale = profile.getProfileSettingFloat('model_scale')
\r
786 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
\r
788 glRotate(self.tempRotate, 0, 0, 1)
\r
789 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
\r
790 for mx in xrange(0, multiX):
\r
791 for my in xrange(0, multiY):
\r
793 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
\r
794 glScalef(modelScale, modelScale, modelScale)
\r
795 glCallList(obj.displayList)
\r