chiark / gitweb /
4a25ea31c1705272220aad475b6e5a48bbd07722
[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 import traceback
11 import sys
12
13 from wx import glcanvas
14 import wx
15 import OpenGL
16 OpenGL.ERROR_CHECKING = False
17 from OpenGL.GLU import *
18 from OpenGL.GL import *
19
20 from Cura.util import profile
21 from Cura.util import gcodeInterpreter
22 from Cura.util import meshLoader
23 from Cura.util import util3d
24 from Cura.util import sliceRun
25
26 from Cura.gui.util import opengl
27 from Cura.gui.util import previewTools
28 from Cura.gui.util import openglGui
29
30 class previewObject():
31         def __init__(self):
32                 self.mesh = None
33                 self.filename = None
34                 self.displayList = None
35                 self.dirty = False
36
37 class previewPanel(wx.Panel):
38         def __init__(self, parent):
39                 super(previewPanel, self).__init__(parent,-1)
40                 
41                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
42                 self.SetMinSize((440,320))
43
44                 self.objectList = []
45                 self.errorList = []
46                 self.gcode = None
47                 self.objectsMinV = None
48                 self.objectsMaxV = None
49                 self.objectsBoundaryCircleSize = None
50                 self.loadThread = None
51                 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
52                 self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
53
54                 self.glCanvas = PreviewGLCanvas(self)
55                 #Create the popup window
56                 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
57                 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
58                 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
59                 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
60                 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
61                 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
62                 self.warningPopup.SetSizer(self.warningPopup.sizer)
63                 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
64                 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
65                 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
66                 self.warningPopup.Fit()
67                 self.warningPopup.Layout()
68                 self.warningPopup.timer = wx.Timer(self)
69                 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
70                 
71                 self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
72                 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
73                 parent.Bind(wx.EVT_MOVE, self.OnMove)
74                 parent.Bind(wx.EVT_SIZE, self.OnMove)
75                 
76                 sizer = wx.BoxSizer(wx.VERTICAL)
77                 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
78                 self.SetSizer(sizer)
79                 
80                 self.checkReloadFileTimer = wx.Timer(self)
81                 self.Bind(wx.EVT_TIMER, self.OnCheckReloadFile, self.checkReloadFileTimer)
82                 self.checkReloadFileTimer.Start(1000)
83
84                 group = []
85                 self.rotateToolButton = openglGui.glRadioButton(self.glCanvas, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
86                 self.scaleToolButton  = openglGui.glRadioButton(self.glCanvas, 9, 'Scale', (1,-1), group, self.OnToolSelect)
87                 self.mirrorToolButton  = openglGui.glRadioButton(self.glCanvas, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
88
89                 self.resetRotationButton = openglGui.glButton(self.glCanvas, 12, 'Reset', (0,-2), self.OnRotateReset)
90                 self.layFlatButton       = openglGui.glButton(self.glCanvas, 16, 'Lay flat', (0,-3), self.OnLayFlat)
91
92                 self.resetScaleButton    = openglGui.glButton(self.glCanvas, 13, 'Reset', (1,-2), self.OnScaleReset)
93                 self.scaleMaxButton      = openglGui.glButton(self.glCanvas, 17, 'To max', (1,-3), self.OnScaleMax)
94
95                 self.mirrorXButton       = openglGui.glButton(self.glCanvas, 14, 'Mirror X', (2,-2), lambda : self.OnMirror(0))
96                 self.mirrorYButton       = openglGui.glButton(self.glCanvas, 18, 'Mirror Y', (2,-3), lambda : self.OnMirror(1))
97                 self.mirrorZButton       = openglGui.glButton(self.glCanvas, 22, 'Mirror Z', (2,-4), lambda : self.OnMirror(2))
98
99                 self.openFileButton      = openglGui.glButton(self.glCanvas, 4, 'Load', (0,0), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(1))
100                 self.sliceButton         = openglGui.glButton(self.glCanvas, 5, 'Prepare', (1,0), lambda : self.GetParent().GetParent().GetParent().OnSlice(None))
101                 self.printButton         = openglGui.glButton(self.glCanvas, 6, 'Print', (2,0), lambda : self.GetParent().GetParent().GetParent().OnPrint(None))
102
103                 self.rotateToolButton.setExpandArrow(True)
104                 self.scaleToolButton.setExpandArrow(True)
105                 self.mirrorToolButton.setExpandArrow(True)
106
107                 extruderCount = int(profile.getPreference('extruder_amount'))
108                 if extruderCount > 1:
109                         openglGui.glButton(self.glCanvas, 4, 'Load dual', (0,1), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
110                 if extruderCount > 2:
111                         openglGui.glButton(self.glCanvas, 4, 'Load triple', (0,2), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
112                 if extruderCount > 3:
113                         openglGui.glButton(self.glCanvas, 4, 'Load quad', (0,3), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(4))
114
115                 self.scaleForm = openglGui.glFrame(self.glCanvas, (2, -2))
116                 openglGui.glGuiLayoutGrid(self.scaleForm)
117                 openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
118                 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
119                 openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
120                 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
121                 openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
122                 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
123                 openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
124                 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
125                 openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
126                 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
127                 openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
128                 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
129                 openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
130                 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
131
132                 self.viewSelection = openglGui.glComboButton(self.glCanvas, 'View mode', [7,11,15,19,23], ['Normal', 'Transparent', 'X-Ray', 'Overhang', 'Layers'], (-1,0), self.OnViewChange)
133                 self.layerSelect = openglGui.glSlider(self.glCanvas, 0, 0, 100, (-1,-2), lambda : self.Refresh())
134
135                 self.OnViewChange()
136                 self.OnToolSelect()
137                 self.updateModelTransform()
138
139                 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
140
141         def OnToolSelect(self):
142                 if self.rotateToolButton.getSelected():
143                         self.tool = previewTools.toolRotate(self.glCanvas)
144                 elif self.scaleToolButton.getSelected():
145                         self.tool = previewTools.toolScale(self.glCanvas)
146                 elif self.mirrorToolButton.getSelected():
147                         self.tool = previewTools.toolNone(self.glCanvas)
148                 else:
149                         self.tool = previewTools.toolNone(self.glCanvas)
150                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
151                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
152                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
153                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
154                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
155                 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
156                 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
157                 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
158                 self.updateModelTransform()
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.dirty = True
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 setViewMode(self, mode):
296                 if mode == "Normal":
297                         self.viewSelection.setValue(0)
298                 if mode == "GCode":
299                         self.viewSelection.setValue(4)
300                 wx.CallAfter(self.glCanvas.Refresh)
301
302         def loadModelFiles(self, filelist, showWarning = False):
303                 while len(filelist) > len(self.objectList):
304                         self.objectList.append(previewObject())
305                 for idx in xrange(len(filelist), len(self.objectList)):
306                         self.objectList[idx].mesh = None
307                         self.objectList[idx].filename = None
308                 for idx in xrange(0, len(filelist)):
309                         obj = self.objectList[idx]
310                         if obj.filename != filelist[idx]:
311                                 obj.fileTime = None
312                                 self.gcodeFileTime = None
313                                 self.logFileTime = None
314                                 obj.mesh = None
315                         obj.filename = filelist[idx]
316
317                 self.deselectTool()
318                 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
319                 self.gcode = None
320                 #Do the STL file loading in a background thread so we don't block the UI.
321                 if self.loadThread is not None and self.loadThread.isAlive():
322                         self.abortLoading = True
323                         self.loadThread.join()
324                 self.abortLoading = False
325                 self.loadThread = threading.Thread(target=self.doFileLoadThread)
326                 self.loadThread.daemon = True
327                 self.loadThread.start()
328                 
329                 if showWarning:
330                         if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
331                                 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
332         
333         def OnCheckReloadFile(self, e):
334                 #Only show the reload popup when the window has focus, because the popup goes over other programs.
335                 if self.GetParent().FindFocus() is None:
336                         return
337                 for obj in self.objectList:
338                         if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
339                                 self.checkReloadFileTimer.Stop()
340                                 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
341                 if wx.TheClipboard.Open():
342                         data = wx.TextDataObject()
343                         if wx.TheClipboard.GetData(data):
344                                 data = data.GetText()
345                                 if re.match('^http://.*/.*$', data):
346                                         if data.endswith(tuple(meshLoader.loadSupportedExtensions())):
347                                                 #Got an url on the clipboard with a model file.
348                                                 pass
349                         wx.TheClipboard.Close()
350         
351         def reloadModelFiles(self, filelist = None):
352                 if filelist is not None:
353                         #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
354                         for idx in xrange(0, len(filelist)):
355                                 if self.objectList[idx].filename != filelist[idx]:
356                                         return False
357                 else:
358                         filelist = []
359                         for idx in xrange(0, len(self.objectList)):
360                                 filelist.append(self.objectList[idx].filename)
361                 self.loadModelFiles(filelist)
362                 return True
363         
364         def doFileLoadThread(self):
365                 for obj in self.objectList:
366                         if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
367                                 obj.fileTime = os.stat(obj.filename).st_mtime
368                                 try:
369                                         mesh = meshLoader.loadMeshes(obj.filename)[0]
370                                 except:
371                                         traceback.print_exc(file=sys.stdout)
372                                         wx.CallAfter(self.ShowWarningPopup, 'Failed to load %s' % (obj.filename))
373                                         obj.mesh = None
374                                         obj.filename = None
375                                 else:
376                                         obj.mesh = mesh
377                                 obj.dirty = True
378                                 obj.steepDirty = True
379                                 self.updateModelTransform()
380                                 if self.objectsBoundaryCircleSize is not None:
381                                         self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
382                                 self.errorList = []
383                                 wx.CallAfter(self.updateToolbar)
384                                 wx.CallAfter(self.glCanvas.Refresh)
385                         elif obj.filename is None or not os.path.isfile(obj.filename):
386                                 obj.mesh = None
387                                 obj.filename = None
388
389                 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
390                         self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
391                         self.gcodeDirty = True
392                         self.gcode = gcodeInterpreter.gcode()
393                         self.gcode.progressCallback = self.loadProgress
394                         self.gcode.load(self.gcodeFilename)
395
396                         errorList = []
397                         for line in open(self.gcodeFilename, "rt"):
398                                 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)
399                                 if res is not None:
400                                         v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
401                                         v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
402                                         errorList.append([v1, v2])
403                         self.errorList = errorList
404
405                         wx.CallAfter(self.updateToolbar)
406                         wx.CallAfter(self.glCanvas.Refresh)
407                 elif not os.path.isfile(self.gcodeFilename):
408                         self.gcode = None
409                 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
410         
411         def loadProgress(self, progress):
412                 if self.gcode is None:
413                         return True
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.sliceButton.setDisabled(len(self.objectList) < 1 or self.objectList[0].mesh is None)
452                 self.printButton.setDisabled(self.gcode is None)
453                 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
454                 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
455                 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
456                 if self.gcode is not None:
457                         self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
458                 self.Update()
459         
460         def OnViewChange(self):
461                 selection = self.viewSelection.getValue()
462                 self.glCanvas.drawSteepOverhang = False
463                 self.glCanvas.drawBorders = False
464                 if selection == 0:
465                         self.glCanvas.viewMode = "Normal"
466                 elif selection == 1:
467                         self.glCanvas.viewMode = "Transparent"
468                 elif selection == 2:
469                         self.glCanvas.viewMode = "X-Ray"
470                 elif selection == 3:
471                         self.glCanvas.viewMode = "Normal"
472                         self.glCanvas.drawSteepOverhang = True
473                 elif selection == 4:
474                         self.layerSelect.setValue(self.layerSelect.getMaxValue())
475                         self.glCanvas.viewMode = "GCode"
476                 elif selection == 5:
477                         self.glCanvas.viewMode = "Mixed"
478                 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
479                 self.updateToolbar()
480                 self.deselectTool()
481                 self.glCanvas.Refresh()
482
483         def deselectTool(self):
484                 self.rotateToolButton.setSelected(False)
485                 self.scaleToolButton.setSelected(False)
486                 self.mirrorToolButton.setSelected(False)
487                 self.OnToolSelect()
488
489         def updateModelTransform(self, f=0):
490                 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
491                         return
492
493                 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
494                 for obj in self.objectList:
495                         if obj.mesh is None:
496                                 continue
497                         obj.mesh.matrix = self.matrix
498                         obj.mesh.processMatrix()
499
500                 minV = self.objectList[0].mesh.getMinimum()
501                 maxV = self.objectList[0].mesh.getMaximum()
502                 objectsBoundaryCircleSize = self.objectList[0].mesh.getBoundaryCircle()
503                 for obj in self.objectList:
504                         if obj.mesh is None:
505                                 continue
506
507                         minV = numpy.minimum(minV, obj.mesh.getMinimum())
508                         maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
509                         objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.getBoundaryCircle())
510
511                 self.objectsMaxV = maxV
512                 self.objectsMinV = minV
513                 self.objectsSize = self.objectsMaxV - self.objectsMinV
514                 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
515
516                 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
517                 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
518                 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
519                 self.scaleXctrl.setValue(round(scaleX, 2))
520                 self.scaleYctrl.setValue(round(scaleY, 2))
521                 self.scaleZctrl.setValue(round(scaleZ, 2))
522                 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
523                 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
524                 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
525
526                 self.glCanvas.Refresh()
527         
528         def updateProfileToControls(self):
529                 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
530                 self.updateModelTransform()
531                 for obj in self.objectList:
532                         obj.steepDirty = True
533                 self.glCanvas.updateProfileToControls()
534
535 class PreviewGLCanvas(openglGui.glGuiPanel):
536         def __init__(self, parent):
537                 super(PreviewGLCanvas, self).__init__(parent)
538                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
539                 self.parent = parent
540                 self.yaw = 30
541                 self.pitch = 60
542                 self.zoom = 300
543                 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
544                 self.view3D = True
545                 self.gcodeDisplayList = []
546                 self.gcodeQuickDisplayList = []
547                 self.gcodeDisplayListMade = 0
548                 self.gcodeQuickDisplayListMade = 0
549                 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]]
550                 self.oldX = 0
551                 self.oldY = 0
552                 self.dragType = ''
553                 self.tempMatrix = None
554                 self.viewport = None
555
556         def updateProfileToControls(self):
557                 self.objColor[0] = profile.getPreferenceColour('model_colour')
558                 self.objColor[1] = profile.getPreferenceColour('model_colour2')
559                 self.objColor[2] = profile.getPreferenceColour('model_colour3')
560                 self.objColor[3] = profile.getPreferenceColour('model_colour4')
561
562         def OnMouseMotion(self,e):
563                 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
564                         p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
565                         p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
566                         p0 -= self.viewTarget
567                         p1 -= self.viewTarget
568                         if not e.Dragging() or self.dragType != 'tool':
569                                 self.parent.tool.OnMouseMove(p0, p1)
570                 else:
571                         p0 = [0,0,0]
572                         p1 = [1,0,0]
573
574                 if e.Dragging() and e.LeftIsDown():
575                         if self.dragType == '':
576                                 #Define the drag type depending on the cursor position.
577                                 self.dragType = 'viewRotate'
578                                 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
579                                         if self.parent.tool.OnDragStart(p0, p1):
580                                                 self.dragType = 'tool'
581
582                         if self.dragType == 'viewRotate':
583                                 if self.view3D:
584                                         self.yaw += e.GetX() - self.oldX
585                                         self.pitch -= e.GetY() - self.oldY
586                                         if self.pitch > 170:
587                                                 self.pitch = 170
588                                         if self.pitch < 10:
589                                                 self.pitch = 10
590                                 else:
591                                         self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
592                                         self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
593                         elif self.dragType == 'tool':
594                                 self.parent.tool.OnDrag(p0, p1)
595
596                         #Workaround for buggy ATI cards.
597                         size = self.GetSizeTuple()
598                         self.SetSize((size[0]+1, size[1]))
599                         self.SetSize((size[0], size[1]))
600                         self.Refresh()
601                 else:
602                         if self.dragType != '':
603                                 if self.tempMatrix is not None:
604                                         self.parent.matrix *= self.tempMatrix
605                                         self.parent.updateModelTransform()
606                                         self.tempMatrix = None
607                                         for obj in self.parent.objectList:
608                                                 obj.steepDirty = True
609                                 self.parent.tool.OnDragEnd()
610                                 self.dragType = ''
611                 if e.Dragging() and e.RightIsDown():
612                         self.zoom += e.GetY() - self.oldY
613                         if self.zoom < 1:
614                                 self.zoom = 1
615                         if self.zoom > 500:
616                                 self.zoom = 500
617                 self.oldX = e.GetX()
618                 self.oldY = e.GetY()
619
620         def getObjectBoundaryCircle(self):
621                 return self.parent.objectsBoundaryCircleSize
622
623         def getObjectSize(self):
624                 return self.parent.objectsSize
625
626         def getObjectMatrix(self):
627                 return self.parent.matrix
628
629         def getObjectCenterPos(self):
630                 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
631
632         def OnMouseWheel(self,e):
633                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
634                 if self.zoom < 1.0:
635                         self.zoom = 1.0
636                 if self.zoom > 500:
637                         self.zoom = 500
638                 self.Refresh()
639
640         def OnKeyChar(self, keycode):
641                 if keycode == wx.WXK_UP:
642                         self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() + 1)
643                         self.Refresh()
644                 elif keycode == wx.WXK_DOWN:
645                         self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() - 1)
646                         self.Refresh()
647                 elif keycode == wx.WXK_PAGEUP:
648                         self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() + 10)
649                         self.Refresh()
650                 elif keycode == wx.WXK_PAGEDOWN:
651                         self.parent.layerSelect.setValue(self.parent.layerSelect.getValue() - 10)
652                         self.Refresh()
653
654         def OnPaint(self,e):
655                 opengl.InitGL(self, self.view3D, self.zoom)
656                 if self.view3D:
657                         glTranslate(0,0,-self.zoom)
658                         glTranslate(self.zoom/20.0,0,0)
659                         glRotate(-self.pitch, 1,0,0)
660                         glRotate(self.yaw, 0,0,1)
661
662                         if self.viewMode == "GCode" or self.viewMode == "Mixed":
663                                 n = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue())
664                                 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
665                                         self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
666                         else:
667                                 if self.parent.objectsMaxV is not None:
668                                         self.viewTarget = self.getObjectCenterPos()
669                 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
670
671                 self.viewport = glGetIntegerv(GL_VIEWPORT)
672                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
673                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
674
675                 self.OnDraw()
676
677                 if len(self.parent.objectList) > 0 and self.parent.objectList[0].mesh is None and self.parent.objectList[0].filename is not None:
678                         glDisable(GL_DEPTH_TEST)
679                         glLoadIdentity()
680                         glColor3ub(255,255,255)
681                         glTranslate(0, -3, -10)
682                         opengl.glDrawStringCenter('Loading %s ...' % (os.path.basename(self.parent.objectList[0].filename)))
683
684         def OnDraw(self):
685                 machineSize = self.parent.machineSize
686
687                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
688                         if self.parent.gcodeDirty:
689                                 self.parent.gcodeDirty = False
690                                 self.gcodeDisplayListMade = 0
691                                 self.gcodeQuickDisplayListMade = 0
692
693                         if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
694                                 gcodeGenStartTime = time.time()
695                                 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeQuickDisplayListMade < len(self.parent.gcode.layerList):
696                                         if len(self.gcodeQuickDisplayList) == self.gcodeQuickDisplayListMade:
697                                                 self.gcodeQuickDisplayList.append(glGenLists(1))
698                                         glNewList(self.gcodeQuickDisplayList[self.gcodeQuickDisplayListMade], GL_COMPILE)
699                                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeQuickDisplayListMade], True)
700                                         glEndList()
701                                         self.gcodeQuickDisplayListMade += 1
702                                 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
703                                         if len(self.gcodeDisplayList) == self.gcodeDisplayListMade:
704                                                 self.gcodeDisplayList.append(glGenLists(1))
705                                         glNewList(self.gcodeDisplayList[self.gcodeDisplayListMade], GL_COMPILE)
706                                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade], False)
707                                         glEndList()
708                                         self.gcodeDisplayListMade += 1
709                                 wx.CallAfter(self.Refresh)
710                 
711                 glPushMatrix()
712                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
713                 for obj in self.parent.objectList:
714                         if obj.mesh is None:
715                                 continue
716                         if obj.displayList is None:
717                                 obj.displayList = glGenLists(1)
718                                 obj.steepDisplayList = glGenLists(1)
719                                 obj.outlineDisplayList = glGenLists(1)
720                         if obj.dirty:
721                                 obj.dirty = False
722                                 glNewList(obj.displayList, GL_COMPILE)
723                                 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
724                                 glEndList()
725                                 glNewList(obj.outlineDisplayList, GL_COMPILE)
726                                 opengl.DrawMeshOutline(obj.mesh)
727                                 glEndList()
728
729                         if self.viewMode == "Mixed":
730                                 glDisable(GL_BLEND)
731                                 glColor3f(0.0,0.0,0.0)
732                                 self.drawModel(obj.displayList)
733                                 glColor3f(1.0,1.0,1.0)
734                                 glClear(GL_DEPTH_BUFFER_BIT)
735                 
736                 glPopMatrix()
737                 
738                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
739                         glPushMatrix()
740                         if profile.getPreference('machine_center_is_zero') == 'True':
741                                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
742                         glEnable(GL_COLOR_MATERIAL)
743                         glEnable(GL_LIGHTING)
744                         drawQuickUpToLayer = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue() + 1)
745                         drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
746
747                         for i in xrange(drawQuickUpToLayer - 1, -1, -1):
748                                 c = 1.0
749                                 if i < self.parent.layerSelect.getValue():
750                                         c = 0.9 - (drawQuickUpToLayer - i) * 0.1
751                                         if c < 0.4:
752                                                 c = (0.4 + c) / 2
753                                         if c < 0.1:
754                                                 c = 0.1
755                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
756                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
757                                 if self.gcodeDisplayListMade > i and drawUpToLayer - i < 15:
758                                         glCallList(self.gcodeDisplayList[i])
759                                 else:
760                                         glCallList(self.gcodeQuickDisplayList[i])
761
762                         glDisable(GL_LIGHTING)
763                         glDisable(GL_COLOR_MATERIAL)
764                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
765                         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
766                         glPopMatrix()
767
768                 glColor3f(1.0,1.0,1.0)
769                 glPushMatrix()
770                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
771                 for obj in self.parent.objectList:
772                         if obj.mesh is None:
773                                 continue
774
775                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":
776                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
777                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
778                                 #If we want transparent, then first render a solid black model to remove the printer size lines.
779                                 if self.viewMode != "Mixed":
780                                         glDisable(GL_BLEND)
781                                         glColor3f(0.0,0.0,0.0)
782                                         self.drawModel(obj.displayList)
783                                         glColor3f(1.0,1.0,1.0)
784                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.
785                                 glDisable(GL_DEPTH_TEST)
786                                 glEnable(GL_LIGHTING)
787                                 glEnable(GL_BLEND)
788                                 glBlendFunc(GL_ONE, GL_ONE)
789                                 glEnable(GL_LIGHTING)
790                                 self.drawModel(obj.displayList)
791                                 glEnable(GL_DEPTH_TEST)
792                         elif self.viewMode == "X-Ray":
793                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
794                                 glDisable(GL_LIGHTING)
795                                 glDisable(GL_DEPTH_TEST)
796                                 glEnable(GL_STENCIL_TEST)
797                                 glStencilFunc(GL_ALWAYS, 1, 1)
798                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
799                                 self.drawModel(obj.displayList)
800                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP)
801                                 
802                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
803                                 glStencilFunc(GL_EQUAL, 0, 1)
804                                 glColor(1, 1, 1)
805                                 self.drawModel(obj.displayList)
806                                 glStencilFunc(GL_EQUAL, 1, 1)
807                                 glColor(1, 0, 0)
808                                 self.drawModel(obj.displayList)
809
810                                 glPushMatrix()
811                                 glLoadIdentity()
812                                 for i in xrange(2, 15, 2):
813                                         glStencilFunc(GL_EQUAL, i, 0xFF);
814                                         glColor(float(i)/10, float(i)/10, float(i)/5)
815                                         glBegin(GL_QUADS)
816                                         glVertex3f(-1000,-1000,-1)
817                                         glVertex3f( 1000,-1000,-1)
818                                         glVertex3f( 1000, 1000,-1)
819                                         glVertex3f(-1000, 1000,-1)
820                                         glEnd()
821                                 for i in xrange(1, 15, 2):
822                                         glStencilFunc(GL_EQUAL, i, 0xFF);
823                                         glColor(float(i)/10, 0, 0)
824                                         glBegin(GL_QUADS)
825                                         glVertex3f(-1000,-1000,-1)
826                                         glVertex3f( 1000,-1000,-1)
827                                         glVertex3f( 1000, 1000,-1)
828                                         glVertex3f(-1000, 1000,-1)
829                                         glEnd()
830                                 glPopMatrix()
831
832                                 glDisable(GL_STENCIL_TEST)
833                                 glEnable(GL_DEPTH_TEST)
834                                 
835                                 #Fix the depth buffer
836                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
837                                 self.drawModel(obj.displayList)
838                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
839                         elif self.viewMode == "Normal":
840                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
841                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
842                                 glEnable(GL_LIGHTING)
843                                 self.drawModel(obj.displayList)
844
845                         if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
846                                 glEnable(GL_DEPTH_TEST)
847                                 glDisable(GL_LIGHTING)
848                                 glColor3f(1,1,1)
849                                 self.drawModel(obj.outlineDisplayList)
850
851                         if self.drawSteepOverhang:
852                                 if obj.steepDirty:
853                                         obj.steepDirty = False
854                                         glNewList(obj.steepDisplayList, GL_COMPILE)
855                                         opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
856                                         glEndList()
857                                 glDisable(GL_LIGHTING)
858                                 glColor3f(1,1,1)
859                                 self.drawModel(obj.steepDisplayList)
860
861                 glPopMatrix()   
862                 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
863                 #       glDisable(GL_LIGHTING)
864                 #       glDisable(GL_DEPTH_TEST)
865                 #       glDisable(GL_BLEND)
866                 #       glColor3f(1,0,0)
867                 #       glBegin(GL_LINES)
868                 #       for err in self.parent.errorList:
869                 #               glVertex3f(err[0].x, err[0].y, err[0].z)
870                 #               glVertex3f(err[1].x, err[1].y, err[1].z)
871                 #       glEnd()
872                 #       glEnable(GL_DEPTH_TEST)
873
874                 opengl.DrawMachine(machineSize)
875
876                 #Draw the current selected tool
877                 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
878                         glPushMatrix()
879                         pos = self.getObjectCenterPos()
880                         glTranslate(pos[0], pos[1], pos[2])
881                         self.parent.tool.OnDraw()
882                         glPopMatrix()
883
884         def drawModel(self, displayList):
885                 vMin = self.parent.objectsMinV
886                 vMax = self.parent.objectsMaxV
887                 if vMin is None:
888                         return
889                 offset = - vMin - (vMax - vMin) / 2
890
891                 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
892
893                 glPushMatrix()
894                 glTranslate(0, 0, self.parent.objectsSize[2]/2)
895                 if self.tempMatrix is not None:
896                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
897                         glMultMatrixf(tempMatrix)
898                 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
899                 glTranslate(offset[0], offset[1], -vMin[2])
900                 glMultMatrixf(matrix)
901                 glCallList(displayList)
902                 glPopMatrix()