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