chiark / gitweb /
Renamed glTextCtrl to glNumberCtrl to better reflect the function of the class.
[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                 sizer = wx.BoxSizer(wx.VERTICAL)
99                 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
100                 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
101                 self.SetSizer(sizer)
102                 
103                 self.checkReloadFileTimer = wx.Timer(self)
104                 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
105                 self.checkReloadFileTimer.Start(1000)
106
107                 self.rotateToolButton = openglGui.glButton(self.glCanvas, 1, 'Rotate', (0,1), self.OnRotateSelect)
108                 self.scaleToolButton  = openglGui.glButton(self.glCanvas, 2, 'Scale', (0,2), self.OnScaleSelect)
109
110                 self.resetRotationButton = openglGui.glButton(self.glCanvas, 4, 'Reset rotation', (1,1), self.OnRotateReset)
111                 self.layFlatButton       = openglGui.glButton(self.glCanvas, 5, 'Lay flat', (1,2), self.OnLayFlat)
112
113                 self.resetScaleButton    = openglGui.glButton(self.glCanvas, 8, 'Scale reset', (1,1), self.OnScaleReset)
114                 self.scaleMaxButton      = openglGui.glButton(self.glCanvas, 9, 'Scale to machine size', (1,2), self.OnScaleMax)
115
116                 self.openFileButton      = openglGui.glButton(self.glCanvas, 3, 'Load model', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
117                 self.sliceButton         = openglGui.glButton(self.glCanvas, 6, 'Prepare model', (0,-2), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
118                 self.printButton         = openglGui.glButton(self.glCanvas, 7, 'Print model', (0,-1), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
119
120                 extruderCount = int(profile.getPreference('extruder_amount'))
121                 if extruderCount > 1:
122                         openglGui.glButton(self.glCanvas, 3, 'Load dual model', (1,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
123                 if extruderCount > 2:
124                         openglGui.glButton(self.glCanvas, 3, 'Load triple model', (2,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
125                 if extruderCount > 3:
126                         openglGui.glButton(self.glCanvas, 3, 'Load quad model', (3,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
127
128                 self.scaleForm = openglGui.glFrame(self.glCanvas, (1, 3))
129                 openglGui.glGuiLayoutGrid(self.scaleForm)
130                 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
131                 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
132                 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
133                 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
134                 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
135                 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
136                 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
137                 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
138                 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
139                 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
140                 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
141                 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
142                 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
143                 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
144                 self.scaleForm.setHidden(True)
145
146                 self.OnViewChange()
147                 self.returnToModelViewAndUpdateModel()
148
149                 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
150                 self.tool = previewTools.toolNone(self.glCanvas)
151
152         def returnToModelViewAndUpdateModel(self):
153                 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
154                         self.setViewMode('Normal')
155                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
156                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
157                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
158                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
159                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
160                 self.updateModelTransform()
161
162         def OnRotateSelect(self):
163                 if self.rotateToolButton.getSelected():
164                         self.rotateToolButton.setSelected(False)
165                         self.tool = previewTools.toolNone(self.glCanvas)
166                 else:
167                         self.rotateToolButton.setSelected(True)
168                         self.tool = previewTools.toolRotate(self.glCanvas)
169                 self.scaleToolButton.setSelected(False)
170                 self.returnToModelViewAndUpdateModel()
171
172         def OnScaleSelect(self):
173                 self.rotateToolButton.setSelected(False)
174                 if self.scaleToolButton.getSelected():
175                         self.scaleToolButton.setSelected(False)
176                         self.tool = previewTools.toolNone(self.glCanvas)
177                 else:
178                         self.scaleToolButton.setSelected(True)
179                         self.tool = previewTools.toolScale(self.glCanvas)
180                 self.returnToModelViewAndUpdateModel()
181
182         def OnScaleEntry(self, value, axis):
183                 try:
184                         value = float(value)
185                 except:
186                         return
187                 scale = numpy.linalg.norm(self.matrix[::,axis].getA().flatten())
188                 scale = value / scale
189                 if scale == 0:
190                         return
191                 if self.scaleUniform.getValue():
192                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
193                 else:
194                         matrix = [[1.0,0,0], [0, 1.0, 0], [0, 0, 1.0]]
195                         matrix[axis][axis] = scale
196                 self.matrix *= numpy.matrix(matrix, numpy.float64)
197                 self.updateModelTransform()
198
199         def OnScaleEntryMM(self, value, axis):
200                 try:
201                         value = float(value)
202                 except:
203                         return
204                 scale = self.objectsSize[axis]
205                 scale = value / scale
206                 if scale == 0:
207                         return
208                 if self.scaleUniform.getValue():
209                         matrix = [[scale,0,0], [0, scale, 0], [0, 0, scale]]
210                 else:
211                         matrix = [[1,0,0], [0, 1, 0], [0, 0, 1]]
212                         matrix[axis][axis] = scale
213                 self.matrix *= numpy.matrix(matrix, numpy.float64)
214                 self.updateModelTransform()
215
216         def OnMove(self, e = None):
217                 if e is not None:
218                         e.Skip()
219                 x, y = self.glCanvas.ClientToScreenXY(0, 0)
220                 sx, sy = self.glCanvas.GetClientSizeTuple()
221                 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
222
223         def OnScaleMax(self, onlyScaleDown = False):
224                 if self.objectsMinV is None:
225                         return
226                 vMin = self.objectsMinV
227                 vMax = self.objectsMaxV
228                 skirtSize = 3
229                 if profile.getProfileSettingFloat('skirt_line_count') > 0:
230                         skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
231                 scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
232                 scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
233                 scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
234                 scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
235                 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
236                 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
237                 if scale > 1.0 and onlyScaleDown:
238                         return
239                 self.matrix *= numpy.matrix([[scale,0,0],[0,scale,0],[0,0,scale]], numpy.float64)
240                 if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
241                         self.setViewMode('Normal')
242                 self.updateModelTransform()
243
244         def OnRotateReset(self):
245                 x = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
246                 y = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
247                 z = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
248                 self.matrix = numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
249                 for obj in self.objectList:
250                         obj.steepDirty = True
251                 self.updateModelTransform()
252
253         def OnScaleReset(self):
254                 x = 1/numpy.linalg.norm(self.matrix[::,0].getA().flatten())
255                 y = 1/numpy.linalg.norm(self.matrix[::,1].getA().flatten())
256                 z = 1/numpy.linalg.norm(self.matrix[::,2].getA().flatten())
257                 self.matrix *= numpy.matrix([[x,0,0],[0,y,0],[0,0,z]], numpy.float64)
258                 for obj in self.objectList:
259                         obj.steepDirty = True
260                 self.updateModelTransform()
261
262         def OnLayFlat(self):
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[0] * diff[0] + 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                 rad = -math.atan2(dotV[1], dotV[0])
279                 self.matrix *= numpy.matrix([[math.cos(rad), math.sin(rad), 0], [-math.sin(rad), math.cos(rad), 0], [0,0,1]], numpy.float64)
280                 rad = -math.asin(dotMin)
281                 self.matrix *= numpy.matrix([[math.cos(rad), 0, math.sin(rad)], [0,1,0], [-math.sin(rad), 0, math.cos(rad)]], numpy.float64)
282
283
284                 transformedVertexes = (numpy.matrix(self.objectList[0].mesh.vertexes, copy = False) * self.matrix).getA()
285                 minZvertex = transformedVertexes[transformedVertexes.argmin(0)[2]]
286                 dotMin = 1.0
287                 dotV = None
288                 for v in transformedVertexes:
289                         diff = v - minZvertex
290                         len = math.sqrt(diff[1] * diff[1] + diff[2] * diff[2])
291                         if len < 5:
292                                 continue
293                         dot = (diff[2] / len)
294                         if dotMin > dot:
295                                 dotMin = dot
296                                 dotV = diff
297                 if dotV is None:
298                         return
299                 if dotV[1] < 0:
300                         rad = math.asin(dotMin)
301                 else:
302                         rad = -math.asin(dotMin)
303                 self.matrix *= numpy.matrix([[1,0,0], [0, math.cos(rad), math.sin(rad)], [0, -math.sin(rad), math.cos(rad)]], numpy.float64)
304
305                 for obj in self.objectList:
306                         obj.steepDirty = True
307                 self.updateModelTransform()
308
309         def On3DClick(self):
310                 self.glCanvas.yaw = 30
311                 self.glCanvas.pitch = 60
312                 self.glCanvas.zoom = 300
313                 self.glCanvas.view3D = True
314                 self.glCanvas.Refresh()
315
316         def OnTopClick(self):
317                 self.glCanvas.view3D = False
318                 self.glCanvas.zoom = 100
319                 self.glCanvas.offsetX = 0
320                 self.glCanvas.offsetY = 0
321                 self.glCanvas.Refresh()
322
323         def OnLayerNrChange(self, e):
324                 self.glCanvas.Refresh()
325         
326         def setViewMode(self, mode):
327                 if mode == "Normal":
328                         self.normalViewButton.SetValue(True)
329                 if mode == "GCode":
330                         self.gcodeViewButton.SetValue(True)
331                 self.glCanvas.viewMode = mode
332                 wx.CallAfter(self.glCanvas.Refresh)
333         
334         def loadModelFiles(self, filelist, showWarning = False):
335                 while len(filelist) > len(self.objectList):
336                         self.objectList.append(previewObject())
337                 for idx in xrange(len(filelist), len(self.objectList)):
338                         self.objectList[idx].mesh = None
339                         self.objectList[idx].filename = None
340                 for idx in xrange(0, len(filelist)):
341                         obj = self.objectList[idx]
342                         if obj.filename != filelist[idx]:
343                                 obj.fileTime = None
344                                 self.gcodeFileTime = None
345                                 self.logFileTime = None
346                         obj.filename = filelist[idx]
347                 
348                 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
349                 #Do the STL file loading in a background thread so we don't block the UI.
350                 if self.loadThread is not None and self.loadThread.isAlive():
351                         self.loadThread.join()
352                 self.loadThread = threading.Thread(target=self.doFileLoadThread)
353                 self.loadThread.daemon = True
354                 self.loadThread.start()
355                 
356                 if showWarning:
357                         if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
358                                 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
359         
360         def OnCheckReloadFile(self, e):
361                 #Only show the reload popup when the window has focus, because the popup goes over other programs.
362                 if self.GetParent().FindFocus() is None:
363                         return
364                 for obj in self.objectList:
365                         if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
366                                 self.checkReloadFileTimer.Stop()
367                                 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
368                 if wx.TheClipboard.Open():
369                         data = wx.TextDataObject()
370                         if wx.TheClipboard.GetData(data):
371                                 data = data.GetText()
372                                 if re.match('^http://.*/.*$', data):
373                                         if data.endswith(tuple(meshLoader.supportedExtensions())):
374                                                 #Got an url on the clipboard with a model file.
375                                                 pass
376                         wx.TheClipboard.Close()
377         
378         def reloadModelFiles(self, filelist = None):
379                 if filelist is not None:
380                         #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
381                         for idx in xrange(0, len(filelist)):
382                                 if self.objectList[idx].filename != filelist[idx]:
383                                         return False
384                 else:
385                         filelist = []
386                         for idx in xrange(0, len(self.objectList)):
387                                 filelist.append(self.objectList[idx].filename)
388                 self.loadModelFiles(filelist)
389                 return True
390         
391         def doFileLoadThread(self):
392                 for obj in self.objectList:
393                         if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
394                                 obj.fileTime = os.stat(obj.filename).st_mtime
395                                 mesh = meshLoader.loadMesh(obj.filename)
396                                 obj.mesh = mesh
397                                 obj.dirty = True
398                                 obj.steepDirty = True
399                                 self.updateModelTransform()
400                                 self.OnScaleMax(True)
401                                 self.glCanvas.zoom = numpy.max(self.objectsSize) * 3.5
402                                 self.errorList = []
403                                 wx.CallAfter(self.updateToolbar)
404                                 wx.CallAfter(self.glCanvas.Refresh)
405                 
406                 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
407                         self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
408                         gcode = gcodeInterpreter.gcode()
409                         gcode.progressCallback = self.loadProgress
410                         gcode.load(self.gcodeFilename)
411                         self.gcodeDirty = False
412                         self.gcode = gcode
413                         self.gcodeDirty = True
414
415                         errorList = []
416                         for line in open(self.gcodeFilename, "rt"):
417                                 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)
418                                 if res is not None:
419                                         v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
420                                         v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
421                                         errorList.append([v1, v2])
422                         self.errorList = errorList
423
424                         wx.CallAfter(self.updateToolbar)
425                         wx.CallAfter(self.glCanvas.Refresh)
426                 elif not os.path.isfile(self.gcodeFilename):
427                         self.gcode = None
428                 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
429         
430         def loadProgress(self, progress):
431                 pass
432
433         def OnResetAll(self, e = None):
434                 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
435                 profile.setPluginConfig([])
436                 self.updateProfileToControls()
437
438         def ShowWarningPopup(self, text, callback = None):
439                 self.warningPopup.text.SetLabel(text)
440                 self.warningPopup.callback = callback
441                 if callback is None:
442                         self.warningPopup.yesButton.Show(False)
443                         self.warningPopup.noButton.SetLabel('ok')
444                 else:
445                         self.warningPopup.yesButton.Show(True)
446                         self.warningPopup.noButton.SetLabel('no')
447                 self.warningPopup.Fit()
448                 self.warningPopup.Layout()
449                 self.OnMove()
450                 self.warningPopup.Show(True)
451                 self.warningPopup.timer.Start(5000)
452         
453         def OnWarningPopup(self, e):
454                 self.warningPopup.Show(False)
455                 self.warningPopup.timer.Stop()
456                 self.warningPopup.callback()
457
458         def OnHideWarning(self, e):
459                 self.warningPopup.Show(False)
460                 self.warningPopup.timer.Stop()
461
462         def updateToolbar(self):
463                 self.gcodeViewButton.Show(self.gcode is not None)
464                 self.mixedViewButton.Show(self.gcode is not None)
465                 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
466                 self.printButton.setDisabled(self.gcode is None)
467                 if self.gcode is not None:
468                         self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
469                 self.toolbar.Realize()
470                 self.Update()
471         
472         def OnViewChange(self):
473                 if self.normalViewButton.GetValue():
474                         self.glCanvas.viewMode = "Normal"
475                 elif self.transparentViewButton.GetValue():
476                         self.glCanvas.viewMode = "Transparent"
477                 elif self.xrayViewButton.GetValue():
478                         self.glCanvas.viewMode = "X-Ray"
479                 elif self.gcodeViewButton.GetValue():
480                         self.layerSpin.SetValue(self.layerSpin.GetMax())
481                         self.glCanvas.viewMode = "GCode"
482                 elif self.mixedViewButton.GetValue():
483                         self.glCanvas.viewMode = "Mixed"
484                 self.glCanvas.drawBorders = self.showBorderButton.GetValue()
485                 self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
486                 self.updateToolbar()
487                 self.glCanvas.Refresh()
488         
489         def updateModelTransform(self, f=0):
490                 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
491                         return
492
493                 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
494                 for obj in self.objectList:
495                         if obj.mesh is None:
496                                 continue
497                         obj.mesh.matrix = self.matrix
498                         obj.mesh.processMatrix()
499
500                 minV = self.objectList[0].mesh.getMinimum()
501                 maxV = self.objectList[0].mesh.getMaximum()
502                 objectsBoundaryCircleSize = self.objectList[0].mesh.bounderyCircleSize
503                 for obj in self.objectList:
504                         if obj.mesh is None:
505                                 continue
506
507                         minV = numpy.minimum(minV, obj.mesh.getMinimum())
508                         maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
509                         objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.bounderyCircleSize)
510
511                 self.objectsMaxV = maxV
512                 self.objectsMinV = minV
513                 self.objectsSize = self.objectsMaxV - self.objectsMinV
514                 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
515
516                 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
517                 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
518                 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
519                 self.scaleXctrl.setValue(round(scaleX, 2))
520                 self.scaleYctrl.setValue(round(scaleY, 2))
521                 self.scaleZctrl.setValue(round(scaleZ, 2))
522                 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
523                 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
524                 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
525
526                 self.glCanvas.Refresh()
527         
528         def updateProfileToControls(self):
529                 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
530                 self.updateModelTransform()
531                 for obj in self.objectList:
532                         obj.steepDirty = True
533                 self.glCanvas.updateProfileToControls()
534
535 class PreviewGLCanvas(openglGui.glGuiPanel):
536         def __init__(self, parent):
537                 super(PreviewGLCanvas, self).__init__(parent)
538                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
539                 self.parent = parent
540                 self.yaw = 30
541                 self.pitch = 60
542                 self.zoom = 300
543                 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
544                 self.view3D = True
545                 self.gcodeDisplayList = None
546                 self.gcodeDisplayListMade = None
547                 self.gcodeDisplayListCount = 0
548                 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]]
549                 self.oldX = 0
550                 self.oldY = 0
551                 self.dragType = ''
552                 self.tempMatrix = None
553                 self.viewport = None
554
555         def updateProfileToControls(self):
556                 self.objColor[0] = profile.getPreferenceColour('model_colour')
557                 self.objColor[1] = profile.getPreferenceColour('model_colour2')
558                 self.objColor[2] = profile.getPreferenceColour('model_colour3')
559                 self.objColor[3] = profile.getPreferenceColour('model_colour4')
560
561         def OnMouseMotion(self,e):
562                 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
563                         p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
564                         p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
565                         p0 -= self.viewTarget
566                         p1 -= self.viewTarget
567                         if not e.Dragging() or self.dragType != 'tool':
568                                 self.parent.tool.OnMouseMove(p0, p1)
569                 else:
570                         p0 = [0,0,0]
571                         p1 = [1,0,0]
572
573                 if e.Dragging() and e.LeftIsDown():
574                         if self.dragType == '':
575                                 #Define the drag type depending on the cursor position.
576                                 self.dragType = 'viewRotate'
577                                 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
578                                         if self.parent.tool.OnDragStart(p0, p1):
579                                                 self.dragType = 'tool'
580
581                         if self.dragType == 'viewRotate':
582                                 if self.view3D:
583                                         self.yaw += e.GetX() - self.oldX
584                                         self.pitch -= e.GetY() - self.oldY
585                                         if self.pitch > 170:
586                                                 self.pitch = 170
587                                         if self.pitch < 10:
588                                                 self.pitch = 10
589                                 else:
590                                         self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
591                                         self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
592                         elif self.dragType == 'tool':
593                                 self.parent.tool.OnDrag(p0, p1)
594
595                         #Workaround for buggy ATI cards.
596                         size = self.GetSizeTuple()
597                         self.SetSize((size[0]+1, size[1]))
598                         self.SetSize((size[0], size[1]))
599                         self.Refresh()
600                 else:
601                         if self.dragType != '':
602                                 if self.tempMatrix is not None:
603                                         self.parent.matrix *= self.tempMatrix
604                                         self.parent.updateModelTransform()
605                                         self.tempMatrix = None
606                                         for obj in self.parent.objectList:
607                                                 obj.steepDirty = True
608                                 self.parent.tool.OnDragEnd()
609                                 self.dragType = ''
610                 if e.Dragging() and e.RightIsDown():
611                         self.zoom += e.GetY() - self.oldY
612                         if self.zoom < 1:
613                                 self.zoom = 1
614                         if self.zoom > 500:
615                                 self.zoom = 500
616                 self.oldX = e.GetX()
617                 self.oldY = e.GetY()
618
619         def getObjectBoundaryCircle(self):
620                 return self.parent.objectsBoundaryCircleSize
621
622         def getObjectSize(self):
623                 return self.parent.objectsSize
624
625         def getObjectMatrix(self):
626                 return self.parent.matrix
627
628         def getObjectCenterPos(self):
629                 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2]
630
631         def OnMouseWheel(self,e):
632                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
633                 if self.zoom < 1.0:
634                         self.zoom = 1.0
635                 if self.zoom > 500:
636                         self.zoom = 500
637                 self.Refresh()
638
639         def OnPaint(self,e):
640                 opengl.InitGL(self, self.view3D, self.zoom)
641                 if self.view3D:
642                         glTranslate(0,0,-self.zoom)
643                         glTranslate(self.zoom/20.0,0,0)
644                         glRotate(-self.pitch, 1,0,0)
645                         glRotate(self.yaw, 0,0,1)
646
647                         if self.viewMode == "GCode" or self.viewMode == "Mixed":
648                                 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:
649                                         self.viewTarget[2] = self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z
650                         else:
651                                 if self.parent.objectsMaxV is not None:
652                                         self.viewTarget = self.getObjectCenterPos()
653                 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
654
655                 self.viewport = glGetIntegerv(GL_VIEWPORT)
656                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
657                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
658
659                 self.OnDraw()
660
661         def OnDraw(self):
662                 machineSize = self.parent.machineSize
663
664                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
665                         if self.parent.gcodeDirty:
666                                 if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList is None:
667                                         if self.gcodeDisplayList is not None:
668                                                 glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
669                                         self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList))
670                                         self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
671                                 self.parent.gcodeDirty = False
672                                 self.gcodeDisplayListMade = 0
673
674                         if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
675                                 glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
676                                 opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
677                                 glEndList()
678                                 self.gcodeDisplayListMade += 1
679                                 wx.CallAfter(self.Refresh)
680                 
681                 glPushMatrix()
682                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
683                 for obj in self.parent.objectList:
684                         if obj.mesh is None:
685                                 continue
686                         if obj.displayList is None:
687                                 obj.displayList = glGenLists(1)
688                                 obj.steepDisplayList = glGenLists(1)
689                                 obj.outlineDisplayList = glGenLists(1)
690                         if obj.dirty:
691                                 obj.dirty = False
692                                 glNewList(obj.displayList, GL_COMPILE)
693                                 opengl.DrawMesh(obj.mesh)
694                                 glEndList()
695                                 glNewList(obj.outlineDisplayList, GL_COMPILE)
696                                 opengl.DrawMeshOutline(obj.mesh)
697                                 glEndList()
698
699                         if self.viewMode == "Mixed":
700                                 glDisable(GL_BLEND)
701                                 glColor3f(0.0,0.0,0.0)
702                                 self.drawModel(obj.displayList)
703                                 glColor3f(1.0,1.0,1.0)
704                                 glClear(GL_DEPTH_BUFFER_BIT)
705                 
706                 glPopMatrix()
707                 
708                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
709                         glPushMatrix()
710                         if profile.getPreference('machine_center_is_zero') == 'True':
711                                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
712                         glEnable(GL_COLOR_MATERIAL)
713                         glEnable(GL_LIGHTING)
714                         drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
715                         starttime = time.time()
716                         for i in xrange(drawUpToLayer - 1, -1, -1):
717                                 c = 1.0
718                                 if i < self.parent.layerSpin.GetValue():
719                                         c = 0.9 - (drawUpToLayer - i) * 0.1
720                                         if c < 0.4:
721                                                 c = (0.4 + c) / 2
722                                         if c < 0.1:
723                                                 c = 0.1
724                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
725                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
726                                 glCallList(self.gcodeDisplayList + i)
727                                 if time.time() - starttime > 0.1:
728                                         break
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, 0)
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                         glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2]/2)
848                         self.parent.tool.OnDraw()
849                         glPopMatrix()
850
851         def drawModel(self, displayList):
852                 vMin = self.parent.objectsMinV
853                 vMax = self.parent.objectsMaxV
854                 offset = - vMin - (vMax - vMin) / 2
855
856                 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
857
858                 glPushMatrix()
859                 glTranslate(0, 0, self.parent.objectsSize[2]/2)
860                 if self.tempMatrix is not None:
861                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
862                         glMultMatrixf(tempMatrix)
863                 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
864                 glTranslate(offset[0], offset[1], -vMin[2])
865                 glMultMatrixf(matrix)
866                 glCallList(displayList)
867                 glPopMatrix()