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