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