chiark / gitweb /
6ebae45d1a13bfa1703d3db62377e981a2534208
[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 loadReModelFiles(self, filelist):\r
256                 #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)\r
257                 for idx in xrange(0, len(filelist)):\r
258                         if self.objectList[idx].filename != filelist[idx]:\r
259                                 return False\r
260                 self.loadModelFiles(filelist)\r
261                 return True\r
262         \r
263         def doFileLoadThread(self):\r
264                 for obj in self.objectList:\r
265                         if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:\r
266                                 obj.ileTime = os.stat(obj.filename).st_mtime\r
267                                 mesh = meshLoader.loadMesh(obj.filename)\r
268                                 obj.dirty = False\r
269                                 obj.mesh = mesh\r
270                                 self.updateModelTransform()\r
271                                 scale = profile.getProfileSettingFloat('model_scale')\r
272                                 size = (self.objectsMaxV - self.objectsMinV) * scale\r
273                                 if size[0] > self.machineSize.x or size[1] > self.machineSize.y or size[2] > self.machineSize.z:\r
274                                         self.OnScaleMax(None)\r
275                                 self.glCanvas.zoom = numpy.max(size) * 2.5\r
276                                 self.errorList = []\r
277                                 wx.CallAfter(self.updateToolbar)\r
278                                 wx.CallAfter(self.glCanvas.Refresh)\r
279                 \r
280                 if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:\r
281                         self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime\r
282                         gcode = gcodeInterpreter.gcode()\r
283                         gcode.progressCallback = self.loadProgress\r
284                         gcode.load(self.gcodeFilename)\r
285                         self.gcodeDirty = False\r
286                         self.gcode = gcode\r
287                         self.gcodeDirty = True\r
288 \r
289                         errorList = []\r
290                         for line in open(self.gcodeFilename, "rt"):\r
291                                 res = re.search(';Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line)\r
292                                 if res != None:\r
293                                         v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))\r
294                                         v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))\r
295                                         errorList.append([v1, v2])\r
296                         self.errorList = errorList\r
297 \r
298                         wx.CallAfter(self.updateToolbar)\r
299                         wx.CallAfter(self.glCanvas.Refresh)\r
300                 elif not os.path.isfile(self.gcodeFilename):\r
301                         self.gcode = None\r
302         \r
303         def loadProgress(self, progress):\r
304                 pass\r
305 \r
306         def OnResetAll(self, e = None):\r
307                 profile.putProfileSetting('model_scale', '1.0')\r
308                 profile.putProfileSetting('model_rotate_base', '0')\r
309                 profile.putProfileSetting('flip_x', 'False')\r
310                 profile.putProfileSetting('flip_y', 'False')\r
311                 profile.putProfileSetting('flip_z', 'False')\r
312                 profile.putProfileSetting('swap_xz', 'False')\r
313                 profile.putProfileSetting('swap_yz', 'False')\r
314                 self.updateProfileToControls()\r
315 \r
316         def ShowWarningPopup(self, text, callback = None):\r
317                 self.warningPopup.text.SetLabel(text)\r
318                 self.warningPopup.callback = callback\r
319                 if callback == None:\r
320                         self.warningPopup.yesButton.Show(False)\r
321                         self.warningPopup.noButton.SetLabel('ok')\r
322                 else:\r
323                         self.warningPopup.yesButton.Show(True)\r
324                         self.warningPopup.noButton.SetLabel('no')\r
325                 self.warningPopup.Fit()\r
326                 self.warningPopup.Layout()\r
327                 self.OnMove()\r
328                 self.warningPopup.Show(True)\r
329                 self.warningPopup.timer.Start(5000)\r
330         \r
331         def OnWarningPopup(self, e):\r
332                 self.warningPopup.Show(False)\r
333                 self.warningPopup.timer.Stop()\r
334                 self.warningPopup.callback()\r
335 \r
336         def OnHideWarning(self, e):\r
337                 self.warningPopup.Show(False)\r
338                 self.warningPopup.timer.Stop()\r
339 \r
340         def updateToolbar(self):\r
341                 self.gcodeViewButton.Show(self.gcode != None)\r
342                 self.mixedViewButton.Show(self.gcode != None)\r
343                 self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")\r
344                 if self.gcode != None:\r
345                         self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)\r
346                 self.toolbar.Realize()\r
347                 self.Update()\r
348         \r
349         def OnViewChange(self):\r
350                 if self.normalViewButton.GetValue():\r
351                         self.glCanvas.viewMode = "Normal"\r
352                 elif self.transparentViewButton.GetValue():\r
353                         self.glCanvas.viewMode = "Transparent"\r
354                 elif self.xrayViewButton.GetValue():\r
355                         self.glCanvas.viewMode = "X-Ray"\r
356                 elif self.gcodeViewButton.GetValue():\r
357                         self.glCanvas.viewMode = "GCode"\r
358                 elif self.mixedViewButton.GetValue():\r
359                         self.glCanvas.viewMode = "Mixed"\r
360                 self.glCanvas.drawBorders = self.showBorderButton.GetValue()\r
361                 self.updateToolbar()\r
362                 self.glCanvas.Refresh()\r
363         \r
364         def updateModelTransform(self, f=0):\r
365                 if len(self.objectList) < 1 or self.objectList[0].mesh == None:\r
366                         return\r
367                 \r
368                 rotate = profile.getProfileSettingFloat('model_rotate_base')\r
369                 mirrorX = profile.getProfileSetting('flip_x') == 'True'\r
370                 mirrorY = profile.getProfileSetting('flip_y') == 'True'\r
371                 mirrorZ = profile.getProfileSetting('flip_z') == 'True'\r
372                 swapXZ = profile.getProfileSetting('swap_xz') == 'True'\r
373                 swapYZ = profile.getProfileSetting('swap_yz') == 'True'\r
374 \r
375                 for obj in self.objectList:\r
376                         if obj.mesh == None:\r
377                                 continue\r
378                         obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)\r
379                 \r
380                 minV = self.objectList[0].mesh.getMinimum()\r
381                 maxV = self.objectList[0].mesh.getMaximum()\r
382                 for obj in self.objectList:\r
383                         if obj.mesh == None:\r
384                                 continue\r
385 \r
386                         obj.mesh.getMinimumZ()\r
387                         minV = numpy.minimum(minV, obj.mesh.getMinimum())\r
388                         maxV = numpy.maximum(maxV, obj.mesh.getMaximum())\r
389 \r
390                 self.objectsMaxV = maxV\r
391                 self.objectsMinV = minV\r
392                 for obj in self.objectList:\r
393                         if obj.mesh == None:\r
394                                 continue\r
395 \r
396                         obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])\r
397                         #for v in obj.mesh.vertexes:\r
398                         #       v[2] -= minV[2]\r
399                         #       v[0] -= minV[0] + (maxV[0] - minV[0]) / 2\r
400                         #       v[1] -= minV[1] + (maxV[1] - minV[1]) / 2\r
401                         obj.mesh.getMinimumZ()\r
402                         obj.dirty = True\r
403                 self.glCanvas.Refresh()\r
404         \r
405         def updateProfileToControls(self):\r
406                 self.scale.SetValue(profile.getProfileSetting('model_scale'))\r
407                 self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))\r
408                 self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')\r
409                 self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')\r
410                 self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')\r
411                 self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')\r
412                 self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')\r
413                 self.updateModelTransform()\r
414                 self.glCanvas.updateProfileToControls()\r
415 \r
416 class PreviewGLCanvas(glcanvas.GLCanvas):\r
417         def __init__(self, parent):\r
418                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
419                 glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
420                 self.parent = parent\r
421                 self.context = glcanvas.GLContext(self)\r
422                 wx.EVT_PAINT(self, self.OnPaint)\r
423                 wx.EVT_SIZE(self, self.OnSize)\r
424                 wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
425                 wx.EVT_MOTION(self, self.OnMouseMotion)\r
426                 wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
427                 self.yaw = 30\r
428                 self.pitch = 60\r
429                 self.zoom = 300\r
430                 self.offsetX = 0\r
431                 self.offsetY = 0\r
432                 self.view3D = True\r
433                 self.gcodeDisplayList = None\r
434                 self.gcodeDisplayListMade = None\r
435                 self.gcodeDisplayListCount = 0\r
436                 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
437                 self.oldX = 0\r
438                 self.oldY = 0\r
439                 self.dragType = ''\r
440                 self.tempRotate = 0\r
441         \r
442         def updateProfileToControls(self):\r
443                 self.objColor[0] = profile.getPreferenceColour('model_colour')\r
444                 self.objColor[1] = profile.getPreferenceColour('model_colour2')\r
445                 self.objColor[2] = profile.getPreferenceColour('model_colour3')\r
446                 self.objColor[3] = profile.getPreferenceColour('model_colour3')\r
447 \r
448         def OnMouseMotion(self,e):\r
449                 cursorXY = 100000\r
450                 sizeXY = 0\r
451                 if self.parent.objectsMaxV != None:\r
452                         size = (self.parent.objectsMaxV - self.parent.objectsMinV)\r
453                         sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))\r
454                         \r
455                         p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))\r
456                         p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))\r
457                         cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))\r
458                         cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))\r
459                         if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:\r
460                                 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))\r
461                         else:\r
462                                 self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))\r
463 \r
464                 if e.Dragging() and e.LeftIsDown():\r
465                         if self.dragType == '':\r
466                                 #Define the drag type depending on the cursor position.\r
467                                 if cursorXY >= sizeXY * 0.7 and cursorXY <= sizeXY * 0.7 + 3 and False:\r
468                                         self.dragType = 'modelRotate'\r
469                                         self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])\r
470                                 else:\r
471                                         self.dragType = 'viewRotate'\r
472                                 \r
473                         if self.dragType == 'viewRotate':\r
474                                 if self.view3D:\r
475                                         self.yaw += e.GetX() - self.oldX\r
476                                         self.pitch -= e.GetY() - self.oldY\r
477                                         if self.pitch > 170:\r
478                                                 self.pitch = 170\r
479                                         if self.pitch < 10:\r
480                                                 self.pitch = 10\r
481                                 else:\r
482                                         self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
483                                         self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
484                         elif self.dragType == 'modelRotate':\r
485                                 angle = math.atan2(cursorZ0[0], cursorZ0[1])\r
486                                 diff = self.dragStart - angle\r
487                                 self.tempRotate = diff * 180 / math.pi\r
488                         #Workaround for buggy ATI cards.\r
489                         size = self.GetSizeTuple()\r
490                         self.SetSize((size[0]+1, size[1]))\r
491                         self.SetSize((size[0], size[1]))\r
492                         self.Refresh()\r
493                 else:\r
494                         if self.tempRotate != 0:\r
495                                 profile.putProfileSetting('model_rotate_base', profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate)\r
496                                 self.parent.updateModelTransform()\r
497                                 self.tempRotate = 0\r
498                                 \r
499                         self.dragType = ''\r
500                 if e.Dragging() and e.RightIsDown():\r
501                         self.zoom += e.GetY() - self.oldY\r
502                         if self.zoom < 1:\r
503                                 self.zoom = 1\r
504                         if self.zoom > 500:\r
505                                 self.zoom = 500\r
506                         self.Refresh()\r
507                 self.oldX = e.GetX()\r
508                 self.oldY = e.GetY()\r
509 \r
510                 #self.Refresh()\r
511                 \r
512         \r
513         def OnMouseWheel(self,e):\r
514                 self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
515                 if self.zoom < 1.0:\r
516                         self.zoom = 1.0\r
517                 if self.zoom > 500:\r
518                         self.zoom = 500\r
519                 self.Refresh()\r
520         \r
521         def OnEraseBackground(self,event):\r
522                 #Workaround for windows background redraw flicker.\r
523                 pass\r
524         \r
525         def OnSize(self,e):\r
526                 self.Refresh()\r
527 \r
528         def OnPaint(self,e):\r
529                 dc = wx.PaintDC(self)\r
530                 if not hasOpenGLlibs:\r
531                         dc.Clear()\r
532                         dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
533                         return\r
534                 self.SetCurrent(self.context)\r
535                 opengl.InitGL(self, self.view3D, self.zoom)\r
536                 if self.view3D:\r
537                         glTranslate(0,0,-self.zoom)\r
538                         glRotate(-self.pitch, 1,0,0)\r
539                         glRotate(self.yaw, 0,0,1)\r
540                         if self.viewMode == "GCode" or self.viewMode == "Mixed":\r
541                                 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
542                                         glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)\r
543                         else:\r
544                                 if self.parent.objectsMaxV != None:\r
545                                         glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)\r
546                 else:\r
547                         glScale(1.0/self.zoom, 1.0/self.zoom, 1.0)\r
548                         glTranslate(self.offsetX, self.offsetY, 0.0)\r
549 \r
550                 self.viewport = glGetIntegerv(GL_VIEWPORT);\r
551                 self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);\r
552                 self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);\r
553 \r
554                 glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)\r
555 \r
556                 self.OnDraw()\r
557                 self.SwapBuffers()\r
558 \r
559         def OnDraw(self):\r
560                 machineSize = self.parent.machineSize\r
561 \r
562                 if self.parent.gcode != None and self.parent.gcodeDirty:\r
563                         if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:\r
564                                 if self.gcodeDisplayList != None:\r
565                                         glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)\r
566                                 self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));\r
567                                 self.gcodeDisplayListCount = len(self.parent.gcode.layerList)\r
568                         self.parent.gcodeDirty = False\r
569                         self.gcodeDisplayListMade = 0\r
570                 \r
571                 if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):\r
572                         glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)\r
573                         opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])\r
574                         glEndList()\r
575                         self.gcodeDisplayListMade += 1\r
576                         wx.CallAfter(self.Refresh)\r
577                 \r
578                 glPushMatrix()\r
579                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
580                 for obj in self.parent.objectList:\r
581                         if obj.mesh == None:\r
582                                 continue\r
583                         if obj.displayList == None:\r
584                                 obj.displayList = glGenLists(1);\r
585                         if obj.dirty:\r
586                                 obj.dirty = False\r
587                                 glNewList(obj.displayList, GL_COMPILE)\r
588                                 opengl.DrawMesh(obj.mesh)\r
589                                 glEndList()\r
590                         \r
591                         if self.viewMode == "Mixed":\r
592                                 glDisable(GL_BLEND)\r
593                                 glColor3f(0.0,0.0,0.0)\r
594                                 self.drawModel(obj)\r
595                                 glColor3f(1.0,1.0,1.0)\r
596                                 glClear(GL_DEPTH_BUFFER_BIT)\r
597                 \r
598                 glPopMatrix()\r
599                 \r
600                 if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):\r
601                         glEnable(GL_COLOR_MATERIAL)\r
602                         glEnable(GL_LIGHTING)\r
603                         drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)\r
604                         starttime = time.time()\r
605                         for i in xrange(drawUpToLayer - 1, -1, -1):\r
606                                 c = 1.0\r
607                                 if i < self.parent.layerSpin.GetValue():\r
608                                         c = 0.9 - (drawUpToLayer - i) * 0.1\r
609                                         if c < 0.4:\r
610                                                 c = (0.4 + c) / 2\r
611                                         if c < 0.1:\r
612                                                 c = 0.1\r
613                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])\r
614                                 glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])\r
615                                 glCallList(self.gcodeDisplayList + i)\r
616                                 if time.time() - starttime > 0.1:\r
617                                         break\r
618 \r
619                         glDisable(GL_LIGHTING)\r
620                         glDisable(GL_COLOR_MATERIAL)\r
621                         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);\r
622                         glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);\r
623 \r
624                 glColor3f(1.0,1.0,1.0)\r
625                 glPushMatrix()\r
626                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
627                 for obj in self.parent.objectList:\r
628                         if obj.mesh == None:\r
629                                 continue\r
630                         \r
631                         if self.viewMode == "Transparent" or self.viewMode == "Mixed":\r
632                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))\r
633                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))\r
634                                 #If we want transparent, then first render a solid black model to remove the printer size lines.\r
635                                 if self.viewMode != "Mixed":\r
636                                         glDisable(GL_BLEND)\r
637                                         glColor3f(0.0,0.0,0.0)\r
638                                         self.drawModel(obj)\r
639                                         glColor3f(1.0,1.0,1.0)\r
640                                 #After the black model is rendered, render the model again but now with lighting and no depth testing.\r
641                                 glDisable(GL_DEPTH_TEST)\r
642                                 glEnable(GL_LIGHTING)\r
643                                 glEnable(GL_BLEND)\r
644                                 glBlendFunc(GL_ONE, GL_ONE)\r
645                                 glEnable(GL_LIGHTING)\r
646                                 self.drawModel(obj)\r
647                                 glEnable(GL_DEPTH_TEST)\r
648                         elif self.viewMode == "X-Ray":\r
649                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
650                                 glDisable(GL_LIGHTING)\r
651                                 glDisable(GL_DEPTH_TEST)\r
652                                 glEnable(GL_STENCIL_TEST)\r
653                                 glStencilFunc(GL_ALWAYS, 1, 1)\r
654                                 glStencilOp(GL_INCR, GL_INCR, GL_INCR)\r
655                                 self.drawModel(obj)\r
656                                 glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);\r
657                                 \r
658                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
659                                 glStencilFunc(GL_EQUAL, 0, 1)\r
660                                 glColor(1, 1, 1)\r
661                                 self.drawModel(obj)\r
662                                 glStencilFunc(GL_EQUAL, 1, 1)\r
663                                 glColor(1, 0, 0)\r
664                                 self.drawModel(obj)\r
665 \r
666                                 glPushMatrix()\r
667                                 glLoadIdentity()\r
668                                 for i in xrange(2, 15, 2):\r
669                                         glStencilFunc(GL_EQUAL, i, 0xFF);\r
670                                         glColor(float(i)/10, float(i)/10, float(i)/5)\r
671                                         glBegin(GL_QUADS)\r
672                                         glVertex3f(-1000,-1000,-1)\r
673                                         glVertex3f( 1000,-1000,-1)\r
674                                         glVertex3f( 1000, 1000,-1)\r
675                                         glVertex3f(-1000, 1000,-1)\r
676                                         glEnd()\r
677                                 for i in xrange(1, 15, 2):\r
678                                         glStencilFunc(GL_EQUAL, i, 0xFF);\r
679                                         glColor(float(i)/10, 0, 0)\r
680                                         glBegin(GL_QUADS)\r
681                                         glVertex3f(-1000,-1000,-1)\r
682                                         glVertex3f( 1000,-1000,-1)\r
683                                         glVertex3f( 1000, 1000,-1)\r
684                                         glVertex3f(-1000, 1000,-1)\r
685                                         glEnd()\r
686                                 glPopMatrix()\r
687 \r
688                                 glDisable(GL_STENCIL_TEST)\r
689                                 glEnable(GL_DEPTH_TEST)\r
690                                 \r
691                                 #Fix the depth buffer\r
692                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
693                                 self.drawModel(obj)\r
694                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
695                         elif self.viewMode == "Normal":\r
696                                 glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])\r
697                                 glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))\r
698                                 glEnable(GL_LIGHTING)\r
699                                 self.drawModel(obj)\r
700 \r
701                         if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):\r
702                                 glEnable(GL_DEPTH_TEST)\r
703                                 glDisable(GL_LIGHTING)\r
704                                 glColor3f(1,1,1)\r
705                                 glPushMatrix()\r
706                                 modelScale = profile.getProfileSettingFloat('model_scale')\r
707                                 glScalef(modelScale, modelScale, modelScale)\r
708                                 opengl.DrawMeshOutline(obj.mesh)\r
709                                 glPopMatrix()\r
710                 \r
711                 glPopMatrix()   \r
712                 if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":\r
713                         glDisable(GL_LIGHTING)\r
714                         glDisable(GL_DEPTH_TEST)\r
715                         glDisable(GL_BLEND)\r
716                         glColor3f(1,0,0)\r
717                         glBegin(GL_LINES)\r
718                         for err in self.parent.errorList:\r
719                                 glVertex3f(err[0].x, err[0].y, err[0].z)\r
720                                 glVertex3f(err[1].x, err[1].y, err[1].z)\r
721                         glEnd()\r
722                         glEnable(GL_DEPTH_TEST)\r
723 \r
724                 opengl.DrawMachine(machineSize)\r
725 \r
726                 glPushMatrix()\r
727                 glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
728                 \r
729                 #Draw the rotate circle\r
730                 if self.parent.objectsMaxV != None and False:\r
731                         glDisable(GL_LIGHTING)\r
732                         glDisable(GL_CULL_FACE)\r
733                         glEnable(GL_BLEND)\r
734                         glBegin(GL_TRIANGLE_STRIP)\r
735                         size = (self.parent.objectsMaxV - self.parent.objectsMinV)\r
736                         sizeXY = math.sqrt((size[0] * size[0]) + (size[1] * size[1]))\r
737                         for i in xrange(0, 64+1):\r
738                                 f = i if i < 64/2 else 64 - i\r
739                                 glColor4ub(255,int(f*255/(64/2)),0,128)\r
740                                 glVertex3f(sizeXY * 0.7 * math.cos(i/32.0*math.pi), sizeXY * 0.7 * math.sin(i/32.0*math.pi),0.1)\r
741                                 glColor4ub(  0,128,0,128)\r
742                                 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
743                         glEnd()\r
744                         glEnable(GL_CULL_FACE)\r
745                 \r
746                 glPopMatrix()\r
747                 \r
748                 glFlush()\r
749         \r
750         def drawModel(self, obj):\r
751                 multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))\r
752                 multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))\r
753                 modelScale = profile.getProfileSettingFloat('model_scale')\r
754                 modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale\r
755                 glPushMatrix()\r
756                 glRotate(self.tempRotate, 0, 0, 1)\r
757                 glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)\r
758                 for mx in xrange(0, multiX):\r
759                         for my in xrange(0, multiY):\r
760                                 glPushMatrix()\r
761                                 glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)\r
762                                 glScalef(modelScale, modelScale, modelScale)\r
763                                 glCallList(obj.displayList)\r
764                                 glPopMatrix()\r
765                 glPopMatrix()\r
766 \r