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