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