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.toolbar.AddSeparator()
\r
83 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
\r
84 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
\r
85 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
\r
86 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
\r
87 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
\r
88 self.toolbar.AddSeparator()
\r
90 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
\r
91 self.toolbar.AddControl(self.layerSpin)
\r
92 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
\r
94 self.toolbar2 = toolbarUtil.Toolbar(self)
\r
97 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.updateModelTransform)
\r
98 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.updateModelTransform)
\r
99 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.updateModelTransform)
\r
100 self.toolbar2.AddSeparator()
\r
103 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.updateModelTransform)
\r
104 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.updateModelTransform)
\r
105 self.toolbar2.AddSeparator()
\r
108 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
\r
109 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
\r
110 self.toolbar2.AddControl(self.scale)
\r
111 self.scale.Bind(wx.EVT_TEXT, self.OnScale)
\r
112 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
\r
114 self.toolbar2.AddSeparator()
\r
117 #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
\r
118 #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
\r
119 #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
\r
120 #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
\r
121 #self.toolbar2.AddSeparator()
\r
124 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
\r
125 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
126 self.rotate.SetRange(0, 360)
\r
127 self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
\r
128 self.toolbar2.AddControl(self.rotate)
\r
130 self.toolbar2.Realize()
\r
131 self.OnViewChange()
\r
133 sizer = wx.BoxSizer(wx.VERTICAL)
\r
134 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
\r
135 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
\r
136 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
\r
137 self.SetSizer(sizer)
\r
139 def OnMove(self, e = None):
\r
142 x, y = self.glCanvas.ClientToScreenXY(0, 0)
\r
143 sx, sy = self.glCanvas.GetClientSizeTuple()
\r
144 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
\r
146 def OnMulXAddClick(self, e):
\r
147 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
\r
148 self.glCanvas.Refresh()
\r
150 def OnMulXSubClick(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 OnMulYAddClick(self, e):
\r
155 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
\r
156 self.glCanvas.Refresh()
\r
158 def OnMulYSubClick(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 OnScaleReset(self, e):
\r
163 self.scale.SetValue('1.0')
\r
166 def OnScale(self, e):
\r
168 if self.scale.GetValue() != '':
\r
169 scale = self.scale.GetValue()
\r
170 profile.putProfileSetting('model_scale', scale)
\r
171 self.glCanvas.Refresh()
\r
173 def OnScaleMax(self, e):
\r
174 if self.objectsMinV == None:
\r
176 vMin = self.objectsMinV
\r
177 vMax = self.objectsMaxV
\r
178 scaleX1 = (self.machineSize.x - self.machineCenter.x) / ((vMax[0] - vMin[0]) / 2)
\r
179 scaleY1 = (self.machineSize.y - self.machineCenter.y) / ((vMax[1] - vMin[1]) / 2)
\r
180 scaleX2 = (self.machineCenter.x) / ((vMax[0] - vMin[0]) / 2)
\r
181 scaleY2 = (self.machineCenter.y) / ((vMax[1] - vMin[1]) / 2)
\r
182 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
\r
183 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
\r
184 self.scale.SetValue(str(scale))
\r
185 profile.putProfileSetting('model_scale', self.scale.GetValue())
\r
186 self.glCanvas.Refresh()
\r
188 def OnRotateReset(self, e):
\r
189 self.rotate.SetValue(0)
\r
190 self.OnRotate(None)
\r
192 def OnRotate(self, e):
\r
193 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
\r
194 self.updateModelTransform()
\r
196 def On3DClick(self):
\r
197 self.glCanvas.yaw = 30
\r
198 self.glCanvas.pitch = 60
\r
199 self.glCanvas.zoom = 300
\r
200 self.glCanvas.view3D = True
\r
201 self.glCanvas.Refresh()
\r
203 def OnTopClick(self):
\r
204 self.glCanvas.view3D = False
\r
205 self.glCanvas.zoom = 100
\r
206 self.glCanvas.offsetX = 0
\r
207 self.glCanvas.offsetY = 0
\r
208 self.glCanvas.Refresh()
\r
210 def OnLayerNrChange(self, e):
\r
211 self.glCanvas.Refresh()
\r
213 def updateCenterX(self):
\r
214 self.machineCenter.x = profile.getProfileSettingFloat('machine_center_x')
\r
215 self.glCanvas.Refresh()
\r
217 def updateCenterY(self):
\r
218 self.machineCenter.y = profile.getProfileSettingFloat('machine_center_y')
\r
219 self.glCanvas.Refresh()
\r
221 def setViewMode(self, mode):
\r
222 if mode == "Normal":
\r
223 self.normalViewButton.SetValue(True)
\r
224 if mode == "GCode":
\r
225 self.gcodeViewButton.SetValue(True)
\r
226 self.glCanvas.viewMode = mode
\r
227 wx.CallAfter(self.glCanvas.Refresh)
\r
229 def loadModelFiles(self, filelist, showWarning = False):
\r
230 while len(filelist) > len(self.objectList):
\r
231 self.objectList.append(previewObject())
\r
232 for idx in xrange(len(filelist), len(self.objectList)):
\r
233 self.objectList[idx].mesh = None
\r
234 self.objectList[idx].filename = None
\r
235 for idx in xrange(0, len(filelist)):
\r
236 obj = self.objectList[idx]
\r
237 if obj.filename != filelist[idx]:
\r
238 obj.fileTime = None
\r
239 self.gcodeFileTime = None
\r
240 self.logFileTime = None
\r
241 obj.filename = filelist[idx]
\r
243 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
\r
244 #Do the STL file loading in a background thread so we don't block the UI.
\r
245 if self.loadThread != None and self.loadThread.isAlive():
\r
246 self.loadThread.join()
\r
247 self.loadThread = threading.Thread(target=self.doFileLoadThread)
\r
248 self.loadThread.daemon = True
\r
249 self.loadThread.start()
\r
252 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':
\r
253 self.ShowWarningPopup('Reset scale, rotation and mirror?', self.OnResetAll)
\r
255 def loadReModelFiles(self, filelist):
\r
256 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
\r
257 for idx in xrange(0, len(filelist)):
\r
258 if self.objectList[idx].filename != filelist[idx]:
\r
260 self.loadModelFiles(filelist)
\r
263 def doFileLoadThread(self):
\r
264 for obj in self.objectList:
\r
265 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
\r
266 obj.ileTime = os.stat(obj.filename).st_mtime
\r
267 mesh = meshLoader.loadMesh(obj.filename)
\r
270 self.updateModelTransform()
\r
271 scale = profile.getProfileSettingFloat('model_scale')
\r
272 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
273 if size[0] > self.machineSize.x or size[1] > self.machineSize.y or size[2] > self.machineSize.z:
\r
274 self.OnScaleMax(None)
\r
275 self.glCanvas.zoom = numpy.max(size) * 2.5
\r
276 self.errorList = []
\r
277 wx.CallAfter(self.updateToolbar)
\r
278 wx.CallAfter(self.glCanvas.Refresh)
\r
280 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
\r
281 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
\r
282 gcode = gcodeInterpreter.gcode()
\r
283 gcode.progressCallback = self.loadProgress
\r
284 gcode.load(self.gcodeFilename)
\r
285 self.gcodeDirty = False
\r
287 self.gcodeDirty = True
\r
290 for line in open(self.gcodeFilename, "rt"):
\r
291 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
293 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
\r
294 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
\r
295 errorList.append([v1, v2])
\r
296 self.errorList = errorList
\r
298 wx.CallAfter(self.updateToolbar)
\r
299 wx.CallAfter(self.glCanvas.Refresh)
\r
300 elif not os.path.isfile(self.gcodeFilename):
\r
303 def loadProgress(self, progress):
\r
306 def OnResetAll(self, e = None):
\r
307 profile.putProfileSetting('model_scale', '1.0')
\r
308 profile.putProfileSetting('model_rotate_base', '0')
\r
309 profile.putProfileSetting('flip_x', 'False')
\r
310 profile.putProfileSetting('flip_y', 'False')
\r
311 profile.putProfileSetting('flip_z', 'False')
\r
312 profile.putProfileSetting('swap_xz', 'False')
\r
313 profile.putProfileSetting('swap_yz', 'False')
\r
314 self.updateProfileToControls()
\r
316 def ShowWarningPopup(self, text, callback = None):
\r
317 self.warningPopup.text.SetLabel(text)
\r
318 self.warningPopup.callback = callback
\r
319 if callback == None:
\r
320 self.warningPopup.yesButton.Show(False)
\r
321 self.warningPopup.noButton.SetLabel('ok')
\r
323 self.warningPopup.yesButton.Show(True)
\r
324 self.warningPopup.noButton.SetLabel('no')
\r
325 self.warningPopup.Fit()
\r
326 self.warningPopup.Layout()
\r
328 self.warningPopup.Show(True)
\r
329 self.warningPopup.timer.Start(5000)
\r
331 def OnWarningPopup(self, e):
\r
332 self.warningPopup.Show(False)
\r
333 self.warningPopup.timer.Stop()
\r
334 self.warningPopup.callback()
\r
336 def OnHideWarning(self, e):
\r
337 self.warningPopup.Show(False)
\r
338 self.warningPopup.timer.Stop()
\r
340 def updateToolbar(self):
\r
341 self.gcodeViewButton.Show(self.gcode != None)
\r
342 self.mixedViewButton.Show(self.gcode != None)
\r
343 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
\r
344 if self.gcode != None:
\r
345 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
\r
346 self.toolbar.Realize()
\r
349 def OnViewChange(self):
\r
350 if self.normalViewButton.GetValue():
\r
351 self.glCanvas.viewMode = "Normal"
\r
352 elif self.transparentViewButton.GetValue():
\r
353 self.glCanvas.viewMode = "Transparent"
\r
354 elif self.xrayViewButton.GetValue():
\r
355 self.glCanvas.viewMode = "X-Ray"
\r
356 elif self.gcodeViewButton.GetValue():
\r
357 self.glCanvas.viewMode = "GCode"
\r
358 elif self.mixedViewButton.GetValue():
\r
359 self.glCanvas.viewMode = "Mixed"
\r
360 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
\r
361 self.updateToolbar()
\r
362 self.glCanvas.Refresh()
\r
364 def updateModelTransform(self, f=0):
\r
365 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
\r
368 rotate = profile.getProfileSettingFloat('model_rotate_base')
\r
369 mirrorX = profile.getProfileSetting('flip_x') == 'True'
\r
370 mirrorY = profile.getProfileSetting('flip_y') == 'True'
\r
371 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
\r
372 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
\r
373 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
\r
375 for obj in self.objectList:
\r
376 if obj.mesh == None:
\r
378 obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
\r
380 minV = self.objectList[0].mesh.getMinimum()
\r
381 maxV = self.objectList[0].mesh.getMaximum()
\r
382 for obj in self.objectList:
\r
383 if obj.mesh == None:
\r
386 obj.mesh.getMinimumZ()
\r
387 minV = numpy.minimum(minV, obj.mesh.getMinimum())
\r
388 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
\r
390 self.objectsMaxV = maxV
\r
391 self.objectsMinV = minV
\r
392 for obj in self.objectList:
\r
393 if obj.mesh == None:
\r
396 obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
\r
397 #for v in obj.mesh.vertexes:
\r
399 # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
\r
400 # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
\r
401 obj.mesh.getMinimumZ()
\r
403 self.glCanvas.Refresh()
\r
405 def updateProfileToControls(self):
\r
406 self.scale.SetValue(profile.getProfileSetting('model_scale'))
\r
407 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
\r
408 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
\r
409 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
\r
410 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
\r
411 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
\r
412 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
\r
413 self.updateModelTransform()
\r
414 self.glCanvas.updateProfileToControls()
\r
416 class PreviewGLCanvas(glcanvas.GLCanvas):
\r
417 def __init__(self, parent):
\r
418 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
\r
419 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
\r
420 self.parent = parent
\r
421 self.context = glcanvas.GLContext(self)
\r
422 wx.EVT_PAINT(self, self.OnPaint)
\r
423 wx.EVT_SIZE(self, self.OnSize)
\r
424 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
\r
425 wx.EVT_MOTION(self, self.OnMouseMotion)
\r
426 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
\r
433 self.gcodeDisplayList = None
\r
434 self.gcodeDisplayListMade = None
\r
435 self.gcodeDisplayListCount = 0
\r
436 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
440 self.tempRotate = 0
\r
442 def updateProfileToControls(self):
\r
443 self.objColor[0] = profile.getPreferenceColour('model_colour')
\r
444 self.objColor[1] = profile.getPreferenceColour('model_colour2')
\r
445 self.objColor[2] = profile.getPreferenceColour('model_colour3')
\r
446 self.objColor[3] = profile.getPreferenceColour('model_colour3')
\r
448 def OnMouseMotion(self,e):
\r
451 if self.parent.objectsMaxV != None:
\r
452 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
453 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
455 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
\r
456 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
\r
457 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
\r
458 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
\r
459 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
460 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
\r
462 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
\r
464 if e.Dragging() and e.LeftIsDown():
\r
465 if self.dragType == '':
\r
466 #Define the drag type depending on the cursor position.
\r
467 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
468 self.dragType = 'modelRotate'
\r
469 self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
\r
471 self.dragType = 'viewRotate'
\r
473 if self.dragType == 'viewRotate':
\r
475 self.yaw += e.GetX() - self.oldX
\r
476 self.pitch -= e.GetY() - self.oldY
\r
477 if self.pitch > 170:
\r
479 if self.pitch < 10:
\r
482 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
\r
483 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
\r
484 elif self.dragType == 'modelRotate':
\r
485 angle = math.atan2(cursorZ0[0], cursorZ0[1])
\r
486 diff = self.dragStart - angle
\r
487 self.tempRotate = diff * 180 / math.pi
\r
488 #Workaround for buggy ATI cards.
\r
489 size = self.GetSizeTuple()
\r
490 self.SetSize((size[0]+1, size[1]))
\r
491 self.SetSize((size[0], size[1]))
\r
494 if self.tempRotate != 0:
\r
495 profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)
\r
496 self.parent.updateModelTransform()
\r
497 self.tempRotate = 0
\r
500 if e.Dragging() and e.RightIsDown():
\r
501 self.zoom += e.GetY() - self.oldY
\r
504 if self.zoom > 500:
\r
507 self.oldX = e.GetX()
\r
508 self.oldY = e.GetY()
\r
513 def OnMouseWheel(self,e):
\r
514 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
\r
515 if self.zoom < 1.0:
\r
517 if self.zoom > 500:
\r
521 def OnEraseBackground(self,event):
\r
522 #Workaround for windows background redraw flicker.
\r
525 def OnSize(self,e):
\r
528 def OnPaint(self,e):
\r
529 dc = wx.PaintDC(self)
\r
530 if not hasOpenGLlibs:
\r
532 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
\r
534 self.SetCurrent(self.context)
\r
535 opengl.InitGL(self, self.view3D, self.zoom)
\r
537 glTranslate(0,0,-self.zoom)
\r
538 glRotate(-self.pitch, 1,0,0)
\r
539 glRotate(self.yaw, 0,0,1)
\r
540 if self.viewMode == "GCode" or self.viewMode == "Mixed":
\r
541 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
542 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
\r
544 if self.parent.objectsMaxV != None:
\r
545 glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
\r
547 glScale(1.0/self.zoom, 1.0/self.zoom, 1.0)
\r
548 glTranslate(self.offsetX, self.offsetY, 0.0)
\r
550 self.viewport = glGetIntegerv(GL_VIEWPORT);
\r
551 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
\r
552 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
\r
554 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
\r
560 machineSize = self.parent.machineSize
\r
562 if self.parent.gcode != None and self.parent.gcodeDirty:
\r
563 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
\r
564 if self.gcodeDisplayList != None:
\r
565 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
\r
566 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
\r
567 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
\r
568 self.parent.gcodeDirty = False
\r
569 self.gcodeDisplayListMade = 0
\r
571 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
\r
572 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
\r
573 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
\r
575 self.gcodeDisplayListMade += 1
\r
576 wx.CallAfter(self.Refresh)
\r
579 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
580 for obj in self.parent.objectList:
\r
581 if obj.mesh == None:
\r
583 if obj.displayList == None:
\r
584 obj.displayList = glGenLists(1);
\r
587 glNewList(obj.displayList, GL_COMPILE)
\r
588 opengl.DrawMesh(obj.mesh)
\r
591 if self.viewMode == "Mixed":
\r
592 glDisable(GL_BLEND)
\r
593 glColor3f(0.0,0.0,0.0)
\r
594 self.drawModel(obj)
\r
595 glColor3f(1.0,1.0,1.0)
\r
596 glClear(GL_DEPTH_BUFFER_BIT)
\r
600 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
\r
601 glEnable(GL_COLOR_MATERIAL)
\r
602 glEnable(GL_LIGHTING)
\r
603 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
\r
604 starttime = time.time()
\r
605 for i in xrange(drawUpToLayer - 1, -1, -1):
\r
607 if i < self.parent.layerSpin.GetValue():
\r
608 c = 0.9 - (drawUpToLayer - i) * 0.1
\r
613 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
\r
614 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
\r
615 glCallList(self.gcodeDisplayList + i)
\r
616 if time.time() - starttime > 0.1:
\r
619 glDisable(GL_LIGHTING)
\r
620 glDisable(GL_COLOR_MATERIAL)
\r
621 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
\r
622 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
\r
624 glColor3f(1.0,1.0,1.0)
\r
626 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
627 for obj in self.parent.objectList:
\r
628 if obj.mesh == None:
\r
631 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
\r
632 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
\r
633 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
\r
634 #If we want transparent, then first render a solid black model to remove the printer size lines.
\r
635 if self.viewMode != "Mixed":
\r
636 glDisable(GL_BLEND)
\r
637 glColor3f(0.0,0.0,0.0)
\r
638 self.drawModel(obj)
\r
639 glColor3f(1.0,1.0,1.0)
\r
640 #After the black model is rendered, render the model again but now with lighting and no depth testing.
\r
641 glDisable(GL_DEPTH_TEST)
\r
642 glEnable(GL_LIGHTING)
\r
644 glBlendFunc(GL_ONE, GL_ONE)
\r
645 glEnable(GL_LIGHTING)
\r
646 self.drawModel(obj)
\r
647 glEnable(GL_DEPTH_TEST)
\r
648 elif self.viewMode == "X-Ray":
\r
649 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
650 glDisable(GL_LIGHTING)
\r
651 glDisable(GL_DEPTH_TEST)
\r
652 glEnable(GL_STENCIL_TEST)
\r
653 glStencilFunc(GL_ALWAYS, 1, 1)
\r
654 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
\r
655 self.drawModel(obj)
\r
656 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
\r
658 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
659 glStencilFunc(GL_EQUAL, 0, 1)
\r
661 self.drawModel(obj)
\r
662 glStencilFunc(GL_EQUAL, 1, 1)
\r
664 self.drawModel(obj)
\r
668 for i in xrange(2, 15, 2):
\r
669 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
670 glColor(float(i)/10, float(i)/10, float(i)/5)
\r
672 glVertex3f(-1000,-1000,-1)
\r
673 glVertex3f( 1000,-1000,-1)
\r
674 glVertex3f( 1000, 1000,-1)
\r
675 glVertex3f(-1000, 1000,-1)
\r
677 for i in xrange(1, 15, 2):
\r
678 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
679 glColor(float(i)/10, 0, 0)
\r
681 glVertex3f(-1000,-1000,-1)
\r
682 glVertex3f( 1000,-1000,-1)
\r
683 glVertex3f( 1000, 1000,-1)
\r
684 glVertex3f(-1000, 1000,-1)
\r
688 glDisable(GL_STENCIL_TEST)
\r
689 glEnable(GL_DEPTH_TEST)
\r
691 #Fix the depth buffer
\r
692 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
693 self.drawModel(obj)
\r
694 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
695 elif self.viewMode == "Normal":
\r
696 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
\r
697 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
\r
698 glEnable(GL_LIGHTING)
\r
699 self.drawModel(obj)
\r
701 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
\r
702 glEnable(GL_DEPTH_TEST)
\r
703 glDisable(GL_LIGHTING)
\r
706 modelScale = profile.getProfileSettingFloat('model_scale')
\r
707 glScalef(modelScale, modelScale, modelScale)
\r
708 opengl.DrawMeshOutline(obj.mesh)
\r
712 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
\r
713 glDisable(GL_LIGHTING)
\r
714 glDisable(GL_DEPTH_TEST)
\r
715 glDisable(GL_BLEND)
\r
718 for err in self.parent.errorList:
\r
719 glVertex3f(err[0].x, err[0].y, err[0].z)
\r
720 glVertex3f(err[1].x, err[1].y, err[1].z)
\r
722 glEnable(GL_DEPTH_TEST)
\r
724 opengl.DrawMachine(machineSize)
\r
727 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
729 #Draw the rotate circle
\r
730 if self.parent.objectsMaxV != None and False:
\r
731 glDisable(GL_LIGHTING)
\r
732 glDisable(GL_CULL_FACE)
\r
734 glBegin(GL_TRIANGLE_STRIP)
\r
735 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
736 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
737 for i in xrange(0, 64+1):
\r
738 f = i if i < 64/2 else 64 - i
\r
739 glColor4ub(255,int(f*255/(64/2)),0,128)
\r
740 glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)
\r
741 glColor4ub( 0,128,0,128)
\r
742 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
744 glEnable(GL_CULL_FACE)
\r
750 def drawModel(self, obj):
\r
751 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
\r
752 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
\r
753 modelScale = profile.getProfileSettingFloat('model_scale')
\r
754 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
\r
756 glRotate(self.tempRotate, 0, 0, 1)
\r
757 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
\r
758 for mx in xrange(0, multiX):
\r
759 for my in xrange(0, multiY):
\r
761 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
\r
762 glScalef(modelScale, modelScale, modelScale)
\r
763 glCallList(obj.displayList)
\r