chiark / gitweb /
ab2742803ef69bcd33c3868885e1b59207c5532a
[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', (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, 'Reset', (1,-2), self.OnScaleReset)
91                 self.scaleMaxButton      = openglGui.glButton(self.glCanvas, 17, 'To max', (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.deselectTool()
327                 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
328                 #Do the STL file loading in a background thread so we don't block the UI.
329                 if self.loadThread is not None and self.loadThread.isAlive():
330                         self.abortLoading = True
331                         self.loadThread.join()
332                 self.abortLoading = False
333                 self.loadThread = threading.Thread(target=self.doFileLoadThread)
334                 self.loadThread.daemon = True
335                 self.loadThread.start()
336                 
337                 if showWarning:
338                         if (self.matrix - numpy.matrix([[1,0,0],[0,1,0],[0,0,1]], numpy.float64)).any() or len(profile.getPluginConfig()) > 0:
339                                 self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
340         
341         def OnCheckReloadFile(self, e):
342                 #Only show the reload popup when the window has focus, because the popup goes over other programs.
343                 if self.GetParent().FindFocus() is None:
344                         return
345                 for obj in self.objectList:
346                         if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
347                                 self.checkReloadFileTimer.Stop()
348                                 self.ShowWarningPopup('File changed, reload?', self.reloadModelFiles)
349                 if wx.TheClipboard.Open():
350                         data = wx.TextDataObject()
351                         if wx.TheClipboard.GetData(data):
352                                 data = data.GetText()
353                                 if re.match('^http://.*/.*$', data):
354                                         if data.endswith(tuple(meshLoader.supportedExtensions())):
355                                                 #Got an url on the clipboard with a model file.
356                                                 pass
357                         wx.TheClipboard.Close()
358         
359         def reloadModelFiles(self, filelist = None):
360                 if filelist is not None:
361                         #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
362                         for idx in xrange(0, len(filelist)):
363                                 if self.objectList[idx].filename != filelist[idx]:
364                                         return False
365                 else:
366                         filelist = []
367                         for idx in xrange(0, len(self.objectList)):
368                                 filelist.append(self.objectList[idx].filename)
369                 self.loadModelFiles(filelist)
370                 return True
371         
372         def doFileLoadThread(self):
373                 for obj in self.objectList:
374                         if obj.filename is not None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
375                                 obj.fileTime = os.stat(obj.filename).st_mtime
376                                 mesh = meshLoader.loadMesh(obj.filename)
377                                 obj.mesh = mesh
378                                 obj.dirty = True
379                                 obj.steepDirty = True
380                                 self.updateModelTransform()
381                                 self.glCanvas.zoom = self.objectsBoundaryCircleSize * 6.0
382                                 self.errorList = []
383                                 wx.CallAfter(self.updateToolbar)
384                                 wx.CallAfter(self.glCanvas.Refresh)
385                 
386                 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
387                         self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
388                         self.gcodeDirty = True
389                         self.gcode = gcodeInterpreter.gcode()
390                         self.gcode.progressCallback = self.loadProgress
391                         self.gcode.load(self.gcodeFilename)
392
393                         errorList = []
394                         for line in open(self.gcodeFilename, "rt"):
395                                 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)
396                                 if res is not None:
397                                         v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
398                                         v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
399                                         errorList.append([v1, v2])
400                         self.errorList = errorList
401
402                         wx.CallAfter(self.updateToolbar)
403                         wx.CallAfter(self.glCanvas.Refresh)
404                 elif not os.path.isfile(self.gcodeFilename):
405                         self.gcode = None
406                 wx.CallAfter(self.checkReloadFileTimer.Start, 1000)
407         
408         def loadProgress(self, progress):
409                 if self.layerSelect.getValue() == self.layerSelect.getMaxValue():
410                         self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
411                         self.layerSelect.setValue(self.layerSelect.getMaxValue())
412                 else:
413                         self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
414                 return self.abortLoading
415
416         def OnResetAll(self, e = None):
417                 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
418                 profile.setPluginConfig([])
419                 self.updateProfileToControls()
420
421         def ShowWarningPopup(self, text, callback = None):
422                 self.warningPopup.text.SetLabel(text)
423                 self.warningPopup.callback = callback
424                 if callback is None:
425                         self.warningPopup.yesButton.Show(False)
426                         self.warningPopup.noButton.SetLabel('ok')
427                 else:
428                         self.warningPopup.yesButton.Show(True)
429                         self.warningPopup.noButton.SetLabel('no')
430                 self.warningPopup.Fit()
431                 self.warningPopup.Layout()
432                 self.OnMove()
433                 self.warningPopup.Show(True)
434                 self.warningPopup.timer.Start(5000)
435         
436         def OnWarningPopup(self, e):
437                 self.warningPopup.Show(False)
438                 self.warningPopup.timer.Stop()
439                 self.warningPopup.callback()
440
441         def OnHideWarning(self, e):
442                 self.warningPopup.Show(False)
443                 self.warningPopup.timer.Stop()
444
445         def updateToolbar(self):
446                 self.printButton.setDisabled(self.gcode is None)
447                 self.rotateToolButton.setHidden(self.glCanvas.viewMode == "GCode")
448                 self.scaleToolButton.setHidden(self.glCanvas.viewMode == "GCode")
449                 self.mirrorToolButton.setHidden(self.glCanvas.viewMode == "GCode")
450                 if self.gcode is not None:
451                         self.layerSelect.setRange(1, len(self.gcode.layerList) - 1)
452                 self.Update()
453         
454         def OnViewChange(self):
455                 selection = self.viewSelection.getValue()
456                 self.glCanvas.drawSteepOverhang = False
457                 self.glCanvas.drawBorders = False
458                 if selection == 0:
459                         self.glCanvas.viewMode = "Normal"
460                 elif selection == 1:
461                         self.glCanvas.viewMode = "Transparent"
462                 elif selection == 2:
463                         self.glCanvas.viewMode = "X-Ray"
464                 elif selection == 3:
465                         self.glCanvas.viewMode = "Normal"
466                         self.glCanvas.drawSteepOverhang = True
467                 elif selection == 4:
468                         self.layerSelect.setValue(self.layerSelect.getMaxValue())
469                         self.glCanvas.viewMode = "GCode"
470                 elif selection == 5:
471                         self.glCanvas.viewMode = "Mixed"
472                 self.layerSelect.setHidden(self.glCanvas.viewMode != "GCode")
473                 self.updateToolbar()
474                 self.deselectTool()
475                 self.glCanvas.Refresh()
476
477         def deselectTool(self):
478                 self.rotateToolButton.setSelected(False)
479                 self.scaleToolButton.setSelected(False)
480                 self.mirrorToolButton.setSelected(False)
481                 self.OnToolSelect()
482
483         def updateModelTransform(self, f=0):
484                 if len(self.objectList) < 1 or self.objectList[0].mesh is None:
485                         return
486
487                 profile.putProfileSetting('model_matrix', ','.join(map(str, list(self.matrix.getA().flatten()))))
488                 for obj in self.objectList:
489                         if obj.mesh is None:
490                                 continue
491                         obj.mesh.matrix = self.matrix
492                         obj.mesh.processMatrix()
493
494                 minV = self.objectList[0].mesh.getMinimum()
495                 maxV = self.objectList[0].mesh.getMaximum()
496                 objectsBoundaryCircleSize = self.objectList[0].mesh.boundaryCircleSize
497                 for obj in self.objectList:
498                         if obj.mesh is None:
499                                 continue
500
501                         minV = numpy.minimum(minV, obj.mesh.getMinimum())
502                         maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
503                         objectsBoundaryCircleSize = max(objectsBoundaryCircleSize, obj.mesh.boundaryCircleSize)
504
505                 self.objectsMaxV = maxV
506                 self.objectsMinV = minV
507                 self.objectsSize = self.objectsMaxV - self.objectsMinV
508                 self.objectsBoundaryCircleSize = objectsBoundaryCircleSize
509
510                 scaleX = numpy.linalg.norm(self.matrix[::,0].getA().flatten())
511                 scaleY = numpy.linalg.norm(self.matrix[::,1].getA().flatten())
512                 scaleZ = numpy.linalg.norm(self.matrix[::,2].getA().flatten())
513                 self.scaleXctrl.setValue(round(scaleX, 2))
514                 self.scaleYctrl.setValue(round(scaleY, 2))
515                 self.scaleZctrl.setValue(round(scaleZ, 2))
516                 self.scaleXmmctrl.setValue(round(self.objectsSize[0], 2))
517                 self.scaleYmmctrl.setValue(round(self.objectsSize[1], 2))
518                 self.scaleZmmctrl.setValue(round(self.objectsSize[2], 2))
519
520                 self.glCanvas.Refresh()
521         
522         def updateProfileToControls(self):
523                 self.matrix = numpy.matrix(numpy.array(profile.getObjectMatrix(), numpy.float64).reshape((3,3,)))
524                 self.updateModelTransform()
525                 for obj in self.objectList:
526                         obj.steepDirty = True
527                 self.glCanvas.updateProfileToControls()
528
529 class PreviewGLCanvas(openglGui.glGuiPanel):
530         def __init__(self, parent):
531                 super(PreviewGLCanvas, self).__init__(parent)
532                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
533                 self.parent = parent
534                 self.yaw = 30
535                 self.pitch = 60
536                 self.zoom = 300
537                 self.viewTarget = [parent.machineCenter.x, parent.machineCenter.y, 0.0]
538                 self.view3D = True
539                 self.gcodeDisplayList = []
540                 self.gcodeQuickDisplayList = []
541                 self.gcodeDisplayListMade = 0
542                 self.gcodeQuickDisplayListMade = 0
543                 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]]
544                 self.oldX = 0
545                 self.oldY = 0
546                 self.dragType = ''
547                 self.tempMatrix = None
548                 self.viewport = None
549
550         def updateProfileToControls(self):
551                 self.objColor[0] = profile.getPreferenceColour('model_colour')
552                 self.objColor[1] = profile.getPreferenceColour('model_colour2')
553                 self.objColor[2] = profile.getPreferenceColour('model_colour3')
554                 self.objColor[3] = profile.getPreferenceColour('model_colour4')
555
556         def OnMouseMotion(self,e):
557                 if self.parent.objectsMaxV is not None and self.viewport is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
558                         p0 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport)
559                         p1 = opengl.unproject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport)
560                         p0 -= self.viewTarget
561                         p1 -= self.viewTarget
562                         if not e.Dragging() or self.dragType != 'tool':
563                                 self.parent.tool.OnMouseMove(p0, p1)
564                 else:
565                         p0 = [0,0,0]
566                         p1 = [1,0,0]
567
568                 if e.Dragging() and e.LeftIsDown():
569                         if self.dragType == '':
570                                 #Define the drag type depending on the cursor position.
571                                 self.dragType = 'viewRotate'
572                                 if self.viewMode != 'GCode' and self.viewMode != 'Mixed':
573                                         if self.parent.tool.OnDragStart(p0, p1):
574                                                 self.dragType = 'tool'
575
576                         if self.dragType == 'viewRotate':
577                                 if self.view3D:
578                                         self.yaw += e.GetX() - self.oldX
579                                         self.pitch -= e.GetY() - self.oldY
580                                         if self.pitch > 170:
581                                                 self.pitch = 170
582                                         if self.pitch < 10:
583                                                 self.pitch = 10
584                                 else:
585                                         self.viewTarget[0] -= float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
586                                         self.viewTarget[1] += float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
587                         elif self.dragType == 'tool':
588                                 self.parent.tool.OnDrag(p0, p1)
589
590                         #Workaround for buggy ATI cards.
591                         size = self.GetSizeTuple()
592                         self.SetSize((size[0]+1, size[1]))
593                         self.SetSize((size[0], size[1]))
594                         self.Refresh()
595                 else:
596                         if self.dragType != '':
597                                 if self.tempMatrix is not None:
598                                         self.parent.matrix *= self.tempMatrix
599                                         self.parent.updateModelTransform()
600                                         self.tempMatrix = None
601                                         for obj in self.parent.objectList:
602                                                 obj.steepDirty = True
603                                 self.parent.tool.OnDragEnd()
604                                 self.dragType = ''
605                 if e.Dragging() and e.RightIsDown():
606                         self.zoom += e.GetY() - self.oldY
607                         if self.zoom < 1:
608                                 self.zoom = 1
609                         if self.zoom > 500:
610                                 self.zoom = 500
611                 self.oldX = e.GetX()
612                 self.oldY = e.GetY()
613
614         def getObjectBoundaryCircle(self):
615                 return self.parent.objectsBoundaryCircleSize
616
617         def getObjectSize(self):
618                 return self.parent.objectsSize
619
620         def getObjectMatrix(self):
621                 return self.parent.matrix
622
623         def getObjectCenterPos(self):
624                 return [self.parent.machineCenter.x, self.parent.machineCenter.y, self.parent.objectsSize[2] / 2 - profile.getProfileSettingFloat('object_sink')]
625
626         def OnMouseWheel(self,e):
627                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
628                 if self.zoom < 1.0:
629                         self.zoom = 1.0
630                 if self.zoom > 500:
631                         self.zoom = 500
632                 self.Refresh()
633
634         def OnPaint(self,e):
635                 opengl.InitGL(self, self.view3D, self.zoom)
636                 if self.view3D:
637                         glTranslate(0,0,-self.zoom)
638                         glTranslate(self.zoom/20.0,0,0)
639                         glRotate(-self.pitch, 1,0,0)
640                         glRotate(self.yaw, 0,0,1)
641
642                         if self.viewMode == "GCode" or self.viewMode == "Mixed":
643                                 n = self.parent.layerSelect.getValue()
644                                 if self.parent.gcode is not None and -1 < n < len(self.parent.gcode.layerList) and len(self.parent.gcode.layerList[n]) > 0:
645                                         self.viewTarget[2] = self.parent.gcode.layerList[n][0].list[-1].z
646                         else:
647                                 if self.parent.objectsMaxV is not None:
648                                         self.viewTarget = self.getObjectCenterPos()
649                 glTranslate(-self.viewTarget[0], -self.viewTarget[1], -self.viewTarget[2])
650
651                 self.viewport = glGetIntegerv(GL_VIEWPORT)
652                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
653                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
654
655                 self.OnDraw()
656
657         def OnDraw(self):
658                 machineSize = self.parent.machineSize
659
660                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
661                         if self.parent.gcodeDirty:
662                                 self.parent.gcodeDirty = False
663                                 self.gcodeDisplayListMade = 0
664                                 self.gcodeQuickDisplayListMade = 0
665
666                         if self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
667                                 gcodeGenStartTime = time.time()
668                                 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeQuickDisplayListMade < len(self.parent.gcode.layerList):
669                                         if len(self.gcodeQuickDisplayList) == self.gcodeQuickDisplayListMade:
670                                                 self.gcodeQuickDisplayList.append(glGenLists(1))
671                                         glNewList(self.gcodeQuickDisplayList[self.gcodeQuickDisplayListMade], GL_COMPILE)
672                                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeQuickDisplayListMade], True)
673                                         glEndList()
674                                         self.gcodeQuickDisplayListMade += 1
675                                 while time.time() - gcodeGenStartTime < 0.1 and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
676                                         if len(self.gcodeDisplayList) == self.gcodeDisplayListMade:
677                                                 self.gcodeDisplayList.append(glGenLists(1))
678                                         glNewList(self.gcodeDisplayList[self.gcodeDisplayListMade], GL_COMPILE)
679                                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade], False)
680                                         glEndList()
681                                         self.gcodeDisplayListMade += 1
682                                 wx.CallAfter(self.Refresh)
683                 
684                 glPushMatrix()
685                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
686                 for obj in self.parent.objectList:
687                         if obj.mesh is None:
688                                 continue
689                         if obj.displayList is None:
690                                 obj.displayList = glGenLists(1)
691                                 obj.steepDisplayList = glGenLists(1)
692                                 obj.outlineDisplayList = glGenLists(1)
693                         if obj.dirty:
694                                 obj.dirty = False
695                                 glNewList(obj.displayList, GL_COMPILE)
696                                 opengl.DrawMesh(obj.mesh, numpy.linalg.det(obj.mesh.matrix) < 0)
697                                 glEndList()
698                                 glNewList(obj.outlineDisplayList, GL_COMPILE)
699                                 opengl.DrawMeshOutline(obj.mesh)
700                                 glEndList()
701
702                         if self.viewMode == "Mixed":
703                                 glDisable(GL_BLEND)
704                                 glColor3f(0.0,0.0,0.0)
705                                 self.drawModel(obj.displayList)
706                                 glColor3f(1.0,1.0,1.0)
707                                 glClear(GL_DEPTH_BUFFER_BIT)
708                 
709                 glPopMatrix()
710                 
711                 if self.parent.gcode is not None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
712                         glPushMatrix()
713                         if profile.getPreference('machine_center_is_zero') == 'True':
714                                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
715                         glEnable(GL_COLOR_MATERIAL)
716                         glEnable(GL_LIGHTING)
717                         drawQuickUpToLayer = min(self.gcodeQuickDisplayListMade, self.parent.layerSelect.getValue() + 1)
718                         drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSelect.getValue() + 1)
719
720                         for i in xrange(drawQuickUpToLayer - 1, -1, -1):
721                                 c = 1.0
722                                 if i < self.parent.layerSelect.getValue():
723                                         c = 0.9 - (drawQuickUpToLayer - i) * 0.1
724                                         if c < 0.4:
725                                                 c = (0.4 + c) / 2
726                                         if c < 0.1:
727                                                 c = 0.1
728                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
729                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
730                                 if self.gcodeDisplayListMade > i and drawUpToLayer - i < 15:
731                                         glCallList(self.gcodeDisplayList[i])
732                                 else:
733                                         glCallList(self.gcodeQuickDisplayList[i])
734
735                         glDisable(GL_LIGHTING)
736                         glDisable(GL_COLOR_MATERIAL)
737                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
738                         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
739                         glPopMatrix()
740
741                 glColor3f(1.0,1.0,1.0)
742                 glPushMatrix()
743                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, -profile.getProfileSettingFloat('object_sink'))
744                 for obj in self.parent.objectList:
745                         if obj.mesh is None:
746                                 continue
747
748                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":
749                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
750                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
751                                 #If we want transparent, then first render a solid black model to remove the printer size lines.
752                                 if self.viewMode != "Mixed":
753                                         glDisable(GL_BLEND)
754                                         glColor3f(0.0,0.0,0.0)
755                                         self.drawModel(obj.displayList)
756                                         glColor3f(1.0,1.0,1.0)
757                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.
758                                 glDisable(GL_DEPTH_TEST)
759                                 glEnable(GL_LIGHTING)
760                                 glEnable(GL_BLEND)
761                                 glBlendFunc(GL_ONE, GL_ONE)
762                                 glEnable(GL_LIGHTING)
763                                 self.drawModel(obj.displayList)
764                                 glEnable(GL_DEPTH_TEST)
765                         elif self.viewMode == "X-Ray":
766                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
767                                 glDisable(GL_LIGHTING)
768                                 glDisable(GL_DEPTH_TEST)
769                                 glEnable(GL_STENCIL_TEST)
770                                 glStencilFunc(GL_ALWAYS, 1, 1)
771                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)
772                                 self.drawModel(obj.displayList)
773                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
774                                 
775                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
776                                 glStencilFunc(GL_EQUAL, 0, 1)
777                                 glColor(1, 1, 1)
778                                 self.drawModel(obj.displayList)
779                                 glStencilFunc(GL_EQUAL, 1, 1)
780                                 glColor(1, 0, 0)
781                                 self.drawModel(obj.displayList)
782
783                                 glPushMatrix()
784                                 glLoadIdentity()
785                                 for i in xrange(2, 15, 2):
786                                         glStencilFunc(GL_EQUAL, i, 0xFF);
787                                         glColor(float(i)/10, float(i)/10, float(i)/5)
788                                         glBegin(GL_QUADS)
789                                         glVertex3f(-1000,-1000,-1)
790                                         glVertex3f( 1000,-1000,-1)
791                                         glVertex3f( 1000, 1000,-1)
792                                         glVertex3f(-1000, 1000,-1)
793                                         glEnd()
794                                 for i in xrange(1, 15, 2):
795                                         glStencilFunc(GL_EQUAL, i, 0xFF);
796                                         glColor(float(i)/10, 0, 0)
797                                         glBegin(GL_QUADS)
798                                         glVertex3f(-1000,-1000,-1)
799                                         glVertex3f( 1000,-1000,-1)
800                                         glVertex3f( 1000, 1000,-1)
801                                         glVertex3f(-1000, 1000,-1)
802                                         glEnd()
803                                 glPopMatrix()
804
805                                 glDisable(GL_STENCIL_TEST)
806                                 glEnable(GL_DEPTH_TEST)
807                                 
808                                 #Fix the depth buffer
809                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
810                                 self.drawModel(obj.displayList)
811                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
812                         elif self.viewMode == "Normal":
813                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
814                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
815                                 glEnable(GL_LIGHTING)
816                                 self.drawModel(obj.displayList)
817
818                         if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
819                                 glEnable(GL_DEPTH_TEST)
820                                 glDisable(GL_LIGHTING)
821                                 glColor3f(1,1,1)
822                                 self.drawModel(obj.outlineDisplayList)
823
824                         if self.drawSteepOverhang:
825                                 if obj.steepDirty:
826                                         obj.steepDirty = False
827                                         glNewList(obj.steepDisplayList, GL_COMPILE)
828                                         opengl.DrawMeshSteep(obj.mesh, self.parent.matrix, 60)
829                                         glEndList()
830                                 glDisable(GL_LIGHTING)
831                                 glColor3f(1,1,1)
832                                 self.drawModel(obj.steepDisplayList)
833
834                 glPopMatrix()   
835                 #if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
836                 #       glDisable(GL_LIGHTING)
837                 #       glDisable(GL_DEPTH_TEST)
838                 #       glDisable(GL_BLEND)
839                 #       glColor3f(1,0,0)
840                 #       glBegin(GL_LINES)
841                 #       for err in self.parent.errorList:
842                 #               glVertex3f(err[0].x, err[0].y, err[0].z)
843                 #               glVertex3f(err[1].x, err[1].y, err[1].z)
844                 #       glEnd()
845                 #       glEnable(GL_DEPTH_TEST)
846
847                 opengl.DrawMachine(machineSize)
848
849                 #Draw the current selected tool
850                 if self.parent.objectsMaxV is not None and self.viewMode != 'GCode' and self.viewMode != 'Mixed':
851                         glPushMatrix()
852                         pos = self.getObjectCenterPos()
853                         glTranslate(pos[0], pos[1], pos[2])
854                         self.parent.tool.OnDraw()
855                         glPopMatrix()
856
857         def drawModel(self, displayList):
858                 vMin = self.parent.objectsMinV
859                 vMax = self.parent.objectsMaxV
860                 if vMin is None:
861                         return
862                 offset = - vMin - (vMax - vMin) / 2
863
864                 matrix = opengl.convert3x3MatrixTo4x4(self.parent.matrix)
865
866                 glPushMatrix()
867                 glTranslate(0, 0, self.parent.objectsSize[2]/2)
868                 if self.tempMatrix is not None:
869                         tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
870                         glMultMatrixf(tempMatrix)
871                 glTranslate(0, 0, -self.parent.objectsSize[2]/2)
872                 glTranslate(offset[0], offset[1], -vMin[2])
873                 glMultMatrixf(matrix)
874                 glCallList(displayList)
875                 glPopMatrix()