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