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