chiark / gitweb /
c689b7f9ad161f15868fc3a8f45462c660846050
[cura.git] / Cura / gui / preview3d.py
1 from __future__ import division
2
3 import sys, math, threading, re, time, os
4 import numpy
5
6 from wx import glcanvas
7 import wx
8 try:
9         import OpenGL
10         OpenGL.ERROR_CHECKING = False
11         from OpenGL.GLU import *
12         from OpenGL.GL import *
13         hasOpenGLlibs = True
14 except:
15         print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
16         hasOpenGLlibs = False
17
18 from gui import opengl
19 from gui import toolbarUtil
20
21 from util import profile
22 from util import gcodeInterpreter
23 from util import meshLoader
24 from util import util3d
25 from util import sliceRun
26
27 class previewObject():
28         def __init__(self):
29                 self.mesh = None
30                 self.filename = None
31                 self.displayList = None
32                 self.dirty = False
33
34 class previewPanel(wx.Panel):
35         def __init__(self, parent):
36                 super(previewPanel, self).__init__(parent,-1)
37                 
38                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
39                 self.SetMinSize((440,320))
40                 
41                 self.objectList = []
42                 self.errorList = []
43                 self.gcode = None
44                 self.objectsMinV = None
45                 self.objectsMaxV = None
46                 self.objectsBounderyCircleSize = None
47                 self.loadThread = None
48                 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
49                 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
50
51                 self.glCanvas = PreviewGLCanvas(self)
52                 #Create the popup window
53                 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
54                 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
55                 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
56                 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
57                 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
58                 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
59                 self.warningPopup.SetSizer(self.warningPopup.sizer)
60                 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
61                 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
62                 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
63                 self.warningPopup.Fit()
64                 self.warningPopup.Layout()
65                 self.warningPopup.timer = wx.Timer(self)
66                 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
67                 
68                 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
69                 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
70                 parent.Bind(wx.EVT_MOVE, self.OnMove)
71                 parent.Bind(wx.EVT_SIZE, self.OnMove)
72                 
73                 self.toolbar = toolbarUtil.Toolbar(self)
74
75                 group = []
76                 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
77                 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
78                 self.toolbar.AddSeparator()
79
80                 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
81                 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
82                 self.toolbar.AddSeparator()
83
84                 group = []
85                 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
86                 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
87                 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
88                 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
89                 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
90                 self.toolbar.AddSeparator()
91
92                 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
93                 self.toolbar.AddControl(self.layerSpin)
94                 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
95                 self.toolbar.AddSeparator()
96                 self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)
97                 self.toolbar.AddControl(self.toolbarInfo)
98
99                 self.toolbar2 = toolbarUtil.Toolbar(self)
100
101                 # Mirror
102                 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.returnToModelViewAndUpdateModel)
103                 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)
104                 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)
105                 self.toolbar2.AddSeparator()
106
107                 # Swap
108                 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.returnToModelViewAndUpdateModel)
109                 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)
110                 self.toolbar2.AddSeparator()
111
112                 # Scale
113                 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
114                 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
115                 self.toolbar2.AddControl(self.scale)
116                 self.scale.Bind(wx.EVT_TEXT, self.OnScale)
117                 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
118
119                 self.toolbar2.AddSeparator()
120
121                 # Multiply
122                 #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
123                 #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
124                 #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
125                 #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
126                 #self.toolbar2.AddSeparator()
127
128                 # Rotate
129                 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
130                 self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)
131                 self.rotate.SetRange(0, 360)
132                 self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
133                 self.toolbar2.AddControl(self.rotate)
134
135                 self.toolbar2.Realize()
136                 self.OnViewChange()
137                 
138                 sizer = wx.BoxSizer(wx.VERTICAL)
139                 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
140                 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
141                 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
142                 self.SetSizer(sizer)
143         
144         def returnToModelViewAndUpdateModel(self):
145                 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
146                         self.setViewMode('Normal')
147                 self.updateModelTransform()
148         
149         def OnMove(self, e = None):
150                 if e != None:
151                         e.Skip()
152                 x, y = self.glCanvas.ClientToScreenXY(0, 0)
153                 sx, sy = self.glCanvas.GetClientSizeTuple()
154                 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
155         
156         def OnMulXAddClick(self, e):
157                 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
158                 self.glCanvas.Refresh()
159
160         def OnMulXSubClick(self, e):
161                 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
162                 self.glCanvas.Refresh()
163
164         def OnMulYAddClick(self, e):
165                 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
166                 self.glCanvas.Refresh()
167
168         def OnMulYSubClick(self, e):
169                 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
170                 self.glCanvas.Refresh()
171
172         def OnScaleReset(self, e):
173                 self.scale.SetValue('1.0')
174                 self.OnScale(None)
175
176         def OnScale(self, e):
177                 scale = 1.0
178                 if self.scale.GetValue() != '':
179                         scale = self.scale.GetValue()
180                 profile.putProfileSetting('model_scale', scale)
181                 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
182                         self.setViewMode('Normal')
183                 self.glCanvas.Refresh()
184
185                 if self.objectsMaxV != None:
186                         size = (self.objectsMaxV - self.objectsMinV) * float(scale)
187                         self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
188
189         def OnScaleMax(self, e = None, onlyScaleDown = False):
190                 if self.objectsMinV == None:
191                         return
192                 vMin = self.objectsMinV
193                 vMax = self.objectsMaxV
194                 skirtSize = 3
195                 if profile.getProfileSettingFloat('skirt_line_count') > 0:
196                         skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
197                 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
198                 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
199                 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
200                 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
201                 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
202                 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
203                 if scale > 1.0 and onlyScaleDown:
204                         return
205                 self.scale.SetValue(str(scale))
206                 profile.putProfileSetting('model_scale', self.scale.GetValue())
207                 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
208                         self.setViewMode('Normal')
209                 self.glCanvas.Refresh()
210
211         def OnRotateReset(self, e):
212                 self.rotate.SetValue(0)
213                 self.OnRotate(None)
214
215         def OnRotate(self, e):
216                 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
217                 self.returnToModelViewAndUpdateModel()
218
219         def On3DClick(self):
220                 self.glCanvas.yaw = 30
221                 self.glCanvas.pitch = 60
222                 self.glCanvas.zoom = 300
223                 self.glCanvas.view3D = True
224                 self.glCanvas.Refresh()
225
226         def OnTopClick(self):
227                 self.glCanvas.view3D = False
228                 self.glCanvas.zoom = 100
229                 self.glCanvas.offsetX = 0
230                 self.glCanvas.offsetY = 0
231                 self.glCanvas.Refresh()
232
233         def OnLayerNrChange(self, e):
234                 self.glCanvas.Refresh()
235         
236         def setViewMode(self, mode):
237                 if mode == "Normal":
238                         self.normalViewButton.SetValue(True)
239                 if mode == "GCode":
240                         self.gcodeViewButton.SetValue(True)
241                 self.glCanvas.viewMode = mode
242                 wx.CallAfter(self.glCanvas.Refresh)
243         
244         def loadModelFiles(self, filelist, showWarning = False):
245                 while len(filelist) > len(self.objectList):
246                         self.objectList.append(previewObject())
247                 for idx in xrange(len(filelist), len(self.objectList)):
248                         self.objectList[idx].mesh = None
249                         self.objectList[idx].filename = None
250                 for idx in xrange(0, len(filelist)):
251                         obj = self.objectList[idx]
252                         if obj.filename != filelist[idx]:
253                                 obj.fileTime = None
254                                 self.gcodeFileTime = None
255                                 self.logFileTime = None
256                         obj.filename = filelist[idx]
257                 
258                 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
259                 #Do the STL file loading in a background thread so we don't block the UI.
260                 if self.loadThread != None and self.loadThread.isAlive():
261                         self.loadThread.join()
262                 self.loadThread = threading.Thread(target=self.doFileLoadThread)
263                 self.loadThread.daemon = True
264                 self.loadThread.start()
265                 
266                 if showWarning:
267                         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:
268                                 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
269         
270         def loadReModelFiles(self, filelist):
271                 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
272                 for idx in xrange(0, len(filelist)):
273                         if self.objectList[idx].filename != filelist[idx]:
274                                 return False
275                 self.loadModelFiles(filelist)
276                 return True
277         
278         def doFileLoadThread(self):
279                 for obj in self.objectList:
280                         if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
281                                 obj.ileTime = os.stat(obj.filename).st_mtime
282                                 mesh = meshLoader.loadMesh(obj.filename)
283                                 obj.dirty = False
284                                 obj.mesh = mesh
285                                 self.updateModelTransform()
286                                 self.OnScaleMax(None, True)
287                                 scale = profile.getProfileSettingFloat('model_scale')
288                                 size = (self.objectsMaxV - self.objectsMinV) * scale
289                                 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
290                                 self.glCanvas.zoom = numpy.max(size) * 2.5
291                                 self.errorList = []
292                                 wx.CallAfter(self.updateToolbar)
293                                 wx.CallAfter(self.glCanvas.Refresh)
294                 
295                 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
296                         self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
297                         gcode = gcodeInterpreter.gcode()
298                         gcode.progressCallback = self.loadProgress
299                         gcode.load(self.gcodeFilename)
300                         self.gcodeDirty = False
301                         self.gcode = gcode
302                         self.gcodeDirty = True
303
304                         errorList = []
305                         for line in open(self.gcodeFilename, "rt"):
306                                 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)
307                                 if res != None:
308                                         v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
309                                         v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
310                                         errorList.append([v1, v2])
311                         self.errorList = errorList
312
313                         wx.CallAfter(self.updateToolbar)
314                         wx.CallAfter(self.glCanvas.Refresh)
315                 elif not os.path.isfile(self.gcodeFilename):
316                         self.gcode = None
317         
318         def loadProgress(self, progress):
319                 pass
320
321         def OnResetAll(self, e = None):
322                 profile.putProfileSetting('model_scale', '1.0')
323                 profile.putProfileSetting('model_rotate_base', '0')
324                 profile.putProfileSetting('flip_x', 'False')
325                 profile.putProfileSetting('flip_y', 'False')
326                 profile.putProfileSetting('flip_z', 'False')
327                 profile.putProfileSetting('swap_xz', 'False')
328                 profile.putProfileSetting('swap_yz', 'False')
329                 profile.setPluginConfig([])
330                 self.GetParent().updateProfileToControls()
331
332         def ShowWarningPopup(self, text, callback = None):
333                 self.warningPopup.text.SetLabel(text)
334                 self.warningPopup.callback = callback
335                 if callback == None:
336                         self.warningPopup.yesButton.Show(False)
337                         self.warningPopup.noButton.SetLabel('ok')
338                 else:
339                         self.warningPopup.yesButton.Show(True)
340                         self.warningPopup.noButton.SetLabel('no')
341                 self.warningPopup.Fit()
342                 self.warningPopup.Layout()
343                 self.OnMove()
344                 self.warningPopup.Show(True)
345                 self.warningPopup.timer.Start(5000)
346         
347         def OnWarningPopup(self, e):
348                 self.warningPopup.Show(False)
349                 self.warningPopup.timer.Stop()
350                 self.warningPopup.callback()
351
352         def OnHideWarning(self, e):
353                 self.warningPopup.Show(False)
354                 self.warningPopup.timer.Stop()
355
356         def updateToolbar(self):
357                 self.gcodeViewButton.Show(self.gcode != None)
358                 self.mixedViewButton.Show(self.gcode != None)
359                 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
360                 if self.gcode != None:
361                         self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
362                 self.toolbar.Realize()
363                 self.Update()
364         
365         def OnViewChange(self):
366                 if self.normalViewButton.GetValue():
367                         self.glCanvas.viewMode = "Normal"
368                 elif self.transparentViewButton.GetValue():
369                         self.glCanvas.viewMode = "Transparent"
370                 elif self.xrayViewButton.GetValue():
371                         self.glCanvas.viewMode = "X-Ray"
372                 elif self.gcodeViewButton.GetValue():
373                         self.glCanvas.viewMode = "GCode"
374                 elif self.mixedViewButton.GetValue():
375                         self.glCanvas.viewMode = "Mixed"
376                 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
377                 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
378                 self.updateToolbar()
379                 self.glCanvas.Refresh()
380         
381         def updateModelTransform(self, f=0):
382                 if len(self.objectList) < 1 or self.objectList[0].mesh == None:
383                         return
384                 
385                 rotate = profile.getProfileSettingFloat('model_rotate_base')
386                 mirrorX = profile.getProfileSetting('flip_x') == 'True'
387                 mirrorY = profile.getProfileSetting('flip_y') == 'True'
388                 mirrorZ = profile.getProfileSetting('flip_z') == 'True'
389                 swapXZ = profile.getProfileSetting('swap_xz') == 'True'
390                 swapYZ = profile.getProfileSetting('swap_yz') == 'True'
391
392                 for obj in self.objectList:
393                         if obj.mesh == None:
394                                 continue
395                         obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
396                 
397                 minV = self.objectList[0].mesh.getMinimum()
398                 maxV = self.objectList[0].mesh.getMaximum()
399                 objectsBounderyCircleSize = self.objectList[0].mesh.bounderyCircleSize
400                 for obj in self.objectList:
401                         if obj.mesh == None:
402                                 continue
403
404                         obj.mesh.getMinimumZ()
405                         minV = numpy.minimum(minV, obj.mesh.getMinimum())
406                         maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
407                         objectsBounderyCircleSize = max(objectsBounderyCircleSize, obj.mesh.bounderyCircleSize)
408
409                 self.objectsMaxV = maxV
410                 self.objectsMinV = minV
411                 self.objectsBounderyCircleSize = objectsBounderyCircleSize
412                 for obj in self.objectList:
413                         if obj.mesh == None:
414                                 continue
415
416                         obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
417                         #for v in obj.mesh.vertexes:
418                         #       v[2] -= minV[2]
419                         #       v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
420                         #       v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
421                         obj.mesh.getMinimumZ()
422                         obj.dirty = True
423
424                 scale = profile.getProfileSettingFloat('model_scale')
425                 size = (self.objectsMaxV - self.objectsMinV) * scale
426                 self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
427
428                 self.glCanvas.Refresh()
429         
430         def updateProfileToControls(self):
431                 self.scale.SetValue(profile.getProfileSetting('model_scale'))
432                 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
433                 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
434                 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
435                 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
436                 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
437                 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
438                 self.updateModelTransform()
439                 self.glCanvas.updateProfileToControls()
440
441 class PreviewGLCanvas(glcanvas.GLCanvas):
442         def __init__(self, parent):
443                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
444                 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
445                 self.parent = parent
446                 self.context = glcanvas.GLContext(self)
447                 wx.EVT_PAINT(self, self.OnPaint)
448                 wx.EVT_SIZE(self, self.OnSize)
449                 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
450                 wx.EVT_MOTION(self, self.OnMouseMotion)
451                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
452                 self.yaw = 30
453                 self.pitch = 60
454                 self.zoom = 300
455                 self.offsetX = 0
456                 self.offsetY = 0
457                 self.view3D = True
458                 self.gcodeDisplayList = None
459                 self.gcodeDisplayListMade = None
460                 self.gcodeDisplayListCount = 0
461                 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]]
462                 self.oldX = 0
463                 self.oldY = 0
464                 self.dragType = ''
465                 self.tempRotate = 0
466         
467         def updateProfileToControls(self):
468                 self.objColor[0] = profile.getPreferenceColour('model_colour')
469                 self.objColor[1] = profile.getPreferenceColour('model_colour2')
470                 self.objColor[2] = profile.getPreferenceColour('model_colour3')
471                 self.objColor[3] = profile.getPreferenceColour('model_colour4')
472
473         def OnMouseMotion(self,e):
474                 cursorXY = 100000
475                 radius = 0
476                 if self.parent.objectsMaxV != None:
477                         radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
478                         
479                         p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
480                         p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
481                         cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
482                         cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
483                         if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
484                                 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
485                         else:
486                                 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
487
488                 if e.Dragging() and e.LeftIsDown():
489                         if self.dragType == '':
490                                 #Define the drag type depending on the cursor position.
491                                 if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
492                                         self.dragType = 'modelRotate'
493                                         self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
494                                 else:
495                                         self.dragType = 'viewRotate'
496                                 
497                         if self.dragType == 'viewRotate':
498                                 if self.view3D:
499                                         self.yaw += e.GetX() - self.oldX
500                                         self.pitch -= e.GetY() - self.oldY
501                                         if self.pitch > 170:
502                                                 self.pitch = 170
503                                         if self.pitch < 10:
504                                                 self.pitch = 10
505                                 else:
506                                         self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
507                                         self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
508                         elif self.dragType == 'modelRotate':
509                                 angle = math.atan2(cursorZ0[0], cursorZ0[1])
510                                 diff = self.dragStart - angle
511                                 self.tempRotate = diff * 180 / math.pi
512                                 rot = profile.getProfileSettingFloat('model_rotate_base')
513                                 self.tempRotate = round((self.tempRotate + rot) / 15) * 15 - rot
514                         #Workaround for buggy ATI cards.
515                         size = self.GetSizeTuple()
516                         self.SetSize((size[0]+1, size[1]))
517                         self.SetSize((size[0], size[1]))
518                         self.Refresh()
519                 else:
520                         if self.tempRotate != 0:
521                                 newRotation = profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate
522                                 while newRotation >= 360:
523                                         newRotation -= 360
524                                 while newRotation < 0:
525                                         newRotation += 360
526                                 profile.putProfileSetting('model_rotate_base', newRotation)
527                                 self.parent.rotate.SetValue(newRotation)
528                                 self.parent.updateModelTransform()
529                                 self.tempRotate = 0
530                                 
531                         self.dragType = ''
532                 if e.Dragging() and e.RightIsDown():
533                         self.zoom += e.GetY() - self.oldY
534                         if self.zoom < 1:
535                                 self.zoom = 1
536                         if self.zoom > 500:
537                                 self.zoom = 500
538                         self.Refresh()
539                 self.oldX = e.GetX()
540                 self.oldY = e.GetY()
541
542                 #self.Refresh()
543                 
544         
545         def OnMouseWheel(self,e):
546                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
547                 if self.zoom < 1.0:
548                         self.zoom = 1.0
549                 if self.zoom > 500:
550                         self.zoom = 500
551                 self.Refresh()
552         
553         def OnEraseBackground(self,event):
554                 #Workaround for windows background redraw flicker.
555                 pass
556         
557         def OnSize(self,e):
558                 self.Refresh()
559
560         def OnPaint(self,e):
561                 dc = wx.PaintDC(self)
562                 if not hasOpenGLlibs:
563                         dc.Clear()
564                         dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
565                         return
566                 self.SetCurrent(self.context)
567                 opengl.InitGL(self, self.view3D, self.zoom)
568                 if self.view3D:
569                         glTranslate(0,0,-self.zoom)
570                         glRotate(-self.pitch, 1,0,0)
571                         glRotate(self.yaw, 0,0,1)
572                         if self.viewMode == "GCode" or self.viewMode == "Mixed":
573                                 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:
574                                         glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
575                         else:
576                                 if self.parent.objectsMaxV != None:
577                                         glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
578                 else:
579                         glTranslate(self.offsetX, self.offsetY, 0)
580
581                 self.viewport = glGetIntegerv(GL_VIEWPORT);
582                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
583                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
584
585                 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
586
587                 self.OnDraw()
588                 self.SwapBuffers()
589
590         def OnDraw(self):
591                 machineSize = self.parent.machineSize
592
593                 if self.parent.gcode != None and self.parent.gcodeDirty:
594                         if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
595                                 if self.gcodeDisplayList != None:
596                                         glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
597                                 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
598                                 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
599                         self.parent.gcodeDirty = False
600                         self.gcodeDisplayListMade = 0
601                 
602                 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
603                         glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
604                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
605                         glEndList()
606                         self.gcodeDisplayListMade += 1
607                         wx.CallAfter(self.Refresh)
608                 
609                 glPushMatrix()
610                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
611                 for obj in self.parent.objectList:
612                         if obj.mesh == None:
613                                 continue
614                         if obj.displayList == None:
615                                 obj.displayList = glGenLists(1)
616                                 obj.steepDisplayList = glGenLists(1)
617                         if obj.dirty:
618                                 obj.dirty = False
619                                 glNewList(obj.displayList, GL_COMPILE)
620                                 opengl.DrawMesh(obj.mesh)
621                                 glEndList()
622                                 glNewList(obj.steepDisplayList, GL_COMPILE)
623                                 opengl.DrawMeshSteep(obj.mesh, 60)
624                                 glEndList()
625                         
626                         if self.viewMode == "Mixed":
627                                 glDisable(GL_BLEND)
628                                 glColor3f(0.0,0.0,0.0)
629                                 self.drawModel(obj)
630                                 glColor3f(1.0,1.0,1.0)
631                                 glClear(GL_DEPTH_BUFFER_BIT)
632                 
633                 glPopMatrix()
634                 
635                 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
636                         glEnable(GL_COLOR_MATERIAL)
637                         glEnable(GL_LIGHTING)
638                         drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
639                         starttime = time.time()
640                         for i in xrange(drawUpToLayer - 1, -1, -1):
641                                 c = 1.0
642                                 if i < self.parent.layerSpin.GetValue():
643                                         c = 0.9 - (drawUpToLayer - i) * 0.1
644                                         if c < 0.4:
645                                                 c = (0.4 + c) / 2
646                                         if c < 0.1:
647                                                 c = 0.1
648                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
649                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
650                                 glCallList(self.gcodeDisplayList + i)
651                                 if time.time() - starttime > 0.1:
652                                         break
653
654                         glDisable(GL_LIGHTING)
655                         glDisable(GL_COLOR_MATERIAL)
656                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
657                         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
658
659                 glColor3f(1.0,1.0,1.0)
660                 glPushMatrix()
661                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
662                 for obj in self.parent.objectList:
663                         if obj.mesh == None:
664                                 continue
665                         
666                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":
667                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
668                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
669                                 #If we want transparent, then first render a solid black model to remove the printer size lines.
670                                 if self.viewMode != "Mixed":
671                                         glDisable(GL_BLEND)
672                                         glColor3f(0.0,0.0,0.0)
673                                         self.drawModel(obj)
674                                         glColor3f(1.0,1.0,1.0)
675                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.
676                                 glDisable(GL_DEPTH_TEST)
677                                 glEnable(GL_LIGHTING)
678                                 glEnable(GL_BLEND)
679                                 glBlendFunc(GL_ONE, GL_ONE)
680                                 glEnable(GL_LIGHTING)
681                                 self.drawModel(obj)
682                                 glEnable(GL_DEPTH_TEST)
683                         elif self.viewMode == "X-Ray":
684                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
685                                 glDisable(GL_LIGHTING)
686                                 glDisable(GL_DEPTH_TEST)
687                                 glEnable(GL_STENCIL_TEST)
688                                 glStencilFunc(GL_ALWAYS, 1, 1)
689                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
690                                 self.drawModel(obj)
691                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
692                                 
693                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
694                                 glStencilFunc(GL_EQUAL, 0, 1)
695                                 glColor(1, 1, 1)
696                                 self.drawModel(obj)
697                                 glStencilFunc(GL_EQUAL, 1, 1)
698                                 glColor(1, 0, 0)
699                                 self.drawModel(obj)
700
701                                 glPushMatrix()
702                                 glLoadIdentity()
703                                 for i in xrange(2, 15, 2):
704                                         glStencilFunc(GL_EQUAL, i, 0xFF);
705                                         glColor(float(i)/10, float(i)/10, float(i)/5)
706                                         glBegin(GL_QUADS)
707                                         glVertex3f(-1000,-1000,-1)
708                                         glVertex3f( 1000,-1000,-1)
709                                         glVertex3f( 1000, 1000,-1)
710                                         glVertex3f(-1000, 1000,-1)
711                                         glEnd()
712                                 for i in xrange(1, 15, 2):
713                                         glStencilFunc(GL_EQUAL, i, 0xFF);
714                                         glColor(float(i)/10, 0, 0)
715                                         glBegin(GL_QUADS)
716                                         glVertex3f(-1000,-1000,-1)
717                                         glVertex3f( 1000,-1000,-1)
718                                         glVertex3f( 1000, 1000,-1)
719                                         glVertex3f(-1000, 1000,-1)
720                                         glEnd()
721                                 glPopMatrix()
722
723                                 glDisable(GL_STENCIL_TEST)
724                                 glEnable(GL_DEPTH_TEST)
725                                 
726                                 #Fix the depth buffer
727                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
728                                 self.drawModel(obj)
729                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
730                         elif self.viewMode == "Normal":
731                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
732                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
733                                 glEnable(GL_LIGHTING)
734                                 self.drawModel(obj)
735
736                         if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
737                                 glEnable(GL_DEPTH_TEST)
738                                 glDisable(GL_LIGHTING)
739                                 glColor3f(1,1,1)
740                                 glPushMatrix()
741                                 modelScale = profile.getProfileSettingFloat('model_scale')
742                                 glScalef(modelScale, modelScale, modelScale)
743                                 opengl.DrawMeshOutline(obj.mesh)
744                                 glPopMatrix()
745                         
746                         if self.drawSteepOverhang:
747                                 glDisable(GL_LIGHTING)
748                                 glColor3f(1,1,1)
749                                 glPushMatrix()
750                                 modelScale = profile.getProfileSettingFloat('model_scale')
751                                 glScalef(modelScale, modelScale, modelScale)
752                                 glCallList(obj.steepDisplayList)
753                                 glPopMatrix()
754                 
755                 glPopMatrix()   
756                 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
757                         glDisable(GL_LIGHTING)
758                         glDisable(GL_DEPTH_TEST)
759                         glDisable(GL_BLEND)
760                         glColor3f(1,0,0)
761                         glBegin(GL_LINES)
762                         for err in self.parent.errorList:
763                                 glVertex3f(err[0].x, err[0].y, err[0].z)
764                                 glVertex3f(err[1].x, err[1].y, err[1].z)
765                         glEnd()
766                         glEnable(GL_DEPTH_TEST)
767
768                 glPushMatrix()
769                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
770                 
771                 #Draw the rotate circle
772                 if self.parent.objectsMaxV != None:
773                         glDisable(GL_LIGHTING)
774                         glDisable(GL_CULL_FACE)
775                         glEnable(GL_BLEND)
776                         glRotate(self.tempRotate + profile.getProfileSettingFloat('model_rotate_base'), 0, 0, 1)
777                         radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
778                         glScalef(radius, radius, 1)
779                         glBegin(GL_TRIANGLE_STRIP)
780                         for i in xrange(0, 64+1):
781                                 f = i if i < 64/2 else 64 - i
782                                 glColor4ub(255,int(f*255/(64/2)),0,255)
783                                 glVertex3f(1.1 * math.cos(i/32.0*math.pi), 1.1 * math.sin(i/32.0*math.pi),0.1)
784                                 glColor4ub(  0,128,0,255)
785                                 glVertex3f(1.3 * math.cos(i/32.0*math.pi), 1.3 * math.sin(i/32.0*math.pi),0.1)
786                         glEnd()
787                         glBegin(GL_TRIANGLES)
788                         glColor4ub(0,0,0,192)
789                         glVertex3f(1, 0.1,0.15)
790                         glVertex3f(1,-0.1,0.15)
791                         glVertex3f(1.4,0,0.15)
792                         glEnd()
793                         glEnable(GL_CULL_FACE)
794                 
795                 glPopMatrix()
796
797                 opengl.DrawMachine(machineSize)
798                 
799                 glFlush()
800         
801         def drawModel(self, obj):
802                 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
803                 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
804                 modelScale = profile.getProfileSettingFloat('model_scale')
805                 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
806                 glPushMatrix()
807                 glRotate(self.tempRotate, 0, 0, 1)
808                 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
809                 for mx in xrange(0, multiX):
810                         for my in xrange(0, multiY):
811                                 glPushMatrix()
812                                 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
813                                 glScalef(modelScale, modelScale, modelScale)
814                                 glCallList(obj.displayList)
815                                 glPopMatrix()
816                 glPopMatrix()
817