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