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