chiark / gitweb /
c3378da36bc694d94fde80a44b9ad0169f756f02
[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.buttonSize = 64
44                 self.glButtonList = []
45                 self.objectList = []
46                 self.errorList = []
47                 self.gcode = None
48                 self.objectsMinV = None
49                 self.objectsMaxV = None
50                 self.objectsBoundaryCircleSize = None
51                 self.loadThread = None
52                 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
53                 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
54
55                 self.glCanvas = PreviewGLCanvas(self)
56                 #Create the popup window
57                 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
58                 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
59                 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
60                 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
61                 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
62                 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
63                 self.warningPopup.SetSizer(self.warningPopup.sizer)
64                 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
65                 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
66                 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
67                 self.warningPopup.Fit()
68                 self.warningPopup.Layout()
69                 self.warningPopup.timer = wx.Timer(self)
70                 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
71                 
72                 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
73                 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
74                 parent.Bind(wx.EVT_MOVE, self.OnMove)
75                 parent.Bind(wx.EVT_SIZE, self.OnMove)
76                 
77                 self.toolbar = toolbarUtil.Toolbar(self)
78
79                 group = []
80                 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
81                 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
82                 self.toolbar.AddSeparator()
83
84                 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
85                 self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
86                 self.toolbar.AddSeparator()
87
88                 group = []
89                 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
90                 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
91                 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
92                 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
93                 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
94                 self.toolbar.AddSeparator()
95
96                 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
97                 self.toolbar.AddControl(self.layerSpin)
98                 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
99
100                 self.OnViewChange()
101                 
102                 sizer = wx.BoxSizer(wx.VERTICAL)
103                 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
104                 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
105                 self.SetSizer(sizer)
106                 
107                 self.checkReloadFileTimer = wx.Timer(self)
108                 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
109                 self.checkReloadFileTimer.Start(1000)
110
111                 self.infoToolButton   = openglGui.glButton(self, 0, 0,0, self.OnInfoSelect)
112                 self.rotateToolButton = openglGui.glButton(self, 1, 0,1, self.OnRotateSelect)
113                 self.scaleToolButton  = openglGui.glButton(self, 2, 0,2, self.OnScaleSelect)
114
115                 self.resetRotationButton = openglGui.glButton(self, 4, 1,0, self.OnRotateReset)
116                 self.layFlatButton = openglGui.glButton(self, 5, 2,0, self.OnLayFlat)
117
118                 self.resetScaleButton = openglGui.glButton(self, 8, 1,0, self.OnScaleReset)
119                 self.scaleMaxButton = openglGui.glButton(self, 9, 2,0, self.OnScaleMax)
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(glcanvas.GLCanvas):
447         def __init__(self, parent):
448                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
449                 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
450                 self.parent = parent
451                 self.context = glcanvas.GLContext(self)
452                 wx.EVT_PAINT(self, self.OnPaint)
453                 wx.EVT_SIZE(self, self.OnSize)
454                 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
455                 wx.EVT_MOUSE_EVENTS(self, self.OnMouseEvents)
456                 wx.EVT_MOTION(self, self.OnMouseMotion)
457                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
458                 self.yaw = 30
459                 self.pitch = 60
460                 self.zoom = 300
461                 self.offsetX = 0
462                 self.offsetY = 0
463                 self.view3D = True
464                 self.gcodeDisplayList = None
465                 self.gcodeDisplayListMade = None
466                 self.gcodeDisplayListCount = 0
467                 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]]
468                 self.oldX = 0
469                 self.oldY = 0
470                 self.dragType = ''
471                 self.tempMatrix = None
472                 self.viewport = None
473
474         def updateProfileToControls(self):
475                 self.objColor[0] = profile.getPreferenceColour('model_colour')
476                 self.objColor[1] = profile.getPreferenceColour('model_colour2')
477                 self.objColor[2] = profile.getPreferenceColour('model_colour3')
478                 self.objColor[3] = profile.getPreferenceColour('model_colour4')
479
480         def OnMouseEvents(self,e):
481                 if e.ButtonDown() and e.LeftIsDown():
482                         for ctrl in self.parent.glButtonList:
483                                 if ctrl.OnMouseDown(e.GetX(), e.GetY()):
484                                         return
485
486         def OnMouseMotion(self,e):
487                 self.Refresh()
488                 for ctrl in self.parent.glButtonList:
489                         if ctrl.OnMouseMotion(e.GetX(), e.GetY()):
490                                 return
491                 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
492                         p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
493                         p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
494                         if not e.Dragging() or self.dragType != 'tool':
495                                 self.parent.tool.OnMouseMove(p0, p1)
496
497                 if e.Dragging() and e.LeftIsDown():
498                         if self.dragType == '':
499                                 #Define the drag type depending on the cursor position.
500                                 self.dragType = 'viewRotate'
501                                 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
502                                         if self.parent.tool.OnDragStart(p0, p1):
503                                                 self.dragType = 'tool'
504
505                         if self.dragType == 'viewRotate':
506                                 if self.view3D:
507                                         self.yaw += e.GetX() - self.oldX
508                                         self.pitch -= e.GetY() - self.oldY
509                                         if self.pitch > 170:
510                                                 self.pitch = 170
511                                         if self.pitch < 10:
512                                                 self.pitch = 10
513                                 else:
514                                         self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
515                                         self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
516                         elif self.dragType == 'tool':
517                                 self.parent.tool.OnDrag(p0, p1)
518
519                         #Workaround for buggy ATI cards.
520                         size = self.GetSizeTuple()
521                         self.SetSize((size[0]+1, size[1]))
522                         self.SetSize((size[0], size[1]))
523                         self.Refresh()
524                 else:
525                         if self.dragType != '':
526                                 if self.tempMatrix is not None:
527                                         self.parent.matrix *= self.tempMatrix
528                                         self.parent.updateModelTransform()
529                                         self.tempMatrix = None
530                                 self.parent.tool.OnDragEnd()
531                                 self.dragType = ''
532
533                         self.dragType = ''
534                 if e.Dragging() and e.RightIsDown():
535                         self.zoom += e.GetY() - self.oldY
536                         if self.zoom < 1:
537                                 self.zoom = 1
538                         if self.zoom > 500:
539                                 self.zoom = 500
540                 self.oldX = e.GetX()
541                 self.oldY = e.GetY()
542
543         def getObjectBoundaryCircle(self):
544                 return self.parent.objectsBoundaryCircleSize
545
546         def getObjectSize(self):
547                 return self.parent.objectsSize
548
549         def OnMouseWheel(self,e):
550                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
551                 if self.zoom < 1.0:
552                         self.zoom = 1.0
553                 if self.zoom > 500:
554                         self.zoom = 500
555                 self.Refresh()
556         
557         def OnEraseBackground(self,event):
558                 #Workaround for windows background redraw flicker.
559                 pass
560         
561         def OnSize(self,e):
562                 self.Refresh()
563
564         def OnPaint(self,e):
565                 dc = wx.PaintDC(self)
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 is not 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 is not None:
577                                         glTranslate(0,0,-self.parent.objectsSize[2] / 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 is not None and self.parent.gcodeDirty:
594                         if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
595                                 if self.gcodeDisplayList is not 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 is not 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 is None:
613                                 continue
614                         if obj.displayList is None:
615                                 obj.displayList = glGenLists(1)
616                                 obj.steepDisplayList = glGenLists(1)
617                                 obj.outlineDisplayList = glGenLists(1)
618                         if obj.dirty:
619                                 obj.dirty = False
620                                 glNewList(obj.displayList, GL_COMPILE)
621                                 opengl.DrawMesh(obj.mesh)
622                                 glEndList()
623                                 glNewList(obj.steepDisplayList, GL_COMPILE)
624                                 opengl.DrawMeshSteep(obj.mesh, 60)
625                                 glEndList()
626                                 glNewList(obj.outlineDisplayList, GL_COMPILE)
627                                 opengl.DrawMeshOutline(obj.mesh)
628                                 glEndList()
629                         
630                         if self.viewMode == "Mixed":
631                                 glDisable(GL_BLEND)
632                                 glColor3f(0.0,0.0,0.0)
633                                 self.drawModel(obj.displayList)
634                                 glColor3f(1.0,1.0,1.0)
635                                 glClear(GL_DEPTH_BUFFER_BIT)
636                 
637                 glPopMatrix()
638                 
639                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
640                         glPushMatrix()
641                         if profile.getPreference('machine_center_is_zero') == 'True':
642                                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
643                         glEnable(GL_COLOR_MATERIAL)
644                         glEnable(GL_LIGHTING)
645                         drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
646                         starttime = time.time()
647                         for i in xrange(drawUpToLayer - 1, -1, -1):
648                                 c = 1.0
649                                 if i < self.parent.layerSpin.GetValue():
650                                         c = 0.9 - (drawUpToLayer - i) * 0.1
651                                         if c < 0.4:
652                                                 c = (0.4 + c) / 2
653                                         if c < 0.1:
654                                                 c = 0.1
655                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
656                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
657                                 glCallList(self.gcodeDisplayList + i)
658                                 if time.time() - starttime > 0.1:
659                                         break
660
661                         glDisable(GL_LIGHTING)
662                         glDisable(GL_COLOR_MATERIAL)
663                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
664                         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
665                         glPopMatrix()
666
667                 glColor3f(1.0,1.0,1.0)
668                 glPushMatrix()
669                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
670                 for obj in self.parent.objectList:
671                         if obj.mesh is None:
672                                 continue
673
674                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":
675                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
676                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
677                                 #If we want transparent, then first render a solid black model to remove the printer size lines.
678                                 if self.viewMode != "Mixed":
679                                         glDisable(GL_BLEND)
680                                         glColor3f(0.0,0.0,0.0)
681                                         self.drawModel(obj.displayList)
682                                         glColor3f(1.0,1.0,1.0)
683                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.
684                                 glDisable(GL_DEPTH_TEST)
685                                 glEnable(GL_LIGHTING)
686                                 glEnable(GL_BLEND)
687                                 glBlendFunc(GL_ONE, GL_ONE)
688                                 glEnable(GL_LIGHTING)
689                                 self.drawModel(obj.displayList)
690                                 glEnable(GL_DEPTH_TEST)
691                         elif self.viewMode == "X-Ray":
692                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
693                                 glDisable(GL_LIGHTING)
694                                 glDisable(GL_DEPTH_TEST)
695                                 glEnable(GL_STENCIL_TEST)
696                                 glStencilFunc(GL_ALWAYS, 1, 1)
697                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
698                                 self.drawModel(obj.displayList)
699                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
700                                 
701                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
702                                 glStencilFunc(GL_EQUAL, 0, 1)
703                                 glColor(1, 1, 1)
704                                 self.drawModel(obj.displayList)
705                                 glStencilFunc(GL_EQUAL, 1, 1)
706                                 glColor(1, 0, 0)
707                                 self.drawModel(obj.displayList)
708
709                                 glPushMatrix()
710                                 glLoadIdentity()
711                                 for i in xrange(2, 15, 2):
712                                         glStencilFunc(GL_EQUAL, i, 0xFF);
713                                         glColor(float(i)/10, float(i)/10, float(i)/5)
714                                         glBegin(GL_QUADS)
715                                         glVertex3f(-1000,-1000,-1)
716                                         glVertex3f( 1000,-1000,-1)
717                                         glVertex3f( 1000, 1000,-1)
718                                         glVertex3f(-1000, 1000,-1)
719                                         glEnd()
720                                 for i in xrange(1, 15, 2):
721                                         glStencilFunc(GL_EQUAL, i, 0xFF);
722                                         glColor(float(i)/10, 0, 0)
723                                         glBegin(GL_QUADS)
724                                         glVertex3f(-1000,-1000,-1)
725                                         glVertex3f( 1000,-1000,-1)
726                                         glVertex3f( 1000, 1000,-1)
727                                         glVertex3f(-1000, 1000,-1)
728                                         glEnd()
729                                 glPopMatrix()
730
731                                 glDisable(GL_STENCIL_TEST)
732                                 glEnable(GL_DEPTH_TEST)
733                                 
734                                 #Fix the depth buffer
735                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
736                                 self.drawModel(obj.displayList)
737                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
738                         elif self.viewMode == "Normal":
739                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
740                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
741                                 glEnable(GL_LIGHTING)
742                                 self.drawModel(obj.displayList)
743
744                         if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
745                                 glEnable(GL_DEPTH_TEST)
746                                 glDisable(GL_LIGHTING)
747                                 glColor3f(1,1,1)
748                                 self.drawModel(obj.outlineDisplayList)
749
750                         if self.drawSteepOverhang:
751                                 glDisable(GL_LIGHTING)
752                                 glColor3f(1,1,1)
753                                 self.drawModel(obj.steepDisplayList)
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                 opengl.DrawMachine(machineSize)
769
770                 #Draw the current selected tool
771                 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
772                         glPushMatrix()
773                         glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2]/2)
774                         self.parent.tool.OnDraw()
775                         glPopMatrix()
776
777                 self.drawGui()
778
779                 glFlush()
780
781         def drawGui(self):
782                 glDisable(GL_DEPTH_TEST)
783                 glEnable(GL_BLEND)
784                 glDisable(GL_LIGHTING)
785                 glColor4ub(255,255,255,255)
786
787                 glMatrixMode(GL_PROJECTION)
788                 glLoadIdentity()
789                 size = self.GetSize()
790                 glOrtho(0, size.GetWidth()-1, size.GetHeight()-1, 0, -1000.0, 1000.0)
791                 glMatrixMode(GL_MODELVIEW)
792                 glLoadIdentity()
793
794                 for glButton in self.parent.glButtonList:
795                         glButton.draw()
796
797         def drawModel(self, displayList):
798                 vMin = self.parent.objectsMinV
799                 vMax = self.parent.objectsMaxV
800                 offset = - vMin - (vMax - vMin) / 2
801
802                 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
803
804                 glPushMatrix()
805                 glTranslate(0, 0, self.parent.objectsSize[2]/2)
806                 if self.tempMatrix is not None:
807                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
808                         glMultMatrixf(tempMatrix)
809                 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
810                 glTranslate(offset[0], offset[1], -vMin[2])
811                 glMultMatrixf(matrix)
812                 glCallList(displayList)
813                 glPopMatrix()