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