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