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, '', 'steepOverhang-on.png', 'steepOverhang-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.returnToModelViewAndUpdateModel)
\r
102 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)
\r
103 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)
\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.returnToModelViewAndUpdateModel)
\r
108 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)
\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 returnToModelViewAndUpdateModel(self):
\r
144 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
\r
145 self.setViewMode('Normal')
\r
146 self.updateModelTransform()
\r
148 def OnMove(self, e = None):
\r
151 x, y = self.glCanvas.ClientToScreenXY(0, 0)
\r
152 sx, sy = self.glCanvas.GetClientSizeTuple()
\r
153 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
\r
155 def OnMulXAddClick(self, e):
\r
156 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
\r
157 self.glCanvas.Refresh()
\r
159 def OnMulXSubClick(self, e):
\r
160 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
\r
161 self.glCanvas.Refresh()
\r
163 def OnMulYAddClick(self, e):
\r
164 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
\r
165 self.glCanvas.Refresh()
\r
167 def OnMulYSubClick(self, e):
\r
168 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
\r
169 self.glCanvas.Refresh()
\r
171 def OnScaleReset(self, e):
\r
172 self.scale.SetValue('1.0')
\r
175 def OnScale(self, e):
\r
177 if self.scale.GetValue() != '':
\r
178 scale = self.scale.GetValue()
\r
179 profile.putProfileSetting('model_scale', scale)
\r
180 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
\r
181 self.setViewMode('Normal')
\r
182 self.glCanvas.Refresh()
\r
184 if self.objectsMaxV != None:
\r
185 size = (self.objectsMaxV - self.objectsMinV) * float(scale)
\r
186 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
188 def OnScaleMax(self, e = None, onlyScaleDown = False):
\r
189 if self.objectsMinV == None:
\r
191 vMin = self.objectsMinV
\r
192 vMax = self.objectsMaxV
\r
194 if profile.getProfileSettingFloat('skirt_line_count') > 0:
\r
195 skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
\r
196 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
\r
197 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
\r
198 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
\r
199 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
\r
200 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
\r
201 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
\r
202 if scale > 1.0 and onlyScaleDown:
\r
204 self.scale.SetValue(str(scale))
\r
205 profile.putProfileSetting('model_scale', self.scale.GetValue())
\r
206 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
\r
207 self.setViewMode('Normal')
\r
208 self.glCanvas.Refresh()
\r
210 def OnRotateReset(self, e):
\r
211 self.rotate.SetValue(0)
\r
212 self.OnRotate(None)
\r
214 def OnRotate(self, e):
\r
215 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
\r
216 self.returnToModelViewAndUpdateModel()
\r
218 def On3DClick(self):
\r
219 self.glCanvas.yaw = 30
\r
220 self.glCanvas.pitch = 60
\r
221 self.glCanvas.zoom = 300
\r
222 self.glCanvas.view3D = True
\r
223 self.glCanvas.Refresh()
\r
225 def OnTopClick(self):
\r
226 self.glCanvas.view3D = False
\r
227 self.glCanvas.zoom = 100
\r
228 self.glCanvas.offsetX = 0
\r
229 self.glCanvas.offsetY = 0
\r
230 self.glCanvas.Refresh()
\r
232 def OnLayerNrChange(self, e):
\r
233 self.glCanvas.Refresh()
\r
235 def updateCenterX(self):
\r
236 self.machineCenter.x = profile.getProfileSettingFloat('machine_center_x')
\r
237 self.glCanvas.Refresh()
\r
239 def updateCenterY(self):
\r
240 self.machineCenter.y = profile.getProfileSettingFloat('machine_center_y')
\r
241 self.glCanvas.Refresh()
\r
243 def setViewMode(self, mode):
\r
244 if mode == "Normal":
\r
245 self.normalViewButton.SetValue(True)
\r
246 if mode == "GCode":
\r
247 self.gcodeViewButton.SetValue(True)
\r
248 self.glCanvas.viewMode = mode
\r
249 wx.CallAfter(self.glCanvas.Refresh)
\r
251 def loadModelFiles(self, filelist, showWarning = False):
\r
252 while len(filelist) > len(self.objectList):
\r
253 self.objectList.append(previewObject())
\r
254 for idx in xrange(len(filelist), len(self.objectList)):
\r
255 self.objectList[idx].mesh = None
\r
256 self.objectList[idx].filename = None
\r
257 for idx in xrange(0, len(filelist)):
\r
258 obj = self.objectList[idx]
\r
259 if obj.filename != filelist[idx]:
\r
260 obj.fileTime = None
\r
261 self.gcodeFileTime = None
\r
262 self.logFileTime = None
\r
263 obj.filename = filelist[idx]
\r
265 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
\r
266 #Do the STL file loading in a background thread so we don't block the UI.
\r
267 if self.loadThread != None and self.loadThread.isAlive():
\r
268 self.loadThread.join()
\r
269 self.loadThread = threading.Thread(target=self.doFileLoadThread)
\r
270 self.loadThread.daemon = True
\r
271 self.loadThread.start()
\r
274 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
275 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
\r
277 def loadReModelFiles(self, filelist):
\r
278 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
\r
279 for idx in xrange(0, len(filelist)):
\r
280 if self.objectList[idx].filename != filelist[idx]:
\r
282 self.loadModelFiles(filelist)
\r
285 def doFileLoadThread(self):
\r
286 for obj in self.objectList:
\r
287 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
\r
288 obj.ileTime = os.stat(obj.filename).st_mtime
\r
289 mesh = meshLoader.loadMesh(obj.filename)
\r
292 self.updateModelTransform()
\r
293 self.OnScaleMax(None, True)
\r
294 scale = profile.getProfileSettingFloat('model_scale')
\r
295 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
296 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
297 self.glCanvas.zoom = numpy.max(size) * 2.5
\r
298 self.errorList = []
\r
299 wx.CallAfter(self.updateToolbar)
\r
300 wx.CallAfter(self.glCanvas.Refresh)
\r
302 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
\r
303 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
\r
304 gcode = gcodeInterpreter.gcode()
\r
305 gcode.progressCallback = self.loadProgress
\r
306 gcode.load(self.gcodeFilename)
\r
307 self.gcodeDirty = False
\r
309 self.gcodeDirty = True
\r
312 for line in open(self.gcodeFilename, "rt"):
\r
313 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
315 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
\r
316 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
\r
317 errorList.append([v1, v2])
\r
318 self.errorList = errorList
\r
320 wx.CallAfter(self.updateToolbar)
\r
321 wx.CallAfter(self.glCanvas.Refresh)
\r
322 elif not os.path.isfile(self.gcodeFilename):
\r
325 def loadProgress(self, progress):
\r
328 def OnResetAll(self, e = None):
\r
329 profile.putProfileSetting('model_scale', '1.0')
\r
330 profile.putProfileSetting('model_rotate_base', '0')
\r
331 profile.putProfileSetting('flip_x', 'False')
\r
332 profile.putProfileSetting('flip_y', 'False')
\r
333 profile.putProfileSetting('flip_z', 'False')
\r
334 profile.putProfileSetting('swap_xz', 'False')
\r
335 profile.putProfileSetting('swap_yz', 'False')
\r
336 profile.setPluginConfig([])
\r
337 self.GetParent().updateProfileToControls()
\r
339 def ShowWarningPopup(self, text, callback = None):
\r
340 self.warningPopup.text.SetLabel(text)
\r
341 self.warningPopup.callback = callback
\r
342 if callback == None:
\r
343 self.warningPopup.yesButton.Show(False)
\r
344 self.warningPopup.noButton.SetLabel('ok')
\r
346 self.warningPopup.yesButton.Show(True)
\r
347 self.warningPopup.noButton.SetLabel('no')
\r
348 self.warningPopup.Fit()
\r
349 self.warningPopup.Layout()
\r
351 self.warningPopup.Show(True)
\r
352 self.warningPopup.timer.Start(5000)
\r
354 def OnWarningPopup(self, e):
\r
355 self.warningPopup.Show(False)
\r
356 self.warningPopup.timer.Stop()
\r
357 self.warningPopup.callback()
\r
359 def OnHideWarning(self, e):
\r
360 self.warningPopup.Show(False)
\r
361 self.warningPopup.timer.Stop()
\r
363 def updateToolbar(self):
\r
364 self.gcodeViewButton.Show(self.gcode != None)
\r
365 self.mixedViewButton.Show(self.gcode != None)
\r
366 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
\r
367 if self.gcode != None:
\r
368 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
\r
369 self.toolbar.Realize()
\r
372 def OnViewChange(self):
\r
373 if self.normalViewButton.GetValue():
\r
374 self.glCanvas.viewMode = "Normal"
\r
375 elif self.transparentViewButton.GetValue():
\r
376 self.glCanvas.viewMode = "Transparent"
\r
377 elif self.xrayViewButton.GetValue():
\r
378 self.glCanvas.viewMode = "X-Ray"
\r
379 elif self.gcodeViewButton.GetValue():
\r
380 self.glCanvas.viewMode = "GCode"
\r
381 elif self.mixedViewButton.GetValue():
\r
382 self.glCanvas.viewMode = "Mixed"
\r
383 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
\r
384 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
\r
385 self.updateToolbar()
\r
386 self.glCanvas.Refresh()
\r
388 def updateModelTransform(self, f=0):
\r
389 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
\r
392 rotate = profile.getProfileSettingFloat('model_rotate_base')
\r
393 mirrorX = profile.getProfileSetting('flip_x') == 'True'
\r
394 mirrorY = profile.getProfileSetting('flip_y') == 'True'
\r
395 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
\r
396 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
\r
397 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
\r
399 for obj in self.objectList:
\r
400 if obj.mesh == None:
\r
402 obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
\r
404 minV = self.objectList[0].mesh.getMinimum()
\r
405 maxV = self.objectList[0].mesh.getMaximum()
\r
406 for obj in self.objectList:
\r
407 if obj.mesh == None:
\r
410 obj.mesh.getMinimumZ()
\r
411 minV = numpy.minimum(minV, obj.mesh.getMinimum())
\r
412 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
\r
414 self.objectsMaxV = maxV
\r
415 self.objectsMinV = minV
\r
416 for obj in self.objectList:
\r
417 if obj.mesh == None:
\r
420 obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
\r
421 #for v in obj.mesh.vertexes:
\r
423 # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
\r
424 # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
\r
425 obj.mesh.getMinimumZ()
\r
428 scale = profile.getProfileSettingFloat('model_scale')
\r
429 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
430 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
432 self.glCanvas.Refresh()
\r
434 def updateProfileToControls(self):
\r
435 self.scale.SetValue(profile.getProfileSetting('model_scale'))
\r
436 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
\r
437 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
\r
438 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
\r
439 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
\r
440 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
\r
441 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
\r
442 self.updateModelTransform()
\r
443 self.glCanvas.updateProfileToControls()
\r
445 class PreviewGLCanvas(glcanvas.GLCanvas):
\r
446 def __init__(self, parent):
\r
447 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
\r
448 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
\r
449 self.parent = parent
\r
450 self.context = glcanvas.GLContext(self)
\r
451 wx.EVT_PAINT(self, self.OnPaint)
\r
452 wx.EVT_SIZE(self, self.OnSize)
\r
453 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
\r
454 wx.EVT_MOTION(self, self.OnMouseMotion)
\r
455 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
\r
462 self.gcodeDisplayList = None
\r
463 self.gcodeDisplayListMade = None
\r
464 self.gcodeDisplayListCount = 0
\r
465 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
469 self.tempRotate = 0
\r
471 def updateProfileToControls(self):
\r
472 self.objColor[0] = profile.getPreferenceColour('model_colour')
\r
473 self.objColor[1] = profile.getPreferenceColour('model_colour2')
\r
474 self.objColor[2] = profile.getPreferenceColour('model_colour3')
\r
475 self.objColor[3] = profile.getPreferenceColour('model_colour4')
\r
477 def OnMouseMotion(self,e):
\r
480 if self.parent.objectsMaxV != None:
\r
481 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
482 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
484 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
\r
485 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
\r
486 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
\r
487 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
\r
488 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
489 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
\r
491 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
\r
493 if e.Dragging() and e.LeftIsDown():
\r
494 if self.dragType == '':
\r
495 #Define the drag type depending on the cursor position.
\r
496 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
497 self.dragType = 'modelRotate'
\r
498 self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
\r
500 self.dragType = 'viewRotate'
\r
502 if self.dragType == 'viewRotate':
\r
504 self.yaw += e.GetX() - self.oldX
\r
505 self.pitch -= e.GetY() - self.oldY
\r
506 if self.pitch > 170:
\r
508 if self.pitch < 10:
\r
511 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
\r
512 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
\r
513 elif self.dragType == 'modelRotate':
\r
514 angle = math.atan2(cursorZ0[0], cursorZ0[1])
\r
515 diff = self.dragStart - angle
\r
516 self.tempRotate = diff * 180 / math.pi
\r
517 #Workaround for buggy ATI cards.
\r
518 size = self.GetSizeTuple()
\r
519 self.SetSize((size[0]+1, size[1]))
\r
520 self.SetSize((size[0], size[1]))
\r
523 if self.tempRotate != 0:
\r
524 profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)
\r
525 self.parent.updateModelTransform()
\r
526 self.tempRotate = 0
\r
529 if e.Dragging() and e.RightIsDown():
\r
530 self.zoom += e.GetY() - self.oldY
\r
533 if self.zoom > 500:
\r
536 self.oldX = e.GetX()
\r
537 self.oldY = e.GetY()
\r
542 def OnMouseWheel(self,e):
\r
543 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
\r
544 if self.zoom < 1.0:
\r
546 if self.zoom > 500:
\r
550 def OnEraseBackground(self,event):
\r
551 #Workaround for windows background redraw flicker.
\r
554 def OnSize(self,e):
\r
557 def OnPaint(self,e):
\r
558 dc = wx.PaintDC(self)
\r
559 if not hasOpenGLlibs:
\r
561 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
\r
563 self.SetCurrent(self.context)
\r
564 opengl.InitGL(self, self.view3D, self.zoom)
\r
566 glTranslate(0,0,-self.zoom)
\r
567 glRotate(-self.pitch, 1,0,0)
\r
568 glRotate(self.yaw, 0,0,1)
\r
569 if self.viewMode == "GCode" or self.viewMode == "Mixed":
\r
570 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
571 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
\r
573 if self.parent.objectsMaxV != None:
\r
574 glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
\r
576 glTranslate(self.offsetX, self.offsetY, 0)
\r
578 self.viewport = glGetIntegerv(GL_VIEWPORT);
\r
579 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
\r
580 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
\r
582 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
\r
588 machineSize = self.parent.machineSize
\r
590 if self.parent.gcode != None and self.parent.gcodeDirty:
\r
591 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
\r
592 if self.gcodeDisplayList != None:
\r
593 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
\r
594 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
\r
595 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
\r
596 self.parent.gcodeDirty = False
\r
597 self.gcodeDisplayListMade = 0
\r
599 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
\r
600 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
\r
601 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
\r
603 self.gcodeDisplayListMade += 1
\r
604 wx.CallAfter(self.Refresh)
\r
607 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
608 for obj in self.parent.objectList:
\r
609 if obj.mesh == None:
\r
611 if obj.displayList == None:
\r
612 obj.displayList = glGenLists(1)
\r
613 obj.steepDisplayList = glGenLists(1)
\r
616 glNewList(obj.displayList, GL_COMPILE)
\r
617 opengl.DrawMesh(obj.mesh)
\r
619 glNewList(obj.steepDisplayList, GL_COMPILE)
\r
620 opengl.DrawMeshSteep(obj.mesh, 60)
\r
623 if self.viewMode == "Mixed":
\r
624 glDisable(GL_BLEND)
\r
625 glColor3f(0.0,0.0,0.0)
\r
626 self.drawModel(obj)
\r
627 glColor3f(1.0,1.0,1.0)
\r
628 glClear(GL_DEPTH_BUFFER_BIT)
\r
632 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
\r
633 glEnable(GL_COLOR_MATERIAL)
\r
634 glEnable(GL_LIGHTING)
\r
635 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
\r
636 starttime = time.time()
\r
637 for i in xrange(drawUpToLayer - 1, -1, -1):
\r
639 if i < self.parent.layerSpin.GetValue():
\r
640 c = 0.9 - (drawUpToLayer - i) * 0.1
\r
645 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
\r
646 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
\r
647 glCallList(self.gcodeDisplayList + i)
\r
648 if time.time() - starttime > 0.1:
\r
651 glDisable(GL_LIGHTING)
\r
652 glDisable(GL_COLOR_MATERIAL)
\r
653 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
\r
654 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
\r
656 glColor3f(1.0,1.0,1.0)
\r
658 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
659 for obj in self.parent.objectList:
\r
660 if obj.mesh == None:
\r
663 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
\r
664 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
\r
665 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
\r
666 #If we want transparent, then first render a solid black model to remove the printer size lines.
\r
667 if self.viewMode != "Mixed":
\r
668 glDisable(GL_BLEND)
\r
669 glColor3f(0.0,0.0,0.0)
\r
670 self.drawModel(obj)
\r
671 glColor3f(1.0,1.0,1.0)
\r
672 #After the black model is rendered, render the model again but now with lighting and no depth testing.
\r
673 glDisable(GL_DEPTH_TEST)
\r
674 glEnable(GL_LIGHTING)
\r
676 glBlendFunc(GL_ONE, GL_ONE)
\r
677 glEnable(GL_LIGHTING)
\r
678 self.drawModel(obj)
\r
679 glEnable(GL_DEPTH_TEST)
\r
680 elif self.viewMode == "X-Ray":
\r
681 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
682 glDisable(GL_LIGHTING)
\r
683 glDisable(GL_DEPTH_TEST)
\r
684 glEnable(GL_STENCIL_TEST)
\r
685 glStencilFunc(GL_ALWAYS, 1, 1)
\r
686 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
\r
687 self.drawModel(obj)
\r
688 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
\r
690 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
691 glStencilFunc(GL_EQUAL, 0, 1)
\r
693 self.drawModel(obj)
\r
694 glStencilFunc(GL_EQUAL, 1, 1)
\r
696 self.drawModel(obj)
\r
700 for i in xrange(2, 15, 2):
\r
701 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
702 glColor(float(i)/10, float(i)/10, float(i)/5)
\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
709 for i in xrange(1, 15, 2):
\r
710 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
711 glColor(float(i)/10, 0, 0)
\r
713 glVertex3f(-1000,-1000,-1)
\r
714 glVertex3f( 1000,-1000,-1)
\r
715 glVertex3f( 1000, 1000,-1)
\r
716 glVertex3f(-1000, 1000,-1)
\r
720 glDisable(GL_STENCIL_TEST)
\r
721 glEnable(GL_DEPTH_TEST)
\r
723 #Fix the depth buffer
\r
724 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
725 self.drawModel(obj)
\r
726 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
727 elif self.viewMode == "Normal":
\r
728 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
\r
729 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
\r
730 glEnable(GL_LIGHTING)
\r
731 self.drawModel(obj)
\r
733 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
\r
734 glEnable(GL_DEPTH_TEST)
\r
735 glDisable(GL_LIGHTING)
\r
738 modelScale = profile.getProfileSettingFloat('model_scale')
\r
739 glScalef(modelScale, modelScale, modelScale)
\r
740 opengl.DrawMeshOutline(obj.mesh)
\r
743 if self.drawSteepOverhang:
\r
744 glDisable(GL_LIGHTING)
\r
747 modelScale = profile.getProfileSettingFloat('model_scale')
\r
748 glScalef(modelScale, modelScale, modelScale)
\r
749 glCallList(obj.steepDisplayList)
\r
753 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
\r
754 glDisable(GL_LIGHTING)
\r
755 glDisable(GL_DEPTH_TEST)
\r
756 glDisable(GL_BLEND)
\r
759 for err in self.parent.errorList:
\r
760 glVertex3f(err[0].x, err[0].y, err[0].z)
\r
761 glVertex3f(err[1].x, err[1].y, err[1].z)
\r
763 glEnable(GL_DEPTH_TEST)
\r
765 opengl.DrawMachine(machineSize)
\r
768 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
770 #Draw the rotate circle
\r
771 if self.parent.objectsMaxV != None and False:
\r
772 glDisable(GL_LIGHTING)
\r
773 glDisable(GL_CULL_FACE)
\r
775 glBegin(GL_TRIANGLE_STRIP)
\r
776 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
777 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
778 for i in xrange(0, 64+1):
\r
779 f = i if i < 64/2 else 64 - i
\r
780 glColor4ub(255,int(f*255/(64/2)),0,128)
\r
781 glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)
\r
782 glColor4ub( 0,128,0,128)
\r
783 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
785 glEnable(GL_CULL_FACE)
\r
791 def drawModel(self, obj):
\r
792 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
\r
793 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
\r
794 modelScale = profile.getProfileSettingFloat('model_scale')
\r
795 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
\r
797 glRotate(self.tempRotate, 0, 0, 1)
\r
798 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
\r
799 for mx in xrange(0, multiX):
\r
800 for my in xrange(0, multiY):
\r
802 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
\r
803 glScalef(modelScale, modelScale, modelScale)
\r
804 glCallList(obj.displayList)
\r