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