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