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