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