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