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(self.machineSize.x / 2, self.machineSize.y / 2, 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 setViewMode(self, mode):
\r
236 if mode == "Normal":
\r
237 self.normalViewButton.SetValue(True)
\r
238 if mode == "GCode":
\r
239 self.gcodeViewButton.SetValue(True)
\r
240 self.glCanvas.viewMode = mode
\r
241 wx.CallAfter(self.glCanvas.Refresh)
\r
243 def loadModelFiles(self, filelist, showWarning = False):
\r
244 while len(filelist) > len(self.objectList):
\r
245 self.objectList.append(previewObject())
\r
246 for idx in xrange(len(filelist), len(self.objectList)):
\r
247 self.objectList[idx].mesh = None
\r
248 self.objectList[idx].filename = None
\r
249 for idx in xrange(0, len(filelist)):
\r
250 obj = self.objectList[idx]
\r
251 if obj.filename != filelist[idx]:
\r
252 obj.fileTime = None
\r
253 self.gcodeFileTime = None
\r
254 self.logFileTime = None
\r
255 obj.filename = filelist[idx]
\r
257 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
\r
258 #Do the STL file loading in a background thread so we don't block the UI.
\r
259 if self.loadThread != None and self.loadThread.isAlive():
\r
260 self.loadThread.join()
\r
261 self.loadThread = threading.Thread(target=self.doFileLoadThread)
\r
262 self.loadThread.daemon = True
\r
263 self.loadThread.start()
\r
266 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
267 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
\r
269 def loadReModelFiles(self, filelist):
\r
270 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
\r
271 for idx in xrange(0, len(filelist)):
\r
272 if self.objectList[idx].filename != filelist[idx]:
\r
274 self.loadModelFiles(filelist)
\r
277 def doFileLoadThread(self):
\r
278 for obj in self.objectList:
\r
279 if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
\r
280 obj.ileTime = os.stat(obj.filename).st_mtime
\r
281 mesh = meshLoader.loadMesh(obj.filename)
\r
284 self.updateModelTransform()
\r
285 self.OnScaleMax(None, True)
\r
286 scale = profile.getProfileSettingFloat('model_scale')
\r
287 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
288 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
289 self.glCanvas.zoom = numpy.max(size) * 2.5
\r
290 self.errorList = []
\r
291 wx.CallAfter(self.updateToolbar)
\r
292 wx.CallAfter(self.glCanvas.Refresh)
\r
294 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
\r
295 self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
\r
296 gcode = gcodeInterpreter.gcode()
\r
297 gcode.progressCallback = self.loadProgress
\r
298 gcode.load(self.gcodeFilename)
\r
299 self.gcodeDirty = False
\r
301 self.gcodeDirty = True
\r
304 for line in open(self.gcodeFilename, "rt"):
\r
305 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
307 v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
\r
308 v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
\r
309 errorList.append([v1, v2])
\r
310 self.errorList = errorList
\r
312 wx.CallAfter(self.updateToolbar)
\r
313 wx.CallAfter(self.glCanvas.Refresh)
\r
314 elif not os.path.isfile(self.gcodeFilename):
\r
317 def loadProgress(self, progress):
\r
320 def OnResetAll(self, e = None):
\r
321 profile.putProfileSetting('model_scale', '1.0')
\r
322 profile.putProfileSetting('model_rotate_base', '0')
\r
323 profile.putProfileSetting('flip_x', 'False')
\r
324 profile.putProfileSetting('flip_y', 'False')
\r
325 profile.putProfileSetting('flip_z', 'False')
\r
326 profile.putProfileSetting('swap_xz', 'False')
\r
327 profile.putProfileSetting('swap_yz', 'False')
\r
328 profile.setPluginConfig([])
\r
329 self.GetParent().updateProfileToControls()
\r
331 def ShowWarningPopup(self, text, callback = None):
\r
332 self.warningPopup.text.SetLabel(text)
\r
333 self.warningPopup.callback = callback
\r
334 if callback == None:
\r
335 self.warningPopup.yesButton.Show(False)
\r
336 self.warningPopup.noButton.SetLabel('ok')
\r
338 self.warningPopup.yesButton.Show(True)
\r
339 self.warningPopup.noButton.SetLabel('no')
\r
340 self.warningPopup.Fit()
\r
341 self.warningPopup.Layout()
\r
343 self.warningPopup.Show(True)
\r
344 self.warningPopup.timer.Start(5000)
\r
346 def OnWarningPopup(self, e):
\r
347 self.warningPopup.Show(False)
\r
348 self.warningPopup.timer.Stop()
\r
349 self.warningPopup.callback()
\r
351 def OnHideWarning(self, e):
\r
352 self.warningPopup.Show(False)
\r
353 self.warningPopup.timer.Stop()
\r
355 def updateToolbar(self):
\r
356 self.gcodeViewButton.Show(self.gcode != None)
\r
357 self.mixedViewButton.Show(self.gcode != None)
\r
358 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
\r
359 if self.gcode != None:
\r
360 self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
\r
361 self.toolbar.Realize()
\r
364 def OnViewChange(self):
\r
365 if self.normalViewButton.GetValue():
\r
366 self.glCanvas.viewMode = "Normal"
\r
367 elif self.transparentViewButton.GetValue():
\r
368 self.glCanvas.viewMode = "Transparent"
\r
369 elif self.xrayViewButton.GetValue():
\r
370 self.glCanvas.viewMode = "X-Ray"
\r
371 elif self.gcodeViewButton.GetValue():
\r
372 self.glCanvas.viewMode = "GCode"
\r
373 elif self.mixedViewButton.GetValue():
\r
374 self.glCanvas.viewMode = "Mixed"
\r
375 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
\r
376 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
\r
377 self.updateToolbar()
\r
378 self.glCanvas.Refresh()
\r
380 def updateModelTransform(self, f=0):
\r
381 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
\r
384 rotate = profile.getProfileSettingFloat('model_rotate_base')
\r
385 mirrorX = profile.getProfileSetting('flip_x') == 'True'
\r
386 mirrorY = profile.getProfileSetting('flip_y') == 'True'
\r
387 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
\r
388 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
\r
389 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
\r
391 for obj in self.objectList:
\r
392 if obj.mesh == None:
\r
394 obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
\r
396 minV = self.objectList[0].mesh.getMinimum()
\r
397 maxV = self.objectList[0].mesh.getMaximum()
\r
398 for obj in self.objectList:
\r
399 if obj.mesh == None:
\r
402 obj.mesh.getMinimumZ()
\r
403 minV = numpy.minimum(minV, obj.mesh.getMinimum())
\r
404 maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
\r
406 self.objectsMaxV = maxV
\r
407 self.objectsMinV = minV
\r
408 for obj in self.objectList:
\r
409 if obj.mesh == None:
\r
412 obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
\r
413 #for v in obj.mesh.vertexes:
\r
415 # v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
\r
416 # v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
\r
417 obj.mesh.getMinimumZ()
\r
420 scale = profile.getProfileSettingFloat('model_scale')
\r
421 size = (self.objectsMaxV - self.objectsMinV) * scale
\r
422 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
\r
424 self.glCanvas.Refresh()
\r
426 def updateProfileToControls(self):
\r
427 self.scale.SetValue(profile.getProfileSetting('model_scale'))
\r
428 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
\r
429 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
\r
430 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
\r
431 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
\r
432 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
\r
433 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
\r
434 self.updateModelTransform()
\r
435 self.glCanvas.updateProfileToControls()
\r
437 class PreviewGLCanvas(glcanvas.GLCanvas):
\r
438 def __init__(self, parent):
\r
439 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
\r
440 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
\r
441 self.parent = parent
\r
442 self.context = glcanvas.GLContext(self)
\r
443 wx.EVT_PAINT(self, self.OnPaint)
\r
444 wx.EVT_SIZE(self, self.OnSize)
\r
445 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
\r
446 wx.EVT_MOTION(self, self.OnMouseMotion)
\r
447 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
\r
454 self.gcodeDisplayList = None
\r
455 self.gcodeDisplayListMade = None
\r
456 self.gcodeDisplayListCount = 0
\r
457 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
461 self.tempRotate = 0
\r
463 def updateProfileToControls(self):
\r
464 self.objColor[0] = profile.getPreferenceColour('model_colour')
\r
465 self.objColor[1] = profile.getPreferenceColour('model_colour2')
\r
466 self.objColor[2] = profile.getPreferenceColour('model_colour3')
\r
467 self.objColor[3] = profile.getPreferenceColour('model_colour4')
\r
469 def OnMouseMotion(self,e):
\r
472 if self.parent.objectsMaxV != None:
\r
473 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
474 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
476 p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
\r
477 p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
\r
478 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
\r
479 cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
\r
480 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
481 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
\r
483 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
\r
485 if e.Dragging() and e.LeftIsDown():
\r
486 if self.dragType == '':
\r
487 #Define the drag type depending on the cursor position.
\r
488 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:
\r
489 self.dragType = 'modelRotate'
\r
490 self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
\r
492 self.dragType = 'viewRotate'
\r
494 if self.dragType == 'viewRotate':
\r
496 self.yaw += e.GetX() - self.oldX
\r
497 self.pitch -= e.GetY() - self.oldY
\r
498 if self.pitch > 170:
\r
500 if self.pitch < 10:
\r
503 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
\r
504 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
\r
505 elif self.dragType == 'modelRotate':
\r
506 angle = math.atan2(cursorZ0[0], cursorZ0[1])
\r
507 diff = self.dragStart - angle
\r
508 self.tempRotate = diff * 180 / math.pi
\r
509 #Workaround for buggy ATI cards.
\r
510 size = self.GetSizeTuple()
\r
511 self.SetSize((size[0]+1, size[1]))
\r
512 self.SetSize((size[0], size[1]))
\r
515 if self.tempRotate != 0:
\r
516 profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)
\r
517 self.parent.updateModelTransform()
\r
518 self.tempRotate = 0
\r
521 if e.Dragging() and e.RightIsDown():
\r
522 self.zoom += e.GetY() - self.oldY
\r
525 if self.zoom > 500:
\r
528 self.oldX = e.GetX()
\r
529 self.oldY = e.GetY()
\r
534 def OnMouseWheel(self,e):
\r
535 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
\r
536 if self.zoom < 1.0:
\r
538 if self.zoom > 500:
\r
542 def OnEraseBackground(self,event):
\r
543 #Workaround for windows background redraw flicker.
\r
546 def OnSize(self,e):
\r
549 def OnPaint(self,e):
\r
550 dc = wx.PaintDC(self)
\r
551 if not hasOpenGLlibs:
\r
553 dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
\r
555 self.SetCurrent(self.context)
\r
556 opengl.InitGL(self, self.view3D, self.zoom)
\r
558 glTranslate(0,0,-self.zoom)
\r
559 glRotate(-self.pitch, 1,0,0)
\r
560 glRotate(self.yaw, 0,0,1)
\r
561 if self.viewMode == "GCode" or self.viewMode == "Mixed":
\r
562 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
563 glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
\r
565 if self.parent.objectsMaxV != None:
\r
566 glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
\r
568 glTranslate(self.offsetX, self.offsetY, 0)
\r
570 self.viewport = glGetIntegerv(GL_VIEWPORT);
\r
571 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
\r
572 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
\r
574 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
\r
580 machineSize = self.parent.machineSize
\r
582 if self.parent.gcode != None and self.parent.gcodeDirty:
\r
583 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
\r
584 if self.gcodeDisplayList != None:
\r
585 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
\r
586 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
\r
587 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
\r
588 self.parent.gcodeDirty = False
\r
589 self.gcodeDisplayListMade = 0
\r
591 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
\r
592 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
\r
593 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
\r
595 self.gcodeDisplayListMade += 1
\r
596 wx.CallAfter(self.Refresh)
\r
599 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
600 for obj in self.parent.objectList:
\r
601 if obj.mesh == None:
\r
603 if obj.displayList == None:
\r
604 obj.displayList = glGenLists(1)
\r
605 obj.steepDisplayList = glGenLists(1)
\r
608 glNewList(obj.displayList, GL_COMPILE)
\r
609 opengl.DrawMesh(obj.mesh)
\r
611 glNewList(obj.steepDisplayList, GL_COMPILE)
\r
612 opengl.DrawMeshSteep(obj.mesh, 60)
\r
615 if self.viewMode == "Mixed":
\r
616 glDisable(GL_BLEND)
\r
617 glColor3f(0.0,0.0,0.0)
\r
618 self.drawModel(obj)
\r
619 glColor3f(1.0,1.0,1.0)
\r
620 glClear(GL_DEPTH_BUFFER_BIT)
\r
624 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
\r
625 glEnable(GL_COLOR_MATERIAL)
\r
626 glEnable(GL_LIGHTING)
\r
627 drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
\r
628 starttime = time.time()
\r
629 for i in xrange(drawUpToLayer - 1, -1, -1):
\r
631 if i < self.parent.layerSpin.GetValue():
\r
632 c = 0.9 - (drawUpToLayer - i) * 0.1
\r
637 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
\r
638 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
\r
639 glCallList(self.gcodeDisplayList + i)
\r
640 if time.time() - starttime > 0.1:
\r
643 glDisable(GL_LIGHTING)
\r
644 glDisable(GL_COLOR_MATERIAL)
\r
645 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
\r
646 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
\r
648 glColor3f(1.0,1.0,1.0)
\r
650 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
651 for obj in self.parent.objectList:
\r
652 if obj.mesh == None:
\r
655 if self.viewMode == "Transparent" or self.viewMode == "Mixed":
\r
656 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
\r
657 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
\r
658 #If we want transparent, then first render a solid black model to remove the printer size lines.
\r
659 if self.viewMode != "Mixed":
\r
660 glDisable(GL_BLEND)
\r
661 glColor3f(0.0,0.0,0.0)
\r
662 self.drawModel(obj)
\r
663 glColor3f(1.0,1.0,1.0)
\r
664 #After the black model is rendered, render the model again but now with lighting and no depth testing.
\r
665 glDisable(GL_DEPTH_TEST)
\r
666 glEnable(GL_LIGHTING)
\r
668 glBlendFunc(GL_ONE, GL_ONE)
\r
669 glEnable(GL_LIGHTING)
\r
670 self.drawModel(obj)
\r
671 glEnable(GL_DEPTH_TEST)
\r
672 elif self.viewMode == "X-Ray":
\r
673 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
674 glDisable(GL_LIGHTING)
\r
675 glDisable(GL_DEPTH_TEST)
\r
676 glEnable(GL_STENCIL_TEST)
\r
677 glStencilFunc(GL_ALWAYS, 1, 1)
\r
678 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
\r
679 self.drawModel(obj)
\r
680 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
\r
682 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
683 glStencilFunc(GL_EQUAL, 0, 1)
\r
685 self.drawModel(obj)
\r
686 glStencilFunc(GL_EQUAL, 1, 1)
\r
688 self.drawModel(obj)
\r
692 for i in xrange(2, 15, 2):
\r
693 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
694 glColor(float(i)/10, float(i)/10, float(i)/5)
\r
696 glVertex3f(-1000,-1000,-1)
\r
697 glVertex3f( 1000,-1000,-1)
\r
698 glVertex3f( 1000, 1000,-1)
\r
699 glVertex3f(-1000, 1000,-1)
\r
701 for i in xrange(1, 15, 2):
\r
702 glStencilFunc(GL_EQUAL, i, 0xFF);
\r
703 glColor(float(i)/10, 0, 0)
\r
705 glVertex3f(-1000,-1000,-1)
\r
706 glVertex3f( 1000,-1000,-1)
\r
707 glVertex3f( 1000, 1000,-1)
\r
708 glVertex3f(-1000, 1000,-1)
\r
712 glDisable(GL_STENCIL_TEST)
\r
713 glEnable(GL_DEPTH_TEST)
\r
715 #Fix the depth buffer
\r
716 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
\r
717 self.drawModel(obj)
\r
718 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
\r
719 elif self.viewMode == "Normal":
\r
720 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
\r
721 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
\r
722 glEnable(GL_LIGHTING)
\r
723 self.drawModel(obj)
\r
725 if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
\r
726 glEnable(GL_DEPTH_TEST)
\r
727 glDisable(GL_LIGHTING)
\r
730 modelScale = profile.getProfileSettingFloat('model_scale')
\r
731 glScalef(modelScale, modelScale, modelScale)
\r
732 opengl.DrawMeshOutline(obj.mesh)
\r
735 if self.drawSteepOverhang:
\r
736 glDisable(GL_LIGHTING)
\r
739 modelScale = profile.getProfileSettingFloat('model_scale')
\r
740 glScalef(modelScale, modelScale, modelScale)
\r
741 glCallList(obj.steepDisplayList)
\r
745 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
\r
746 glDisable(GL_LIGHTING)
\r
747 glDisable(GL_DEPTH_TEST)
\r
748 glDisable(GL_BLEND)
\r
751 for err in self.parent.errorList:
\r
752 glVertex3f(err[0].x, err[0].y, err[0].z)
\r
753 glVertex3f(err[1].x, err[1].y, err[1].z)
\r
755 glEnable(GL_DEPTH_TEST)
\r
757 opengl.DrawMachine(machineSize)
\r
760 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
\r
762 #Draw the rotate circle
\r
763 if self.parent.objectsMaxV != None and False:
\r
764 glDisable(GL_LIGHTING)
\r
765 glDisable(GL_CULL_FACE)
\r
767 glBegin(GL_TRIANGLE_STRIP)
\r
768 size = (self.parent.objectsMaxV - self.parent.objectsMinV)
\r
769 sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))
\r
770 for i in xrange(0, 64+1):
\r
771 f = i if i < 64/2 else 64 - i
\r
772 glColor4ub(255,int(f*255/(64/2)),0,128)
\r
773 glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)
\r
774 glColor4ub( 0,128,0,128)
\r
775 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
777 glEnable(GL_CULL_FACE)
\r
783 def drawModel(self, obj):
\r
784 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
\r
785 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
\r
786 modelScale = profile.getProfileSettingFloat('model_scale')
\r
787 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
\r
789 glRotate(self.tempRotate, 0, 0, 1)
\r
790 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
\r
791 for mx in xrange(0, multiX):
\r
792 for my in xrange(0, multiY):
\r
794 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
\r
795 glScalef(modelScale, modelScale, modelScale)
\r
796 glCallList(obj.displayList)
\r