chiark / gitweb /
Fix dae matrix and instance_node support. Which makes sketchup exports working.
[cura.git] / Cura / gui / preview3d.py
1 from __future__ import division\r
2 \r
3 import sys, math, threading, re, time, os\r
4 import numpy\r
5 \r
6 from wx import glcanvas\r
7 import wx\r
8 try:\r
9         import OpenGL\r
10         OpenGL.ERROR_CHECKING = False\r
11         from OpenGL.GLU import *\r
12         from OpenGL.GL import *\r
13         hasOpenGLlibs = True\r
14 except:\r
15         print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
16         hasOpenGLlibs = False\r
17 \r
18 from gui import opengl\r
19 from gui import toolbarUtil\r
20 \r
21 from util import profile\r
22 from util import gcodeInterpreter\r
23 from util import meshLoader\r
24 from util import util3d\r
25 from util import sliceRun\r
26 \r
27 class previewObject():\r
28         def __init__(self):\r
29                 self.mesh = None\r
30                 self.filename = None\r
31                 self.displayList = None\r
32                 self.dirty = False\r
33 \r
34 class previewPanel(wx.Panel):\r
35         def __init__(self, parent):\r
36                 super(previewPanel, self).__init__(parent,-1)\r
37                 \r
38                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))\r
39                 self.SetMinSize((440,320))\r
40                 \r
41                 self.objectList = []\r
42                 self.errorList = []\r
43                 self.gcode = None\r
44                 self.objectsMinV = None\r
45                 self.objectsMaxV = None\r
46                 self.loadThread = None\r
47                 self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))\r
48                 self.machineCenter = util3d.Vector3(float(profile.getProfileSetting('machine_center_x')), float(profile.getProfileSetting('machine_center_y')), 0)\r
49 \r
50                 self.glCanvas = PreviewGLCanvas(self)\r
51                 #Create the popup window\r
52                 self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)\r
53                 self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))\r
54                 self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')\r
55                 self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)\r
56                 self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)\r
57                 self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)\r
58                 self.warningPopup.SetSizer(self.warningPopup.sizer)\r
59                 self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)\r
60                 self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)\r
61                 self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)\r
62                 self.warningPopup.Fit()\r
63                 self.warningPopup.Layout()\r
64                 self.warningPopup.timer = wx.Timer(self)\r
65                 self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)\r
66                 \r
67                 self.Bind(wx.EVT_BUTTON, self.OnResetAll, self.warningPopup.yesButton)\r
68                 self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)\r
69                 parent.Bind(wx.EVT_MOVE, self.OnMove)\r
70                 parent.Bind(wx.EVT_SIZE, self.OnMove)\r
71                 \r
72                 self.toolbar = toolbarUtil.Toolbar(self)\r
73 \r
74                 group = []\r
75                 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)\r
76                 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)\r
77                 self.toolbar.AddSeparator()\r
78 \r
79                 self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)\r
80                 self.toolbar.AddSeparator()\r
81 \r
82                 group = []\r
83                 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)\r
84                 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)\r
85                 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)\r
86                 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)\r
87                 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)\r
88                 self.toolbar.AddSeparator()\r
89 \r
90                 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)\r
91                 self.toolbar.AddControl(self.layerSpin)\r
92                 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)\r
93 \r
94                 self.toolbar2 = toolbarUtil.Toolbar(self)\r
95 \r
96                 # Mirror\r
97                 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.updateModelTransform)\r
98                 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.updateModelTransform)\r
99                 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.updateModelTransform)\r
100                 self.toolbar2.AddSeparator()\r
101 \r
102                 # Swap\r
103                 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.updateModelTransform)\r
104                 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.updateModelTransform)\r
105                 self.toolbar2.AddSeparator()\r
106 \r
107                 # Scale\r
108                 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')\r
109                 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))\r
110                 self.toolbar2.AddControl(self.scale)\r
111                 self.scale.Bind(wx.EVT_TEXT, self.OnScale)\r
112                 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')\r
113 \r
114                 self.toolbar2.AddSeparator()\r
115 \r
116                 # Multiply\r
117                 #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')\r
118                 #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')\r
119                 #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')\r
120                 #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')\r
121                 #self.toolbar2.AddSeparator()\r
122 \r
123                 # Rotate\r
124                 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')\r
125                 self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)\r
126                 self.rotate.SetRange(0, 360)\r
127                 self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)\r
128                 self.toolbar2.AddControl(self.rotate)\r
129 \r
130                 self.toolbar2.Realize()\r
131                 self.OnViewChange()\r
132                 \r
133                 sizer = wx.BoxSizer(wx.VERTICAL)\r
134                 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)\r
135                 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)\r
136                 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)\r
137                 self.SetSizer(sizer)\r
138         \r
139         def OnMove(self, e):\r
140                 e.Skip()\r
141                 x, y = self.glCanvas.ClientToScreenXY(0, 0)\r
142                 sx, sy = self.glCanvas.GetClientSizeTuple()\r
143                 self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))\r
144         \r
145         def OnMulXAddClick(self, e):\r
146                 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))\r
147                 self.glCanvas.Refresh()\r
148 \r
149         def OnMulXSubClick(self, e):\r
150                 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))\r
151                 self.glCanvas.Refresh()\r
152 \r
153         def OnMulYAddClick(self, e):\r
154                 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))\r
155                 self.glCanvas.Refresh()\r
156 \r
157         def OnMulYSubClick(self, e):\r
158                 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))\r
159                 self.glCanvas.Refresh()\r
160 \r
161         def OnScaleReset(self, e):\r
162                 self.scale.SetValue('1.0')\r
163                 self.OnScale(None)\r
164 \r
165         def OnScale(self, e):\r
166                 scale = 1.0\r
167                 if self.scale.GetValue() != '':\r
168                         scale = self.scale.GetValue()\r
169                 profile.putProfileSetting('model_scale', scale)\r
170                 self.glCanvas.Refresh()\r
171         \r
172         def OnScaleMax(self, e):\r
173                 if self.objectsMinV == None:\r
174                         return\r
175                 vMin = self.objectsMinV\r
176                 vMax = self.objectsMaxV\r
177                 scaleX1 = (self.machineSize.x - self.machineCenter.x) / ((vMax[0] - vMin[0]) / 2)\r
178                 scaleY1 = (self.machineSize.y - self.machineCenter.y) / ((vMax[1] - vMin[1]) / 2)\r
179                 scaleX2 = (self.machineCenter.x) / ((vMax[0] - vMin[0]) / 2)\r
180                 scaleY2 = (self.machineCenter.y) / ((vMax[1] - vMin[1]) / 2)\r
181                 scaleZ = self.machineSize.z / (vMax[2] - vMin[2])\r
182                 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)\r
183                 self.scale.SetValue(str(scale))\r
184                 profile.putProfileSetting('model_scale', self.scale.GetValue())\r
185                 self.glCanvas.Refresh()\r
186 \r
187         def OnRotateReset(self, e):\r
188                 self.rotate.SetValue(0)\r
189                 self.OnRotate(None)\r
190 \r
191         def OnRotate(self, e):\r
192                 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())\r
193                 self.updateModelTransform()\r
194 \r
195         def On3DClick(self):\r
196                 self.glCanvas.yaw = 30\r
197                 self.glCanvas.pitch = 60\r
198                 self.glCanvas.zoom = 300\r
199                 self.glCanvas.view3D = True\r
200                 self.glCanvas.Refresh()\r
201 \r
202         def OnTopClick(self):\r
203                 self.glCanvas.view3D = False\r
204                 self.glCanvas.zoom = 100\r
205                 self.glCanvas.offsetX = 0\r
206                 self.glCanvas.offsetY = 0\r
207                 self.glCanvas.Refresh()\r
208 \r
209         def OnLayerNrChange(self, e):\r
210                 self.glCanvas.Refresh()\r
211 \r
212         def updateCenterX(self):\r
213                 self.machineCenter.x = profile.getProfileSettingFloat('machine_center_x')\r
214                 self.glCanvas.Refresh()\r
215 \r
216         def updateCenterY(self):\r
217                 self.machineCenter.y = profile.getProfileSettingFloat('machine_center_y')\r
218                 self.glCanvas.Refresh()\r
219         \r
220         def setViewMode(self, mode):\r
221                 if mode == "Normal":\r
222                         self.normalViewButton.SetValue(True)\r
223                 if mode == "GCode":\r
224                         self.gcodeViewButton.SetValue(True)\r
225                 self.glCanvas.viewMode = mode\r
226                 wx.CallAfter(self.glCanvas.Refresh)\r
227         \r
228         def loadModelFiles(self, filelist, showWarning = False):\r
229                 while len(filelist) > len(self.objectList):\r
230                         self.objectList.append(previewObject())\r
231                 for idx in xrange(len(filelist), len(self.objectList)):\r
232                         self.objectList[idx].mesh = None\r
233                         self.objectList[idx].filename = None\r
234                 for idx in xrange(0, len(filelist)):\r
235                         obj = self.objectList[idx]\r
236                         if obj.filename != filelist[idx]:\r
237                                 obj.fileTime = None\r
238                                 self.gcodeFileTime = None\r
239                                 self.logFileTime = None\r
240                         obj.filename = filelist[idx]\r
241                 \r
242                 self.gcodeFilename = sliceRun.getExportFilename(filelist[0])\r
243                 #Do the STL file loading in a background thread so we don't block the UI.\r
244                 if self.loadThread != None and self.loadThread.isAlive():\r
245                         self.loadThread.join()\r
246                 self.loadThread = threading.Thread(target=self.doFileLoadThread)\r
247                 self.loadThread.daemon = True\r
248                 self.loadThread.start()\r
249                 \r
250                 if showWarning:\r
251                         if profile.getProfileSettingFloat('model_scale') != 1.0 or profile.getProfileSettingFloat('model_rotate_base') != 0 or profile.getProfileSetting('flip_x') != 'False' or profile.getProfileSetting('flip_y') != 'False' or profile.getProfileSetting('flip_z') != 'False' or profile.getProfileSetting('swap_xz') != 'False' or profile.getProfileSetting('swap_yz') != 'False':\r
252                                 self.warningPopup.Show(True)\r
253                                 self.warningPopup.timer.Start(5000)\r
254         \r
255         def loadReModelFiles(self, filelist):\r
256                 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)\r
257                 for idx in xrange(0, len(filelist)):\r
258                         if self.objectList[idx].filename != filelist[idx]:\r
259                                 return False\r
260                 self.loadModelFiles(filelist)\r
261                 return True\r
262         \r
263         def doFileLoadThread(self):\r
264                 for obj in self.objectList:\r
265                         if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:\r
266                                 obj.ileTime = os.stat(obj.filename).st_mtime\r
267                                 mesh = meshLoader.loadMesh(obj.filename)\r
268                                 obj.dirty = False\r
269                                 obj.mesh = mesh\r
270                                 self.updateModelTransform()\r
271                                 scale = profile.getProfileSettingFloat('model_scale')\r
272                                 size = (self.objectsMaxV - self.objectsMinV) * scale\r
273                                 if size[0] > self.machineSize.x or size[1] > self.machineSize.y or size[2] > self.machineSize.z:\r
274                                         self.OnScaleMax(None)\r
275                                 self.glCanvas.zoom = numpy.max(size) * 2.5\r
276                                 self.errorList = []\r
277                                 wx.CallAfter(self.updateToolbar)\r
278                                 wx.CallAfter(self.glCanvas.Refresh)\r
279                 \r
280                 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:\r
281                         self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime\r
282                         gcode = gcodeInterpreter.gcode()\r
283                         gcode.progressCallback = self.loadProgress\r
284                         gcode.load(self.gcodeFilename)\r
285                         self.gcodeDirty = False\r
286                         self.gcode = gcode\r
287                         self.gcodeDirty = True\r
288 \r
289                         errorList = []\r
290                         for line in open(self.gcodeFilename, "rt"):\r
291                                 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)\r
292                                 if res != None:\r
293                                         v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))\r
294                                         v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))\r
295                                         errorList.append([v1, v2])\r
296                         self.errorList = errorList\r
297 \r
298                         wx.CallAfter(self.updateToolbar)\r
299                         wx.CallAfter(self.glCanvas.Refresh)\r
300                 elif not os.path.isfile(self.gcodeFilename):\r
301                         self.gcode = None\r
302         \r
303         def loadProgress(self, progress):\r
304                 pass\r
305 \r
306         def OnResetAll(self, e):\r
307                 profile.putProfileSetting('model_scale', '1.0')\r
308                 profile.putProfileSetting('model_rotate_base', '0')\r
309                 profile.putProfileSetting('flip_x', 'False')\r
310                 profile.putProfileSetting('flip_y', 'False')\r
311                 profile.putProfileSetting('flip_z', 'False')\r
312                 profile.putProfileSetting('swap_xz', 'False')\r
313                 profile.putProfileSetting('swap_yz', 'False')\r
314                 self.updateProfileToControls()\r
315                 self.warningPopup.Show(False)\r
316                 self.warningPopup.timer.Stop()\r
317 \r
318         def OnHideWarning(self, e):\r
319                 self.warningPopup.Show(False)\r
320                 self.warningPopup.timer.Stop()\r
321 \r
322         def updateToolbar(self):\r
323                 self.gcodeViewButton.Show(self.gcode != None)\r
324                 self.mixedViewButton.Show(self.gcode != None)\r
325                 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")\r
326                 if self.gcode != None:\r
327                         self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)\r
328                 self.toolbar.Realize()\r
329                 self.Update()\r
330         \r
331         def OnViewChange(self):\r
332                 if self.normalViewButton.GetValue():\r
333                         self.glCanvas.viewMode = "Normal"\r
334                 elif self.transparentViewButton.GetValue():\r
335                         self.glCanvas.viewMode = "Transparent"\r
336                 elif self.xrayViewButton.GetValue():\r
337                         self.glCanvas.viewMode = "X-Ray"\r
338                 elif self.gcodeViewButton.GetValue():\r
339                         self.glCanvas.viewMode = "GCode"\r
340                 elif self.mixedViewButton.GetValue():\r
341                         self.glCanvas.viewMode = "Mixed"\r
342                 self.glCanvas.drawBorders = self.showBorderButton.GetValue()\r
343                 self.updateToolbar()\r
344                 self.glCanvas.Refresh()\r
345         \r
346         def updateModelTransform(self, f=0):\r
347                 if len(self.objectList) < 1 or self.objectList[0].mesh == None:\r
348                         return\r
349                 \r
350                 rotate = profile.getProfileSettingFloat('model_rotate_base')\r
351                 mirrorX = profile.getProfileSetting('flip_x') == 'True'\r
352                 mirrorY = profile.getProfileSetting('flip_y') == 'True'\r
353                 mirrorZ = profile.getProfileSetting('flip_z') == 'True'\r
354                 swapXZ = profile.getProfileSetting('swap_xz') == 'True'\r
355                 swapYZ = profile.getProfileSetting('swap_yz') == 'True'\r
356 \r
357                 for obj in self.objectList:\r
358                         if obj.mesh == None:\r
359                                 continue\r
360                         obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)\r
361                 \r
362                 minV = self.objectList[0].mesh.getMinimum()\r
363                 maxV = self.objectList[0].mesh.getMaximum()\r
364                 for obj in self.objectList:\r
365                         if obj.mesh == None:\r
366                                 continue\r
367 \r
368                         obj.mesh.getMinimumZ()\r
369                         minV = numpy.minimum(minV, obj.mesh.getMinimum())\r
370                         maxV = numpy.maximum(maxV, obj.mesh.getMaximum())\r
371 \r
372                 self.objectsMaxV = maxV\r
373                 self.objectsMinV = minV\r
374                 for obj in self.objectList:\r
375                         if obj.mesh == None:\r
376                                 continue\r
377 \r
378                         obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])\r
379                         #for v in obj.mesh.vertexes:\r
380                         #       v[2] -= minV[2]\r
381                         #       v[0] -= minV[0] + (maxV[0] - minV[0]) / 2\r
382                         #       v[1] -= minV[1] + (maxV[1] - minV[1]) / 2\r
383                         obj.mesh.getMinimumZ()\r
384                         obj.dirty = True\r
385                 self.glCanvas.Refresh()\r
386         \r
387         def updateProfileToControls(self):\r
388                 self.scale.SetValue(profile.getProfileSetting('model_scale'))\r
389                 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))\r
390                 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')\r
391                 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')\r
392                 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')\r
393                 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')\r
394                 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')\r
395                 self.updateModelTransform()\r
396                 self.glCanvas.updateProfileToControls()\r
397 \r
398 class PreviewGLCanvas(glcanvas.GLCanvas):\r
399         def __init__(self, parent):\r
400                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
401                 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
402                 self.parent = parent\r
403                 self.context = glcanvas.GLContext(self)\r
404                 wx.EVT_PAINT(self, self.OnPaint)\r
405                 wx.EVT_SIZE(self, self.OnSize)\r
406                 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
407                 wx.EVT_MOTION(self, self.OnMouseMotion)\r
408                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
409                 self.yaw = 30\r
410                 self.pitch = 60\r
411                 self.zoom = 300\r
412                 self.offsetX = 0\r
413                 self.offsetY = 0\r
414                 self.view3D = True\r
415                 self.gcodeDisplayList = None\r
416                 self.gcodeDisplayListMade = None\r
417                 self.gcodeDisplayListCount = 0\r
418                 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]]\r
419                 self.oldX = 0\r
420                 self.oldY = 0\r
421                 self.dragType = ''\r
422                 self.tempRotate = 0\r
423         \r
424         def updateProfileToControls(self):\r
425                 self.objColor[0] = profile.getPreferenceColour('model_colour')\r
426                 self.objColor[1] = profile.getPreferenceColour('model_colour2')\r
427                 self.objColor[2] = profile.getPreferenceColour('model_colour3')\r
428                 self.objColor[3] = profile.getPreferenceColour('model_colour3')\r
429 \r
430         def OnMouseMotion(self,e):\r
431                 cursorXY = 100000\r
432                 sizeXY = 0\r
433                 if self.parent.objectsMaxV != None:\r
434                         size = (self.parent.objectsMaxV - self.parent.objectsMinV)\r
435                         sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))\r
436                         \r
437                         p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))\r
438                         p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))\r
439                         cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))\r
440                         cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))\r
441                         if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:\r
442                                 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))\r
443                         else:\r
444                                 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))\r
445 \r
446                 if e.Dragging() and e.LeftIsDown():\r
447                         if self.dragType == '':\r
448                                 #Define the drag type depending on the cursor position.\r
449                                 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:\r
450                                         self.dragType = 'modelRotate'\r
451                                         self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])\r
452                                 else:\r
453                                         self.dragType = 'viewRotate'\r
454                                 \r
455                         if self.dragType == 'viewRotate':\r
456                                 if self.view3D:\r
457                                         self.yaw += e.GetX() - self.oldX\r
458                                         self.pitch -= e.GetY() - self.oldY\r
459                                         if self.pitch > 170:\r
460                                                 self.pitch = 170\r
461                                         if self.pitch < 10:\r
462                                                 self.pitch = 10\r
463                                 else:\r
464                                         self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
465                                         self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
466                         elif self.dragType == 'modelRotate':\r
467                                 angle = math.atan2(cursorZ0[0], cursorZ0[1])\r
468                                 diff = self.dragStart - angle\r
469                                 self.tempRotate = diff * 180 / math.pi\r
470                         #Workaround for buggy ATI cards.\r
471                         size = self.GetSizeTuple()\r
472                         self.SetSize((size[0]+1, size[1]))\r
473                         self.SetSize((size[0], size[1]))\r
474                         self.Refresh()\r
475                 else:\r
476                         if self.tempRotate != 0:\r
477                                 profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)\r
478                                 self.parent.updateModelTransform()\r
479                                 self.tempRotate = 0\r
480                                 \r
481                         self.dragType = ''\r
482                 if e.Dragging() and e.RightIsDown():\r
483                         self.zoom += e.GetY() - self.oldY\r
484                         if self.zoom < 1:\r
485                                 self.zoom = 1\r
486                         if self.zoom > 500:\r
487                                 self.zoom = 500\r
488                         self.Refresh()\r
489                 self.oldX = e.GetX()\r
490                 self.oldY = e.GetY()\r
491 \r
492                 #self.Refresh()\r
493                 \r
494         \r
495         def OnMouseWheel(self,e):\r
496                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
497                 if self.zoom < 1.0:\r
498                         self.zoom = 1.0\r
499                 if self.zoom > 500:\r
500                         self.zoom = 500\r
501                 self.Refresh()\r
502         \r
503         def OnEraseBackground(self,event):\r
504                 #Workaround for windows background redraw flicker.\r
505                 pass\r
506         \r
507         def OnSize(self,e):\r
508                 self.Refresh()\r
509 \r
510         def OnPaint(self,e):\r
511                 dc = wx.PaintDC(self)\r
512                 if not hasOpenGLlibs:\r
513                         dc.Clear()\r
514                         dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
515                         return\r
516                 self.SetCurrent(self.context)\r
517                 opengl.InitGL(self, self.view3D, self.zoom)\r
518                 if self.view3D:\r
519                         glTranslate(0,0,-self.zoom)\r
520                         glRotate(-self.pitch, 1,0,0)\r
521                         glRotate(self.yaw, 0,0,1)\r
522                         if self.viewMode == "GCode" or self.viewMode == "Mixed":\r
523                                 if self.parent.gcode != None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:\r
524                                         glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)\r
525                         else:\r
526                                 if self.parent.objectsMaxV != None:\r
527                                         glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)\r
528                 else:\r
529                         glScale(1.0/self.zoom, 1.0/self.zoom, 1.0)\r
530                         glTranslate(self.offsetX, self.offsetY, 0.0)\r
531 \r
532                 self.viewport = glGetIntegerv(GL_VIEWPORT);\r
533                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);\r
534                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);\r
535 \r
536                 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)\r
537 \r
538                 self.OnDraw()\r
539                 self.SwapBuffers()\r
540 \r
541         def OnDraw(self):\r
542                 machineSize = self.parent.machineSize\r
543 \r
544                 if self.parent.gcode != None and self.parent.gcodeDirty:\r
545                         if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:\r
546                                 if self.gcodeDisplayList != None:\r
547                                         glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)\r
548                                 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));\r
549                                 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)\r
550                         self.parent.gcodeDirty = False\r
551                         self.gcodeDisplayListMade = 0\r
552                 \r
553                 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):\r
554                         glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)\r
555                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])\r
556                         glEndList()\r
557                         self.gcodeDisplayListMade += 1\r
558                         self.Refresh()\r
559                 \r
560                 glPushMatrix()\r
561                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
562                 for obj in self.parent.objectList:\r
563                         if obj.mesh == None:\r
564                                 continue\r
565                         if obj.displayList == None:\r
566                                 obj.displayList = glGenLists(1);\r
567                         if obj.dirty:\r
568                                 obj.dirty = False\r
569                                 glNewList(obj.displayList, GL_COMPILE)\r
570                                 opengl.DrawMesh(obj.mesh)\r
571                                 glEndList()\r
572                         \r
573                         if self.viewMode == "Mixed":\r
574                                 glDisable(GL_BLEND)\r
575                                 glColor3f(0.0,0.0,0.0)\r
576                                 self.drawModel(obj)\r
577                                 glColor3f(1.0,1.0,1.0)\r
578                                 glClear(GL_DEPTH_BUFFER_BIT)\r
579                 \r
580                 glPopMatrix()\r
581                 \r
582                 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):\r
583                         glEnable(GL_COLOR_MATERIAL)\r
584                         glEnable(GL_LIGHTING)\r
585                         drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)\r
586                         starttime = time.time()\r
587                         for i in xrange(drawUpToLayer - 1, -1, -1):\r
588                                 c = 1.0\r
589                                 if i < self.parent.layerSpin.GetValue():\r
590                                         c = 0.9 - (drawUpToLayer - i) * 0.1\r
591                                         if c < 0.4:\r
592                                                 c = (0.4 + c) / 2\r
593                                         if c < 0.1:\r
594                                                 c = 0.1\r
595                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])\r
596                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])\r
597                                 glCallList(self.gcodeDisplayList + i)\r
598                                 if time.time() - starttime > 0.1:\r
599                                         break\r
600 \r
601                         glDisable(GL_LIGHTING)\r
602                         glDisable(GL_COLOR_MATERIAL)\r
603                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);\r
604                         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);\r
605 \r
606                 glColor3f(1.0,1.0,1.0)\r
607                 glPushMatrix()\r
608                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
609                 for obj in self.parent.objectList:\r
610                         if obj.mesh == None:\r
611                                 continue\r
612                         \r
613                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":\r
614                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))\r
615                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))\r
616                                 #If we want transparent, then first render a solid black model to remove the printer size lines.\r
617                                 if self.viewMode != "Mixed":\r
618                                         glDisable(GL_BLEND)\r
619                                         glColor3f(0.0,0.0,0.0)\r
620                                         self.drawModel(obj)\r
621                                         glColor3f(1.0,1.0,1.0)\r
622                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.\r
623                                 glDisable(GL_DEPTH_TEST)\r
624                                 glEnable(GL_LIGHTING)\r
625                                 glEnable(GL_BLEND)\r
626                                 glBlendFunc(GL_ONE, GL_ONE)\r
627                                 glEnable(GL_LIGHTING)\r
628                                 self.drawModel(obj)\r
629                                 glEnable(GL_DEPTH_TEST)\r
630                         elif self.viewMode == "X-Ray":\r
631                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
632                                 glDisable(GL_LIGHTING)\r
633                                 glDisable(GL_DEPTH_TEST)\r
634                                 glEnable(GL_STENCIL_TEST)\r
635                                 glStencilFunc(GL_ALWAYS, 1, 1)\r
636                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)\r
637                                 self.drawModel(obj)\r
638                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);\r
639                                 \r
640                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
641                                 glStencilFunc(GL_EQUAL, 0, 1)\r
642                                 glColor(1, 1, 1)\r
643                                 self.drawModel(obj)\r
644                                 glStencilFunc(GL_EQUAL, 1, 1)\r
645                                 glColor(1, 0, 0)\r
646                                 self.drawModel(obj)\r
647 \r
648                                 glPushMatrix()\r
649                                 glLoadIdentity()\r
650                                 for i in xrange(2, 15, 2):\r
651                                         glStencilFunc(GL_EQUAL, i, 0xFF);\r
652                                         glColor(float(i)/10, float(i)/10, float(i)/5)\r
653                                         glBegin(GL_QUADS)\r
654                                         glVertex3f(-1000,-1000,-1)\r
655                                         glVertex3f( 1000,-1000,-1)\r
656                                         glVertex3f( 1000, 1000,-1)\r
657                                         glVertex3f(-1000, 1000,-1)\r
658                                         glEnd()\r
659                                 for i in xrange(1, 15, 2):\r
660                                         glStencilFunc(GL_EQUAL, i, 0xFF);\r
661                                         glColor(float(i)/10, 0, 0)\r
662                                         glBegin(GL_QUADS)\r
663                                         glVertex3f(-1000,-1000,-1)\r
664                                         glVertex3f( 1000,-1000,-1)\r
665                                         glVertex3f( 1000, 1000,-1)\r
666                                         glVertex3f(-1000, 1000,-1)\r
667                                         glEnd()\r
668                                 glPopMatrix()\r
669 \r
670                                 glDisable(GL_STENCIL_TEST)\r
671                                 glEnable(GL_DEPTH_TEST)\r
672                                 \r
673                                 #Fix the depth buffer\r
674                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
675                                 self.drawModel(obj)\r
676                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
677                         elif self.viewMode == "Normal":\r
678                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])\r
679                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 5, self.objColor[self.parent.objectList.index(obj)]))\r
680                                 glEnable(GL_LIGHTING)\r
681                                 self.drawModel(obj)\r
682 \r
683                         if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):\r
684                                 glEnable(GL_DEPTH_TEST)\r
685                                 glDisable(GL_LIGHTING)\r
686                                 glColor3f(1,1,1)\r
687                                 glPushMatrix()\r
688                                 modelScale = profile.getProfileSettingFloat('model_scale')\r
689                                 glScalef(modelScale, modelScale, modelScale)\r
690                                 opengl.DrawMeshOutline(obj.mesh)\r
691                                 glPopMatrix()\r
692                 \r
693                 glPopMatrix()   \r
694                 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":\r
695                         glDisable(GL_LIGHTING)\r
696                         glDisable(GL_DEPTH_TEST)\r
697                         glDisable(GL_BLEND)\r
698                         glColor3f(1,0,0)\r
699                         glBegin(GL_LINES)\r
700                         for err in self.parent.errorList:\r
701                                 glVertex3f(err[0].x, err[0].y, err[0].z)\r
702                                 glVertex3f(err[1].x, err[1].y, err[1].z)\r
703                         glEnd()\r
704                         glEnable(GL_DEPTH_TEST)\r
705 \r
706                 opengl.DrawMachine(machineSize)\r
707 \r
708                 glPushMatrix()\r
709                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
710                 \r
711                 #Draw the rotate circle\r
712                 if self.parent.objectsMaxV != None and False:\r
713                         glDisable(GL_LIGHTING)\r
714                         glDisable(GL_CULL_FACE)\r
715                         glEnable(GL_BLEND)\r
716                         glBegin(GL_TRIANGLE_STRIP)\r
717                         size = (self.parent.objectsMaxV - self.parent.objectsMinV)\r
718                         sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))\r
719                         for i in xrange(0, 64+1):\r
720                                 f = i if i < 64/2 else 64 - i\r
721                                 glColor4ub(255,int(f*255/(64/2)),0,128)\r
722                                 glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)\r
723                                 glColor4ub(  0,128,0,128)\r
724                                 glVertex3f((sizeXY * 0.7 + 3) * math.cos(i/32.0*math.pi), (sizeXY * 0.7 + 3) * math.sin(i/32.0*math.pi),0.1)\r
725                         glEnd()\r
726                         glEnable(GL_CULL_FACE)\r
727                 \r
728                 glPopMatrix()\r
729                 \r
730                 glFlush()\r
731         \r
732         def drawModel(self, obj):\r
733                 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))\r
734                 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))\r
735                 modelScale = profile.getProfileSettingFloat('model_scale')\r
736                 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale\r
737                 glPushMatrix()\r
738                 glRotate(self.tempRotate, 0, 0, 1)\r
739                 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)\r
740                 for mx in xrange(0, multiX):\r
741                         for my in xrange(0, multiY):\r
742                                 glPushMatrix()\r
743                                 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)\r
744                                 glScalef(modelScale, modelScale, modelScale)\r
745                                 glCallList(obj.displayList)\r
746                                 glPopMatrix()\r
747                 glPopMatrix()\r
748 \r