chiark / gitweb /
ad2f5f0d49a96c87ba5f34a8dea249a5fd5f2a42
[cura.git] / Cura / gui / preview3d.py
1 from __future__ import division\r
2 \r
3 import sys\r
4 import math\r
5 import threading\r
6 import re\r
7 import time\r
8 import os\r
9 \r
10 from wx import glcanvas\r
11 import wx\r
12 try:\r
13         import OpenGL\r
14         OpenGL.ERROR_CHECKING = False\r
15         from OpenGL.GLU import *\r
16         from OpenGL.GL import *\r
17         hasOpenGLlibs = True\r
18 except:\r
19         print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
20         hasOpenGLlibs = False\r
21 \r
22 from gui import opengl\r
23 from gui import toolbarUtil\r
24 \r
25 from util import profile\r
26 from util import gcodeInterpreter\r
27 from util import stl\r
28 from util import util3d\r
29 \r
30 class previewObject():\r
31         def __init__(self):\r
32                 self.mesh = None\r
33                 self.filename = None\r
34                 self.displayList = None\r
35                 self.dirty = False\r
36 \r
37 class previewPanel(wx.Panel):\r
38         def __init__(self, parent):\r
39                 super(previewPanel, self).__init__(parent,-1)\r
40                 \r
41                 self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))\r
42                 self.SetMinSize((440,320))\r
43                 \r
44                 self.glCanvas = PreviewGLCanvas(self)\r
45                 self.objectList = []\r
46                 self.gcode = None\r
47                 self.objectsMinV = None\r
48                 self.objectsMaxV = None\r
49                 self.loadThread = None\r
50                 self.machineSize = util3d.Vector3(float(profile.getPreference('machine_width')), float(profile.getPreference('machine_depth')), float(profile.getPreference('machine_height')))\r
51                 self.machineCenter = util3d.Vector3(float(profile.getProfileSetting('machine_center_x')), float(profile.getProfileSetting('machine_center_y')), 0)\r
52                 \r
53                 self.toolbar = toolbarUtil.Toolbar(self)\r
54 \r
55                 group = []\r
56                 toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)\r
57                 toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)\r
58                 self.toolbar.AddSeparator()\r
59 \r
60                 group = []\r
61                 self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)\r
62                 self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)\r
63                 self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)\r
64                 self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)\r
65                 self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)\r
66                 self.OnViewChange()\r
67                 self.toolbar.AddSeparator()\r
68 \r
69                 self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)\r
70                 self.toolbar.AddControl(self.layerSpin)\r
71                 self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)\r
72 \r
73                 self.toolbar2 = toolbarUtil.Toolbar(self)\r
74 \r
75                 # Mirror\r
76                 self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.updateModelTransform)\r
77                 self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.updateModelTransform)\r
78                 self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.updateModelTransform)\r
79                 self.toolbar2.AddSeparator()\r
80 \r
81                 # Swap\r
82                 self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.updateModelTransform)\r
83                 self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.updateModelTransform)\r
84                 self.toolbar2.AddSeparator()\r
85 \r
86                 # Scale\r
87                 self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')\r
88                 self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))\r
89                 self.toolbar2.AddControl(self.scale)\r
90                 self.scale.Bind(wx.EVT_TEXT, self.OnScale)\r
91                 self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fix machine size')\r
92 \r
93                 self.toolbar2.AddSeparator()\r
94 \r
95                 # Multiply\r
96                 self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')\r
97                 self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')\r
98                 self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')\r
99                 self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')\r
100 \r
101                 self.toolbar2.AddSeparator()\r
102 \r
103                 # Rotate\r
104                 self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')\r
105                 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
106                 self.rotate.SetRange(0, 360)\r
107                 self.Bind(wx.EVT_TEXT, self.OnRotate)\r
108                 self.toolbar2.AddControl(self.rotate)\r
109 \r
110                 self.toolbar2.Realize()\r
111                 self.updateToolbar()\r
112                 \r
113                 sizer = wx.BoxSizer(wx.VERTICAL)\r
114                 sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)\r
115                 sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)\r
116                 sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)\r
117                 self.SetSizer(sizer)\r
118         \r
119         def OnMulXAddClick(self, e):\r
120                 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))\r
121                 self.glCanvas.Refresh()\r
122 \r
123         def OnMulXSubClick(self, e):\r
124                 profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))\r
125                 self.glCanvas.Refresh()\r
126 \r
127         def OnMulYAddClick(self, e):\r
128                 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))\r
129                 self.glCanvas.Refresh()\r
130 \r
131         def OnMulYSubClick(self, e):\r
132                 profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))\r
133                 self.glCanvas.Refresh()\r
134 \r
135         def OnScaleReset(self, e):\r
136                 self.scale.SetValue('1.0')\r
137                 self.OnScale(None)\r
138 \r
139         def OnScale(self, e):\r
140                 scale = 1.0\r
141                 if self.scale.GetValue() != '':\r
142                         scale = self.scale.GetValue()\r
143                 profile.putProfileSetting('model_scale', scale)\r
144                 self.glCanvas.Refresh()\r
145         \r
146         def OnScaleMax(self, e):\r
147                 if self.objectsMinV == None:\r
148                         return\r
149                 vMin = self.objectsMinV\r
150                 vMax = self.objectsMaxV\r
151                 scaleX1 = (self.machineSize.x - self.machineCenter.x) / ((vMax.x - vMin.x) / 2)\r
152                 scaleY1 = (self.machineSize.y - self.machineCenter.y) / ((vMax.y - vMin.y) / 2)\r
153                 scaleX2 = (self.machineCenter.x) / ((vMax.x - vMin.x) / 2)\r
154                 scaleY2 = (self.machineCenter.y) / ((vMax.y - vMin.y) / 2)\r
155                 scaleZ = self.machineSize.z / (vMax.z - vMin.z)\r
156                 scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)\r
157                 self.scale.SetValue(str(scale))\r
158                 profile.putProfileSetting('model_scale', self.scale.GetValue())\r
159                 self.glCanvas.Refresh()\r
160 \r
161         def OnRotateReset(self, e):\r
162                 self.rotate.SetValue(0)\r
163                 self.OnRotate(None)\r
164 \r
165         def OnRotate(self, e):\r
166                 profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())\r
167                 self.updateModelTransform()\r
168 \r
169         def On3DClick(self):\r
170                 self.glCanvas.yaw = 30\r
171                 self.glCanvas.pitch = 60\r
172                 self.glCanvas.zoom = 300\r
173                 self.glCanvas.view3D = True\r
174                 self.glCanvas.Refresh()\r
175 \r
176         def OnTopClick(self):\r
177                 self.glCanvas.view3D = False\r
178                 self.glCanvas.zoom = 100\r
179                 self.glCanvas.offsetX = 0\r
180                 self.glCanvas.offsetY = 0\r
181                 self.glCanvas.Refresh()\r
182 \r
183         def OnLayerNrChange(self, e):\r
184                 self.gcodeDirty = True\r
185                 self.glCanvas.Refresh()\r
186 \r
187         def updateCenterX(self):\r
188                 self.machineCenter.x = profile.getProfileSettingFloat('machine_center_x')\r
189                 self.glCanvas.Refresh()\r
190 \r
191         def updateCenterY(self):\r
192                 self.machineCenter.y = profile.getProfileSettingFloat('machine_center_y')\r
193                 self.glCanvas.Refresh()\r
194         \r
195         def setViewMode(self, mode):\r
196                 if mode == "Normal":\r
197                         self.normalViewButton.SetValue(True)\r
198                 if mode == "GCode":\r
199                         self.gcodeViewButton.SetValue(True)\r
200                 self.glCanvas.viewMode = mode\r
201                 wx.CallAfter(self.glCanvas.Refresh)\r
202         \r
203         def loadModelFiles(self, filelist):\r
204                 while len(filelist) > len(self.objectList):\r
205                         self.objectList.append(previewObject())\r
206                 for idx in xrange(len(filelist), len(self.objectList)):\r
207                         self.objectList[idx].mesh = None\r
208                         self.objectList[idx].filename = None\r
209                 for idx in xrange(0, len(filelist)):\r
210                         obj = self.objectList[idx]\r
211                         if obj.filename != filelist[idx]:\r
212                                 obj.fileTime = None\r
213                                 self.gcodeFileTime = None\r
214                                 self.logFileTime = None\r
215                         obj.filename = filelist[idx]\r
216                 \r
217                 self.gcodeFilename = filelist[0][: filelist[0].rfind('.')] + "_export.gcode"\r
218                 self.logFilename = filelist[0][: filelist[0].rfind('.')] + "_export.log"\r
219                 #Do the STL file loading in a background thread so we don't block the UI.\r
220                 if self.loadThread != None and self.loadThread.isAlive():\r
221                         self.loadThread.join()\r
222                 self.loadThread = threading.Thread(target=self.doFileLoadThread)\r
223                 self.loadThread.daemon = True\r
224                 self.loadThread.start()\r
225         \r
226         def loadReModelFiles(self, filelist):\r
227                 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)\r
228                 for idx in xrange(0, len(filelist)):\r
229                         if self.objectList[idx].filename != filelist[idx]:\r
230                                 return False\r
231                 self.loadModelFiles(filelist)\r
232                 return True\r
233         \r
234         def doFileLoadThread(self):\r
235                 for obj in self.objectList:\r
236                         if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:\r
237                                 obj.ileTime = os.stat(obj.filename).st_mtime\r
238                                 mesh = stl.stlModel()\r
239                                 mesh.load(obj.filename)\r
240                                 obj.dirty = False\r
241                                 obj.errorList = []\r
242                                 obj.mesh = mesh\r
243                                 self.updateModelTransform()\r
244                                 wx.CallAfter(self.updateToolbar)\r
245                                 wx.CallAfter(self.glCanvas.Refresh)\r
246                 \r
247                 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:\r
248                         self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime\r
249                         gcode = gcodeInterpreter.gcode()\r
250                         gcode.progressCallback = self.loadProgress\r
251                         gcode.load(self.gcodeFilename)\r
252                         self.gcodeDirty = False\r
253                         self.errorList = []\r
254                         self.gcode = gcode\r
255                         self.gcodeDirty = True\r
256                         wx.CallAfter(self.updateToolbar)\r
257                         wx.CallAfter(self.glCanvas.Refresh)\r
258                 elif not os.path.isfile(self.gcodeFilename):\r
259                         self.gcode = None\r
260                 \r
261                 if os.path.isfile(self.logFilename):\r
262                         errorList = []\r
263                         for line in open(self.logFilename, "rt"):\r
264                                 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
265                                 if res != None:\r
266                                         v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))\r
267                                         v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))\r
268                                         errorList.append([v1, v2])\r
269                         self.errorList = errorList\r
270                         wx.CallAfter(self.glCanvas.Refresh)\r
271         \r
272         def loadProgress(self, progress):\r
273                 pass\r
274         \r
275         def updateToolbar(self):\r
276                 self.layerSpin.Show(self.gcode != None)\r
277                 if self.gcode != None:\r
278                         self.layerSpin.SetRange(1, len(self.gcode.layerList))\r
279                 self.toolbar.Realize()\r
280         \r
281         def OnViewChange(self):\r
282                 if self.normalViewButton.GetValue():\r
283                         self.glCanvas.viewMode = "Normal"\r
284                 elif self.transparentViewButton.GetValue():\r
285                         self.glCanvas.viewMode = "Transparent"\r
286                 elif self.xrayViewButton.GetValue():\r
287                         self.glCanvas.viewMode = "X-Ray"\r
288                 elif self.gcodeViewButton.GetValue():\r
289                         self.glCanvas.viewMode = "GCode"\r
290                 elif self.mixedViewButton.GetValue():\r
291                         self.glCanvas.viewMode = "Mixed"\r
292                 self.glCanvas.Refresh()\r
293         \r
294         def updateModelTransform(self, f=0):\r
295                 rotate = profile.getProfileSettingFloat('model_rotate_base') / 180.0 * math.pi\r
296                 scaleX = 1.0\r
297                 scaleY = 1.0\r
298                 scaleZ = 1.0\r
299                 if profile.getProfileSetting('flip_x') == 'True':\r
300                         scaleX = -scaleX\r
301                 if profile.getProfileSetting('flip_y') == 'True':\r
302                         scaleY = -scaleY\r
303                 if profile.getProfileSetting('flip_z') == 'True':\r
304                         scaleZ = -scaleZ\r
305                 swapXZ = profile.getProfileSetting('swap_xz') == 'True'\r
306                 swapYZ = profile.getProfileSetting('swap_yz') == 'True'\r
307                 mat00 = math.cos(rotate) * scaleX\r
308                 mat01 =-math.sin(rotate) * scaleY\r
309                 mat10 = math.sin(rotate) * scaleX\r
310                 mat11 = math.cos(rotate) * scaleY\r
311                 \r
312                 if len(self.objectList) < 1 or self.objectList[0].mesh == None:\r
313                         return\r
314 \r
315                 for obj in self.objectList:\r
316                         if obj.mesh == None:\r
317                                 continue\r
318                 \r
319                         for i in xrange(0, len(obj.mesh.origonalVertexes)):\r
320                                 x = obj.mesh.origonalVertexes[i].x\r
321                                 y = obj.mesh.origonalVertexes[i].y\r
322                                 z = obj.mesh.origonalVertexes[i].z\r
323                                 if swapXZ:\r
324                                         x, z = z, x\r
325                                 if swapYZ:\r
326                                         y, z = z, y\r
327                                 obj.mesh.vertexes[i].x = x * mat00 + y * mat01\r
328                                 obj.mesh.vertexes[i].y = x * mat10 + y * mat11\r
329                                 obj.mesh.vertexes[i].z = z * scaleZ\r
330 \r
331                         for face in obj.mesh.faces:\r
332                                 v1 = face.v[0]\r
333                                 v2 = face.v[1]\r
334                                 v3 = face.v[2]\r
335                                 face.normal = (v2 - v1).cross(v3 - v1)\r
336                                 face.normal.normalize()\r
337                 \r
338                 minV = self.objectList[0].mesh.getMinimum()\r
339                 maxV = self.objectList[0].mesh.getMaximum()\r
340                 for obj in self.objectList:\r
341                         if obj.mesh == None:\r
342                                 continue\r
343 \r
344                         obj.mesh.getMinimumZ()\r
345                         minV = minV.min(obj.mesh.getMinimum())\r
346                         maxV = maxV.max(obj.mesh.getMaximum())\r
347 \r
348                 self.objectsMaxV = maxV\r
349                 self.objectsMinV = minV\r
350                 for obj in self.objectList:\r
351                         if obj.mesh == None:\r
352                                 continue\r
353 \r
354                         for v in obj.mesh.vertexes:\r
355                                 v.z -= minV.z\r
356                                 v.x -= minV.x + (maxV.x - minV.x) / 2\r
357                                 v.y -= minV.y + (maxV.y - minV.y) / 2\r
358                         obj.mesh.getMinimumZ()\r
359                         obj.dirty = True\r
360                 self.glCanvas.Refresh()\r
361 \r
362 class PreviewGLCanvas(glcanvas.GLCanvas):\r
363         def __init__(self, parent):\r
364                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
365                 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
366                 self.parent = parent\r
367                 self.context = glcanvas.GLContext(self)\r
368                 wx.EVT_PAINT(self, self.OnPaint)\r
369                 wx.EVT_SIZE(self, self.OnSize)\r
370                 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
371                 wx.EVT_MOTION(self, self.OnMouseMotion)\r
372                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
373                 self.yaw = 30\r
374                 self.pitch = 60\r
375                 self.zoom = 300\r
376                 self.offsetX = 0\r
377                 self.offsetY = 0\r
378                 self.view3D = True\r
379                 self.gcodeDisplayList = None\r
380                 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
381         \r
382         def OnMouseMotion(self,e):\r
383                 if e.Dragging() and e.LeftIsDown():\r
384                         if self.view3D:\r
385                                 self.yaw += e.GetX() - self.oldX\r
386                                 self.pitch -= e.GetY() - self.oldY\r
387                                 if self.pitch > 170:\r
388                                         self.pitch = 170\r
389                                 if self.pitch < 10:\r
390                                         self.pitch = 10\r
391                         else:\r
392                                 self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
393                                 self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
394                         self.Refresh()\r
395                 if e.Dragging() and e.RightIsDown():\r
396                         self.zoom += e.GetY() - self.oldY\r
397                         if self.zoom < 1:\r
398                                 self.zoom = 1\r
399                         self.Refresh()\r
400                 self.oldX = e.GetX()\r
401                 self.oldY = e.GetY()\r
402         \r
403         def OnMouseWheel(self,e):\r
404                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
405                 if self.zoom < 1.0:\r
406                         self.zoom = 1.0\r
407                 self.Refresh()\r
408         \r
409         def OnEraseBackground(self,event):\r
410                 #Workaround for windows background redraw flicker.\r
411                 pass\r
412         \r
413         def OnSize(self,event):\r
414                 self.Refresh()\r
415 \r
416         def OnPaint(self,event):\r
417                 dc = wx.PaintDC(self)\r
418                 if not hasOpenGLlibs:\r
419                         dc.Clear()\r
420                         dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
421                         return\r
422                 self.SetCurrent(self.context)\r
423                 opengl.InitGL(self, self.view3D, self.zoom)\r
424                 if self.view3D:\r
425                         glTranslate(0,0,-self.zoom)\r
426                         glRotate(-self.pitch, 1,0,0)\r
427                         glRotate(self.yaw, 0,0,1)\r
428                         if self.parent.objectsMaxV != None:\r
429                                 glTranslate(0,0,-self.parent.objectsMaxV.z * profile.getProfileSettingFloat('model_scale') / 2)\r
430                 else:\r
431                         glScale(1.0/self.zoom, 1.0/self.zoom, 1.0)\r
432                         glTranslate(self.offsetX, self.offsetY, 0.0)\r
433                 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)\r
434 \r
435                 self.OnDraw()\r
436                 self.SwapBuffers()\r
437 \r
438         def OnDraw(self):\r
439                 machineSize = self.parent.machineSize\r
440                 opengl.DrawMachine(machineSize)\r
441 \r
442                 if self.parent.gcode != None:\r
443                         if self.gcodeDisplayList == None:\r
444                                 self.gcodeDisplayList = glGenLists(1);\r
445                         if self.parent.gcodeDirty:\r
446                                 self.parent.gcodeDirty = False\r
447                                 glNewList(self.gcodeDisplayList, GL_COMPILE)\r
448                                 prevLayerZ = 0.0\r
449                                 curLayerZ = 0.0\r
450                                 \r
451                                 layerThickness = 0.0\r
452                                 filamentRadius = float(profile.getProfileSetting('filament_diameter')) / 2\r
453                                 filamentArea = math.pi * filamentRadius * filamentRadius\r
454                                 lineWidth = float(profile.getProfileSetting('nozzle_size')) / 2 / 10\r
455                                 \r
456                                 curLayerNum = 0\r
457                                 for layer in self.parent.gcode.layerList:\r
458                                         curLayerZ = layer[0].list[1].z\r
459                                         layerThickness = curLayerZ - prevLayerZ\r
460                                         prevLayerZ = layer[-1].list[-1].z\r
461                                         for path in layer:\r
462                                                 c = 1.0\r
463                                                 if curLayerNum != self.parent.layerSpin.GetValue():\r
464                                                         if curLayerNum < self.parent.layerSpin.GetValue():\r
465                                                                 c = 0.9 - (self.parent.layerSpin.GetValue() - curLayerNum) * 0.1\r
466                                                                 if c < 0.4:\r
467                                                                         c = 0.4\r
468                                                         else:\r
469                                                                 break\r
470                                                 if path.type == 'move':\r
471                                                         glColor3f(0,0,c)\r
472                                                 if path.type == 'extrude':\r
473                                                         if path.pathType == 'FILL':\r
474                                                                 glColor3f(c/2,c/2,0)\r
475                                                         elif path.pathType == 'WALL-INNER':\r
476                                                                 glColor3f(0,c,0)\r
477                                                         elif path.pathType == 'SUPPORT':\r
478                                                                 glColor3f(0,c,c)\r
479                                                         elif path.pathType == 'SKIRT':\r
480                                                                 glColor3f(0,c/2,c/2)\r
481                                                         else:\r
482                                                                 glColor3f(c,0,0)\r
483                                                 if path.type == 'retract':\r
484                                                         glColor3f(0,c,c)\r
485                                                 if c > 0.4 and path.type == 'extrude':\r
486                                                         for i in xrange(0, len(path.list)-1):\r
487                                                                 v0 = path.list[i]\r
488                                                                 v1 = path.list[i+1]\r
489 \r
490                                                                 # Calculate line width from ePerDistance (needs layer thickness and filament diameter)\r
491                                                                 dist = (v0 - v1).vsize()\r
492                                                                 if dist > 0 and layerThickness > 0:\r
493                                                                         extrusionMMperDist = (v1.e - v0.e) / dist\r
494                                                                         lineWidth = extrusionMMperDist * filamentArea / layerThickness / 2\r
495 \r
496                                                                 normal = (v0 - v1).cross(util3d.Vector3(0,0,1))\r
497                                                                 normal.normalize()\r
498                                                                 v2 = v0 + normal * lineWidth\r
499                                                                 v3 = v1 + normal * lineWidth\r
500                                                                 v0 = v0 - normal * lineWidth\r
501                                                                 v1 = v1 - normal * lineWidth\r
502 \r
503                                                                 glBegin(GL_QUADS)\r
504                                                                 if path.pathType == 'FILL':     #Remove depth buffer fighting on infill/wall overlap\r
505                                                                         glVertex3f(v0.x, v0.y, v0.z - 0.02)\r
506                                                                         glVertex3f(v1.x, v1.y, v1.z - 0.02)\r
507                                                                         glVertex3f(v3.x, v3.y, v3.z - 0.02)\r
508                                                                         glVertex3f(v2.x, v2.y, v2.z - 0.02)\r
509                                                                 else:\r
510                                                                         glVertex3f(v0.x, v0.y, v0.z - 0.01)\r
511                                                                         glVertex3f(v1.x, v1.y, v1.z - 0.01)\r
512                                                                         glVertex3f(v3.x, v3.y, v3.z - 0.01)\r
513                                                                         glVertex3f(v2.x, v2.y, v2.z - 0.01)\r
514                                                                 glEnd()\r
515                                                 \r
516                                                         #for v in path['list']:\r
517                                                         #       glBegin(GL_TRIANGLE_FAN)\r
518                                                         #       glVertex3f(v.x, v.y, v.z - 0.001)\r
519                                                         #       for i in xrange(0, 16+1):\r
520                                                         #               if path['pathType'] == 'FILL':  #Remove depth buffer fighting on infill/wall overlap\r
521                                                         #                       glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.02)\r
522                                                         #               else:\r
523                                                         #                       glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.01)\r
524                                                         #       glEnd()\r
525                                                 else:\r
526                                                         glBegin(GL_LINE_STRIP)\r
527                                                         for v in path.list:\r
528                                                                 glVertex3f(v.x, v.y, v.z)\r
529                                                         glEnd()\r
530                                         curLayerNum += 1\r
531                                 glEndList()\r
532                         if self.viewMode == "GCode" or self.viewMode == "Mixed":\r
533                                 glCallList(self.gcodeDisplayList)\r
534                 \r
535                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
536                 for obj in self.parent.objectList:\r
537                         if obj.mesh == None:\r
538                                 continue\r
539                         if obj.displayList == None:\r
540                                 obj.displayList = glGenLists(1);\r
541                         if obj.dirty:\r
542                                 obj.dirty = False\r
543                                 glNewList(obj.displayList, GL_COMPILE)\r
544                                 opengl.DrawSTL(obj.mesh)\r
545                                 glEndList()\r
546                         \r
547                         glEnable(GL_NORMALIZE)\r
548                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":\r
549                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))\r
550                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))\r
551                                 #If we want transparent, then first render a solid black model to remove the printer size lines.\r
552                                 if self.viewMode != "Mixed":\r
553                                         glDisable(GL_BLEND)\r
554                                         glDisable(GL_LIGHTING)\r
555                                         glColor3f(0,0,0)\r
556                                         self.drawModel(obj)\r
557                                         glColor3f(1,1,1)\r
558                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.\r
559                                 glDisable(GL_DEPTH_TEST)\r
560                                 glEnable(GL_LIGHTING)\r
561                                 glEnable(GL_BLEND)\r
562                                 glBlendFunc(GL_ONE, GL_ONE)\r
563                                 glEnable(GL_LIGHTING)\r
564                                 self.drawModel(obj)\r
565                         elif self.viewMode == "X-Ray":\r
566                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
567                                 glDisable(GL_DEPTH_TEST)\r
568                                 glEnable(GL_STENCIL_TEST);\r
569                                 glStencilFunc(GL_ALWAYS, 1, 1)\r
570                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)\r
571                                 self.drawModel(obj)\r
572                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);\r
573                                 \r
574                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
575                                 glStencilFunc(GL_EQUAL, 0, 1);\r
576                                 glColor(1, 1, 1)\r
577                                 self.drawModel(obj)\r
578                                 glStencilFunc(GL_EQUAL, 1, 1);\r
579                                 glColor(1, 0, 0)\r
580                                 self.drawModel(obj)\r
581 \r
582                                 glPushMatrix()\r
583                                 glLoadIdentity()\r
584                                 for i in xrange(2, 15, 2):\r
585                                         glStencilFunc(GL_EQUAL, i, 0xFF);\r
586                                         glColor(float(i)/10, float(i)/10, float(i)/5)\r
587                                         glBegin(GL_QUADS)\r
588                                         glVertex3f(-1000,-1000,-1)\r
589                                         glVertex3f( 1000,-1000,-1)\r
590                                         glVertex3f( 1000, 1000,-1)\r
591                                         glVertex3f(-1000, 1000,-1)\r
592                                         glEnd()\r
593                                 for i in xrange(1, 15, 2):\r
594                                         glStencilFunc(GL_EQUAL, i, 0xFF);\r
595                                         glColor(float(i)/10, 0, 0)\r
596                                         glBegin(GL_QUADS)\r
597                                         glVertex3f(-1000,-1000,-1)\r
598                                         glVertex3f( 1000,-1000,-1)\r
599                                         glVertex3f( 1000, 1000,-1)\r
600                                         glVertex3f(-1000, 1000,-1)\r
601                                         glEnd()\r
602                                 glPopMatrix()\r
603 \r
604                                 glDisable(GL_STENCIL_TEST);\r
605                                 glEnable(GL_DEPTH_TEST)\r
606                         elif self.viewMode == "Normal":\r
607                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])\r
608                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 5, self.objColor[self.parent.objectList.index(obj)]))\r
609                                 glEnable(GL_LIGHTING)\r
610                                 self.drawModel(obj)\r
611                         \r
612                         if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":\r
613                                 glDisable(GL_LIGHTING)\r
614                                 glDisable(GL_DEPTH_TEST)\r
615                                 glDisable(GL_BLEND)\r
616                                 glColor3f(1,0,0)\r
617                                 #glBegin(GL_LINES)\r
618                                 #for err in self.parent.errorList:\r
619                                 #       glVertex3f(err[0].x, err[0].y, err[0].z)\r
620                                 #       glVertex3f(err[1].x, err[1].y, err[1].z)\r
621                                 #glEnd()\r
622                                 glEnable(GL_DEPTH_TEST)\r
623                 glFlush()\r
624         \r
625         def drawModel(self, obj):\r
626                 multiX = int(profile.getProfileSetting('model_multiply_x'))\r
627                 multiY = int(profile.getProfileSetting('model_multiply_y'))\r
628                 modelScale = profile.getProfileSettingFloat('model_scale')\r
629                 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale\r
630                 glPushMatrix()\r
631                 glTranslate(-(modelSize.x+10)*(multiX-1)/2,-(modelSize.y+10)*(multiY-1)/2, 0)\r
632                 for mx in xrange(0, multiX):\r
633                         for my in xrange(0, multiY):\r
634                                 glPushMatrix()\r
635                                 glTranslate((modelSize.x+10)*mx,(modelSize.y+10)*my, 0)\r
636                                 glScalef(modelScale, modelScale, modelScale)\r
637                                 glCallList(obj.displayList)\r
638                                 glPopMatrix()\r
639                 glPopMatrix()\r
640 \r