chiark / gitweb /
Fixed a typo. Changed the GCode rendering so it gives quick results first, and then...
[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', (0,1), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(2))
104                 if extruderCount > 2:
105                         openglGui.glButton(self.glCanvas, 3, 'Load triple model', (0,2), lambda : self.GetParent().GetParent().GetParent()._showModelLoadDialog(3))
106                 if extruderCount > 3:
107                         openglGui.glButton(self.glCanvas, 3, 'Load quad model', (0,3), 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,-2), 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                 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
453                 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
454                 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
455                 if self.gcode is not None:
456                         self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
457                 self.Update()
458         
459         def OnViewChange(self):
460                 selection = self.viewSelection.getValue()
461                 self.glCanvas.drawSteepOverhang = False
462                 self.glCanvas.drawBorders = False
463                 if selection == 0:
464                         self.glCanvas.viewMode = "Normal"
465                 elif selection == 1:
466                         self.glCanvas.viewMode = "Transparent"
467                 elif selection == 2:
468                         self.glCanvas.viewMode = "X-Ray"
469                 elif selection == 3:
470                         self.glCanvas.viewMode = "Normal"
471                         self.glCanvas.drawSteepOverhang = True
472                 elif selection == 4:
473                         self.layerSelect.setValue(self.layerSelect.getMaxValue())
474                         self.glCanvas.viewMode = "GCode"
475                 elif selection == 5:
476                         self.glCanvas.viewMode = "Mixed"
477                 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
478                 self.updateToolbar()
479                 self.rotateToolButton.setSelected(False)
480                 self.scaleToolButton.setSelected(False)
481                 self.mirrorToolButton.setSelected(False)
482                 self.OnToolSelect(False)
483                 self.glCanvas.Refresh()
484         
485         def updateModelTransform(self, f=0):
486                 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
487                         return
488
489                 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
490                 for obj in self.objectList:
491                         if obj.mesh is None:
492                                 continue
493                         obj.mesh.matrix = self.matrix
494                         obj.mesh.processMatrix()
495
496                 minV = self.objectList[0].mesh.getMinimum()
497                 maxV = self.objectList[0].mesh.getMaximum()
498                 objectsBoundaryCircleSize = self.objectList[0].mesh.boundaryCircleSize
499                 for obj in self.objectList:
500                         if obj.mesh is None:
501                                 continue
502
503                         minV = numpy.minimum(minV, obj.mesh.getMinimum())
504                         maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
505                         objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.boundaryCircleSize)
506
507                 self.objectsMaxV = maxV
508                 self.objectsMinV = minV
509                 self.objectsSize = self.objectsMaxV - self.objectsMinV
510                 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
511
512                 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
513                 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
514                 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
515                 self.scaleXctrl.setValue(round(scaleX, 2))
516                 self.scaleYctrl.setValue(round(scaleY, 2))
517                 self.scaleZctrl.setValue(round(scaleZ, 2))
518                 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
519                 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
520                 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
521
522                 self.glCanvas.Refresh()
523         
524         def updateProfileToControls(self):
525                 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
526                 self.updateModelTransform()
527                 for obj in self.objectList:
528                         obj.steepDirty = True
529                 self.glCanvas.updateProfileToControls()
530
531 class PreviewGLCanvas(openglGui.glGuiPanel):
532         def __init__(self, parent):
533                 super(PreviewGLCanvas, self).__init__(parent)
534                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
535                 self.parent = parent
536                 self.yaw = 30
537                 self.pitch = 60
538                 self.zoom = 300
539                 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
540                 self.view3D = True
541                 self.gcodeDisplayList = []
542                 self.gcodeQuickDisplayList = []
543                 self.gcodeDisplayListMade = 0
544                 self.gcodeQuickDisplayListMade = 0
545                 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]]
546                 self.oldX = 0
547                 self.oldY = 0
548                 self.dragType = ''
549                 self.tempMatrix = None
550                 self.viewport = None
551
552         def updateProfileToControls(self):
553                 self.objColor[0] = profile.getPreferenceColour('model_colour')
554                 self.objColor[1] = profile.getPreferenceColour('model_colour2')
555                 self.objColor[2] = profile.getPreferenceColour('model_colour3')
556                 self.objColor[3] = profile.getPreferenceColour('model_colour4')
557
558         def OnMouseMotion(self,e):
559                 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
560                         p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
561                         p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
562                         p0 -= self.viewTarget
563                         p1 -= self.viewTarget
564                         if not e.Dragging() or self.dragType != 'tool':
565                                 self.parent.tool.OnMouseMove(p0, p1)
566                 else:
567                         p0 = [0,0,0]
568                         p1 = [1,0,0]
569
570                 if e.Dragging() and e.LeftIsDown():
571                         if self.dragType == '':
572                                 #Define the drag type depending on the cursor position.
573                                 self.dragType = 'viewRotate'
574                                 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
575                                         if self.parent.tool.OnDragStart(p0, p1):
576                                                 self.dragType = 'tool'
577
578                         if self.dragType == 'viewRotate':
579                                 if self.view3D:
580                                         self.yaw += e.GetX() - self.oldX
581                                         self.pitch -= e.GetY() - self.oldY
582                                         if self.pitch > 170:
583                                                 self.pitch = 170
584                                         if self.pitch < 10:
585                                                 self.pitch = 10
586                                 else:
587                                         self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
588                                         self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
589                         elif self.dragType == 'tool':
590                                 self.parent.tool.OnDrag(p0, p1)
591
592                         #Workaround for buggy ATI cards.
593                         size = self.GetSizeTuple()
594                         self.SetSize((size[0]+1, size[1]))
595                         self.SetSize((size[0], size[1]))
596                         self.Refresh()
597                 else:
598                         if self.dragType != '':
599                                 if self.tempMatrix is not None:
600                                         self.parent.matrix *= self.tempMatrix
601                                         self.parent.updateModelTransform()
602                                         self.tempMatrix = None
603                                         for obj in self.parent.objectList:
604                                                 obj.steepDirty = True
605                                 self.parent.tool.OnDragEnd()
606                                 self.dragType = ''
607                 if e.Dragging() and e.RightIsDown():
608                         self.zoom += e.GetY() - self.oldY
609                         if self.zoom < 1:
610                                 self.zoom = 1
611                         if self.zoom > 500:
612                                 self.zoom = 500
613                 self.oldX = e.GetX()
614                 self.oldY = e.GetY()
615
616         def getObjectBoundaryCircle(self):
617                 return self.parent.objectsBoundaryCircleSize
618
619         def getObjectSize(self):
620                 return self.parent.objectsSize
621
622         def getObjectMatrix(self):
623                 return self.parent.matrix
624
625         def getObjectCenterPos(self):
626                 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
627
628         def OnMouseWheel(self,e):
629                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
630                 if self.zoom < 1.0:
631                         self.zoom = 1.0
632                 if self.zoom > 500:
633                         self.zoom = 500
634                 self.Refresh()
635
636         def OnPaint(self,e):
637                 opengl.InitGL(self, self.view3D, self.zoom)
638                 if self.view3D:
639                         glTranslate(0,0,-self.zoom)
640                         glTranslate(self.zoom/20.0,0,0)
641                         glRotate(-self.pitch, 1,0,0)
642                         glRotate(self.yaw, 0,0,1)
643
644                         if self.viewMode == "GCode" or self.viewMode == "Mixed":
645                                 n = self.parent.layerSelect.getValue()
646                                 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
647                                         self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
648                         else:
649                                 if self.parent.objectsMaxV is not None:
650                                         self.viewTarget = self.getObjectCenterPos()
651                 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
652
653                 self.viewport = glGetIntegerv(GL_VIEWPORT)
654                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
655                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
656
657                 self.OnDraw()
658
659         def OnDraw(self):
660                 machineSize = self.parent.machineSize
661
662                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
663                         if self.parent.gcodeDirty:
664                                 self.parent.gcodeDirty = False
665                                 self.gcodeDisplayListMade = 0
666                                 self.gcodeQuickDisplayListMade = 0
667
668                         if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
669                                 gcodeGenStartTime = time.time()
670                                 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeQuickDisplayListMade < len(self.parent.gcode.layerList):
671                                         if len(self.gcodeQuickDisplayList) == self.gcodeQuickDisplayListMade:
672                                                 self.gcodeQuickDisplayList.append(glGenLists(1))
673                                         glNewList(self.gcodeQuickDisplayList[self.gcodeQuickDisplayListMade], GL_COMPILE)
674                                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeQuickDisplayListMade], True)
675                                         glEndList()
676                                         self.gcodeQuickDisplayListMade += 1
677                                 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
678                                         if len(self.gcodeDisplayList) == self.gcodeDisplayListMade:
679                                                 self.gcodeDisplayList.append(glGenLists(1))
680                                         glNewList(self.gcodeDisplayList[self.gcodeDisplayListMade], GL_COMPILE)
681                                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade], False)
682                                         glEndList()
683                                         self.gcodeDisplayListMade += 1
684                                 wx.CallAfter(self.Refresh)
685                 
686                 glPushMatrix()
687                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
688                 for obj in self.parent.objectList:
689                         if obj.mesh is None:
690                                 continue
691                         if obj.displayList is None:
692                                 obj.displayList = glGenLists(1)
693                                 obj.steepDisplayList = glGenLists(1)
694                                 obj.outlineDisplayList = glGenLists(1)
695                         if obj.dirty:
696                                 obj.dirty = False
697                                 glNewList(obj.displayList, GL_COMPILE)
698                                 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
699                                 glEndList()
700                                 glNewList(obj.outlineDisplayList, GL_COMPILE)
701                                 opengl.DrawMeshOutline(obj.mesh)
702                                 glEndList()
703
704                         if self.viewMode == "Mixed":
705                                 glDisable(GL_BLEND)
706                                 glColor3f(0.0,0.0,0.0)
707                                 self.drawModel(obj.displayList)
708                                 glColor3f(1.0,1.0,1.0)
709                                 glClear(GL_DEPTH_BUFFER_BIT)
710                 
711                 glPopMatrix()
712                 
713                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
714                         glPushMatrix()
715                         if profile.getPreference('machine_center_is_zero') == 'True':
716                                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
717                         glEnable(GL_COLOR_MATERIAL)
718                         glEnable(GL_LIGHTING)
719                         drawUpToLayer = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue() + 1)
720                         starttime = time.time()
721                         for i in xrange(drawUpToLayer - 1, -1, -1):
722                                 c = 1.0
723                                 if i < self.parent.layerSelect.getValue():
724                                         c = 0.9 - (drawUpToLayer - i) * 0.1
725                                         if c < 0.4:
726                                                 c = (0.4 + c) / 2
727                                         if c < 0.1:
728                                                 c = 0.1
729                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
730                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
731                                 if self.gcodeDisplayListMade > i and drawUpToLayer - i < 15:
732                                         glCallList(self.gcodeDisplayList[i])
733                                 else:
734                                         glCallList(self.gcodeQuickDisplayList[i])
735
736                         glDisable(GL_LIGHTING)
737                         glDisable(GL_COLOR_MATERIAL)
738                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
739                         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
740                         glPopMatrix()
741
742                 glColor3f(1.0,1.0,1.0)
743                 glPushMatrix()
744                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
745                 for obj in self.parent.objectList:
746                         if obj.mesh is None:
747                                 continue
748
749                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":
750                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
751                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
752                                 #If we want transparent, then first render a solid black model to remove the printer size lines.
753                                 if self.viewMode != "Mixed":
754                                         glDisable(GL_BLEND)
755                                         glColor3f(0.0,0.0,0.0)
756                                         self.drawModel(obj.displayList)
757                                         glColor3f(1.0,1.0,1.0)
758                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.
759                                 glDisable(GL_DEPTH_TEST)
760                                 glEnable(GL_LIGHTING)
761                                 glEnable(GL_BLEND)
762                                 glBlendFunc(GL_ONE, GL_ONE)
763                                 glEnable(GL_LIGHTING)
764                                 self.drawModel(obj.displayList)
765                                 glEnable(GL_DEPTH_TEST)
766                         elif self.viewMode == "X-Ray":
767                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
768                                 glDisable(GL_LIGHTING)
769                                 glDisable(GL_DEPTH_TEST)
770                                 glEnable(GL_STENCIL_TEST)
771                                 glStencilFunc(GL_ALWAYS, 1, 1)
772                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
773                                 self.drawModel(obj.displayList)
774                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
775                                 
776                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
777                                 glStencilFunc(GL_EQUAL, 0, 1)
778                                 glColor(1, 1, 1)
779                                 self.drawModel(obj.displayList)
780                                 glStencilFunc(GL_EQUAL, 1, 1)
781                                 glColor(1, 0, 0)
782                                 self.drawModel(obj.displayList)
783
784                                 glPushMatrix()
785                                 glLoadIdentity()
786                                 for i in xrange(2, 15, 2):
787                                         glStencilFunc(GL_EQUAL, i, 0xFF);
788                                         glColor(float(i)/10, float(i)/10, float(i)/5)
789                                         glBegin(GL_QUADS)
790                                         glVertex3f(-1000,-1000,-1)
791                                         glVertex3f( 1000,-1000,-1)
792                                         glVertex3f( 1000, 1000,-1)
793                                         glVertex3f(-1000, 1000,-1)
794                                         glEnd()
795                                 for i in xrange(1, 15, 2):
796                                         glStencilFunc(GL_EQUAL, i, 0xFF);
797                                         glColor(float(i)/10, 0, 0)
798                                         glBegin(GL_QUADS)
799                                         glVertex3f(-1000,-1000,-1)
800                                         glVertex3f( 1000,-1000,-1)
801                                         glVertex3f( 1000, 1000,-1)
802                                         glVertex3f(-1000, 1000,-1)
803                                         glEnd()
804                                 glPopMatrix()
805
806                                 glDisable(GL_STENCIL_TEST)
807                                 glEnable(GL_DEPTH_TEST)
808                                 
809                                 #Fix the depth buffer
810                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
811                                 self.drawModel(obj.displayList)
812                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
813                         elif self.viewMode == "Normal":
814                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
815                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
816                                 glEnable(GL_LIGHTING)
817                                 self.drawModel(obj.displayList)
818
819                         if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
820                                 glEnable(GL_DEPTH_TEST)
821                                 glDisable(GL_LIGHTING)
822                                 glColor3f(1,1,1)
823                                 self.drawModel(obj.outlineDisplayList)
824
825                         if self.drawSteepOverhang:
826                                 if obj.steepDirty:
827                                         obj.steepDirty = False
828                                         glNewList(obj.steepDisplayList, GL_COMPILE)
829                                         opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
830                                         glEndList()
831                                 glDisable(GL_LIGHTING)
832                                 glColor3f(1,1,1)
833                                 self.drawModel(obj.steepDisplayList)
834
835                 glPopMatrix()   
836                 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
837                 #       glDisable(GL_LIGHTING)
838                 #       glDisable(GL_DEPTH_TEST)
839                 #       glDisable(GL_BLEND)
840                 #       glColor3f(1,0,0)
841                 #       glBegin(GL_LINES)
842                 #       for err in self.parent.errorList:
843                 #               glVertex3f(err[0].x, err[0].y, err[0].z)
844                 #               glVertex3f(err[1].x, err[1].y, err[1].z)
845                 #       glEnd()
846                 #       glEnable(GL_DEPTH_TEST)
847
848                 opengl.DrawMachine(machineSize)
849
850                 #Draw the current selected tool
851                 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
852                         glPushMatrix()
853                         pos = self.getObjectCenterPos()
854                         glTranslate(pos[0], pos[1], pos[2])
855                         self.parent.tool.OnDraw()
856                         glPopMatrix()
857
858         def drawModel(self, displayList):
859                 vMin = self.parent.objectsMinV
860                 vMax = self.parent.objectsMaxV
861                 offset = - vMin - (vMax - vMin) / 2
862
863                 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
864
865                 glPushMatrix()
866                 glTranslate(0, 0, self.parent.objectsSize[2]/2)
867                 if self.tempMatrix is not None:
868                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
869                         glMultMatrixf(tempMatrix)
870                 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
871                 glTranslate(offset[0], offset[1], -vMin[2])
872                 glMultMatrixf(matrix)
873                 glCallList(displayList)
874                 glPopMatrix()