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