chiark / gitweb /
d875eb768fc5065f0a004ecc3f7dd6ea35d4d253
[cura.git] / Cura / gui / sceneView.py
1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2
3 import wx
4 import numpy
5 import time
6 import os
7 import traceback
8 import threading
9 import math
10 import sys
11 import cStringIO as StringIO
12
13 import OpenGL
14 OpenGL.ERROR_CHECKING = False
15 from OpenGL.GLU import *
16 from OpenGL.GL import *
17
18 from Cura.gui import printWindow
19 from Cura.util import profile
20 from Cura.util import meshLoader
21 from Cura.util import objectScene
22 from Cura.util import resources
23 from Cura.util import sliceEngine
24 from Cura.util import pluginInfo
25 from Cura.util import removableStorage
26 from Cura.util import explorer
27 from Cura.util.printerConnection import printerConnectionManager
28 from Cura.gui.util import previewTools
29 from Cura.gui.util import openglHelpers
30 from Cura.gui.util import openglGui
31 from Cura.gui.util import engineResultView
32 from Cura.gui.tools import youmagineGui
33 from Cura.gui.tools import imageToMesh
34
35 class SceneView(openglGui.glGuiPanel):
36         def __init__(self, parent):
37                 super(SceneView, self).__init__(parent)
38
39                 self._yaw = 30
40                 self._pitch = 60
41                 self._zoom = 300
42                 self._scene = objectScene.Scene(self)
43                 self._objectShader = None
44                 self._objectLoadShader = None
45                 self._focusObj = None
46                 self._selectedObj = None
47                 self._objColors = [None,None,None,None]
48                 self._mouseX = -1
49                 self._mouseY = -1
50                 self._mouseState = None
51                 self._viewTarget = numpy.array([0,0,0], numpy.float32)
52                 self._animView = None
53                 self._animZoom = None
54                 self._lastObjectSink = None
55                 self._platformMesh = {}
56                 self._platformTexture = None
57                 self._isSimpleMode = True
58                 self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
59
60                 self._viewport = None
61                 self._modelMatrix = None
62                 self._projMatrix = None
63                 self.tempMatrix = None
64
65                 self.openFileButton      = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
66                 self.printButton         = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
67                 self.printButton.setDisabled(True)
68
69                 group = []
70                 self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
71                 self.scaleToolButton  = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
72                 self.mirrorToolButton  = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
73
74                 self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
75                 self.layFlatButton       = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
76
77                 self.resetScaleButton    = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
78                 self.scaleMaxButton      = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
79
80                 self.mirrorXButton       = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
81                 self.mirrorYButton       = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
82                 self.mirrorZButton       = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
83
84                 self.rotateToolButton.setExpandArrow(True)
85                 self.scaleToolButton.setExpandArrow(True)
86                 self.mirrorToolButton.setExpandArrow(True)
87
88                 self.scaleForm = openglGui.glFrame(self, (2, -2))
89                 openglGui.glGuiLayoutGrid(self.scaleForm)
90                 openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
91                 self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
92                 openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
93                 self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
94                 openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
95                 self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
96                 openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
97                 self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
98                 openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
99                 self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
100                 openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
101                 self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
102                 openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
103                 self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
104
105                 self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
106
107                 self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
108                 self.youMagineButton.setDisabled(True)
109
110                 self.notification = openglGui.glNotification(self, (0, 0))
111
112                 self._engine = sliceEngine.Engine(self._updateEngineProgress)
113                 self._engineResultView = engineResultView.engineResultView(self)
114                 self._sceneUpdateTimer = wx.Timer(self)
115                 self.Bind(wx.EVT_TIMER, self._onRunEngine, self._sceneUpdateTimer)
116                 self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
117                 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
118
119                 self.OnViewChange()
120                 self.OnToolSelect(0)
121                 self.updateToolButtons()
122                 self.updateProfileToControls()
123
124
125
126         def loadGCodeFile(self, filename):
127                 self.OnDeleteAll(None)
128                 #Cheat the engine results to load a GCode file into it.
129                 self._engine._result = sliceEngine.EngineResult()
130                 with open(filename, "r") as f:
131                         self._engine._result.setGCode(f.read())
132                 self._engine._result.setFinished(True)
133                 self._engineResultView.setResult(self._engine._result)
134                 self.printButton.setBottomText('')
135                 self.viewSelection.setValue(4)
136                 self.printButton.setDisabled(False)
137                 self.youMagineButton.setDisabled(True)
138                 self.OnViewChange()
139
140         def loadSceneFiles(self, filenames):
141                 self.youMagineButton.setDisabled(False)
142                 #if self.viewSelection.getValue() == 4:
143                 #       self.viewSelection.setValue(0)
144                 #       self.OnViewChange()
145                 self.loadScene(filenames)
146
147         def loadFiles(self, filenames):
148                 mainWindow = self.GetParent().GetParent().GetParent()
149                 # only one GCODE file can be active
150                 # so if single gcode file, process this
151                 # otherwise ignore all gcode files
152                 gcodeFilename = None
153                 if len(filenames) == 1:
154                         filename = filenames[0]
155                         ext = os.path.splitext(filename)[1].lower()
156                         if ext == '.g' or ext == '.gcode':
157                                 gcodeFilename = filename
158                                 mainWindow.addToModelMRU(filename)
159                 if gcodeFilename is not None:
160                         self.loadGCodeFile(gcodeFilename)
161                 else:
162                         # process directories and special file types
163                         # and keep scene files for later processing
164                         scene_filenames = []
165                         ignored_types = dict()
166                         # use file list as queue
167                         # pop first entry for processing and append new files at end
168                         while filenames:
169                                 filename = filenames.pop(0)
170                                 if os.path.isdir(filename):
171                                         # directory: queue all included files and directories
172                                         filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
173                                 else:
174                                         ext = os.path.splitext(filename)[1].lower()
175                                         if ext == '.ini':
176                                                 profile.loadProfile(filename)
177                                                 mainWindow.addToProfileMRU(filename)
178                                         elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
179                                                 scene_filenames.append(filename)
180                                                 mainWindow.addToModelMRU(filename)
181                                         else:
182                                                 ignored_types[ext] = 1
183                         if ignored_types:
184                                 ignored_types = ignored_types.keys()
185                                 ignored_types.sort()
186                                 self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
187                         mainWindow.updateProfileToAllControls()
188                         # now process all the scene files
189                         if scene_filenames:
190                                 self.loadSceneFiles(scene_filenames)
191                                 self._selectObject(None)
192                                 self.sceneUpdated()
193                                 newZoom = numpy.max(self._machineSize)
194                                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
195                                 self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
196
197         def reloadScene(self, e):
198                 # Copy the list before DeleteAll clears it
199                 fileList = []
200                 pms_transforms = [] #position, rotation matrix, scale
201                 for obj in self._scene.objects():
202                         fileList.append(obj.getOriginFilename())
203                         pms_transforms.append((obj.getPosition(), obj.getMatrix(), obj.getScale()))
204
205                 self.OnDeleteAll(None)
206                 self.loadScene(fileList, pms_transforms)
207
208         def OnResetPositions(self, e):
209
210                 self._scene.arrangeAll(True)
211                 self._scene.centerAll()
212
213
214         def OnResetTransformations(self, e):
215                 for obj in self._scene.objects():
216                         obj.resetScale()
217                         obj.resetRotation()
218
219                 self._scene.arrangeAll()
220                 self._scene.centerAll()
221
222         def showLoadModel(self, button = 1):
223                 if button == 1:
224                         dlg=wx.FileDialog(self, _("Open 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
225
226                         wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions() + imageToMesh.supportedExtensions() + ['.g', '.gcode']))
227                         wildcardFilter = "All (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
228                         wildcardList = ';'.join(map(lambda s: '*' + s, meshLoader.loadSupportedExtensions()))
229                         wildcardFilter += "|Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
230                         wildcardList = ';'.join(map(lambda s: '*' + s, imageToMesh.supportedExtensions()))
231                         wildcardFilter += "|Image files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
232                         wildcardList = ';'.join(map(lambda s: '*' + s, ['.g', '.gcode']))
233                         wildcardFilter += "|GCode files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
234
235                         dlg.SetWildcard(wildcardFilter)
236                         if dlg.ShowModal() != wx.ID_OK:
237                                 dlg.Destroy()
238                                 return
239                         filenames = dlg.GetPaths()
240                         dlg.Destroy()
241                         if len(filenames) < 1:
242                                 return False
243                         profile.putPreference('lastFile', filenames[0])
244                         self.loadFiles(filenames)
245
246         def showSaveModel(self):
247                 if len(self._scene.objects()) < 1:
248                         return
249                 dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
250                 fileExtensions = meshLoader.saveSupportedExtensions()
251                 wildcardList = ';'.join(map(lambda s: '*' + s, fileExtensions))
252                 wildcardFilter = "Mesh files (%s)|%s;%s" % (wildcardList, wildcardList, wildcardList.upper())
253                 dlg.SetWildcard(wildcardFilter)
254                 if dlg.ShowModal() != wx.ID_OK:
255                         dlg.Destroy()
256                         return
257                 filename = dlg.GetPath()
258                 dlg.Destroy()
259                 meshLoader.saveMeshes(filename, self._scene.objects())
260
261         def OnPrintButton(self, button):
262                 if button == 1:
263                         connectionGroup = self._printerConnectionManager.getAvailableGroup()
264                         if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
265                                 drives = removableStorage.getPossibleSDcardDrives()
266                                 if len(drives) > 1:
267                                         dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
268                                         if dlg.ShowModal() != wx.ID_OK:
269                                                 dlg.Destroy()
270                                                 return
271                                         drive = drives[dlg.GetSelection()]
272                                         dlg.Destroy()
273                                 else:
274                                         drive = drives[0]
275                                 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
276                                 
277                                 #check if the file is part of the root folder. If so, create folders on sd card to get the same folder hierarchy.
278                                 repDir = profile.getPreference("sdcard_rootfolder")
279                                 try:
280                                         if os.path.exists(repDir) and os.path.isdir(repDir):
281                                                 repDir = os.path.abspath(repDir)
282                                                 originFilename = os.path.abspath( self._scene._objectList[0].getOriginFilename() )
283                                                 if os.path.dirname(originFilename).startswith(repDir):
284                                                         new_filename = os.path.splitext(originFilename[len(repDir):])[0] + profile.getGCodeExtension()
285                                                         sdPath = os.path.dirname(os.path.join(drive[1], new_filename))
286                                                         if not os.path.exists(sdPath):
287                                                                 print "Creating replication directory:", sdPath
288                                                                 os.makedirs(sdPath)
289                                                         filename = new_filename
290                                 except:
291                                         pass
292
293                                 threading.Thread(target=self._saveGCode,args=(drive[1] + filename, drive[1])).start()
294                         elif connectionGroup is not None:
295                                 connections = connectionGroup.getAvailableConnections()
296                                 if len(connections) < 2:
297                                         connection = connections[0]
298                                 else:
299                                         dlg = wx.SingleChoiceDialog(self, "Select the %s connection to use" % (connectionGroup.getName()), "Multiple %s connections found" % (connectionGroup.getName()), map(lambda n: n.getName(), connections))
300                                         if dlg.ShowModal() != wx.ID_OK:
301                                                 dlg.Destroy()
302                                                 return
303                                         connection = connections[dlg.GetSelection()]
304                                         dlg.Destroy()
305                                 self._openPrintWindowForConnection(connection)
306                         else:
307                                 self.showSaveGCode()
308                 if button == 3:
309                         menu = wx.Menu()
310                         connections = self._printerConnectionManager.getAvailableConnections()
311                         menu.connectionMap = {}
312                         for connection in connections:
313                                 i = menu.Append(-1, _("Print with %s") % (connection.getName()))
314                                 menu.connectionMap[i.GetId()] = connection
315                                 self.Bind(wx.EVT_MENU, lambda e: self._openPrintWindowForConnection(e.GetEventObject().connectionMap[e.GetId()]), i)
316                         self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
317                         self.Bind(wx.EVT_MENU, lambda e: self._showEngineLog(), menu.Append(-1, _("Slice engine log...")))
318                         self.PopupMenu(menu)
319                         menu.Destroy()
320
321         def _openPrintWindowForConnection(self, connection):
322                 if connection.window is None or not connection.window:
323                         connection.window = None
324                         windowType = profile.getPreference('printing_window')
325                         for p in pluginInfo.getPluginList('printwindow'):
326                                 if p.getName() == windowType:
327                                         connection.window = printWindow.printWindowPlugin(self, connection, p.getFullFilename())
328                                         break
329                         if connection.window is None:
330                                 connection.window = printWindow.printWindowBasic(self, connection)
331                 connection.window.Show()
332                 connection.window.Raise()
333                 if not connection.loadGCodeData(self._engine.getResult().getGCode()):
334                         if connection.isPrinting():
335                                 self.notification.message("Cannot start print, because other print still running.")
336                         else:
337                                 self.notification.message("Failed to start print...")
338
339         def showSaveGCode(self):
340                 if len(self._scene._objectList) < 1:
341                         return
342                 if not self._engine.getResult().isFinished():
343                         return
344                 dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
345                 filename = self._scene._objectList[0].getName() + profile.getGCodeExtension()
346                 dlg.SetFilename(filename)
347                 dlg.SetWildcard('Toolpath (*%s)|*%s;*%s' % (profile.getGCodeExtension(), profile.getGCodeExtension(), profile.getGCodeExtension()[0:2]))
348                 if dlg.ShowModal() != wx.ID_OK:
349                         dlg.Destroy()
350                         return
351                 filename = dlg.GetPath()
352                 dlg.Destroy()
353
354                 threading.Thread(target=self._saveGCode,args=(filename,)).start()
355
356         def _saveGCode(self, targetFilename, ejectDrive = False):
357                 gcode = self._engine.getResult().getGCode()
358                 try:
359                         size = float(len(gcode))
360                         read_pos = 0
361                         with open(targetFilename, 'wb') as fdst:
362                                 while 1:
363                                         buf = gcode.read(16*1024)
364                                         if len(buf) < 1:
365                                                 break
366                                         read_pos += len(buf)
367                                         fdst.write(buf)
368                                         self.printButton.setProgressBar(read_pos / size)
369                                         self._queueRefresh()
370                 except:
371                         import sys, traceback
372                         traceback.print_exc()
373                         self.notification.message("Failed to save")
374                 else:
375                         if ejectDrive:
376                                 self.notification.message("Saved as %s" % (targetFilename), lambda : self._doEjectSD(ejectDrive), 31, 'Eject')
377                         elif explorer.hasExplorer():
378                                 self.notification.message("Saved as %s" % (targetFilename), lambda : explorer.openExplorer(targetFilename), 4, 'Open folder')
379                         else:
380                                 self.notification.message("Saved as %s" % (targetFilename))
381                 self.printButton.setProgressBar(None)
382                 self._engine.getResult().submitInfoOnline()
383
384         def _doEjectSD(self, drive):
385                 if removableStorage.ejectDrive(drive):
386                         self.notification.message('You can now eject the card.')
387                 else:
388                         self.notification.message('Safe remove failed...')
389
390         def _showEngineLog(self):
391                 dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._engine.getResult().getLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
392                 dlg.ShowModal()
393                 dlg.Destroy()
394
395         def OnToolSelect(self, button):
396                 if self.rotateToolButton.getSelected():
397                         self.tool = previewTools.toolRotate(self)
398                 elif self.scaleToolButton.getSelected():
399                         self.tool = previewTools.toolScale(self)
400                 elif self.mirrorToolButton.getSelected():
401                         self.tool = previewTools.toolNone(self)
402                 else:
403                         self.tool = previewTools.toolNone(self)
404                 self.resetRotationButton.setHidden(not self.rotateToolButton.getSelected())
405                 self.layFlatButton.setHidden(not self.rotateToolButton.getSelected())
406                 self.resetScaleButton.setHidden(not self.scaleToolButton.getSelected())
407                 self.scaleMaxButton.setHidden(not self.scaleToolButton.getSelected())
408                 self.scaleForm.setHidden(not self.scaleToolButton.getSelected())
409                 self.mirrorXButton.setHidden(not self.mirrorToolButton.getSelected())
410                 self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
411                 self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
412
413         def updateToolButtons(self):
414                 if self._selectedObj is None:
415                         hidden = True
416                 else:
417                         hidden = False
418                 self.rotateToolButton.setHidden(hidden)
419                 self.scaleToolButton.setHidden(hidden)
420                 self.mirrorToolButton.setHidden(hidden)
421                 if hidden:
422                         self.rotateToolButton.setSelected(False)
423                         self.scaleToolButton.setSelected(False)
424                         self.mirrorToolButton.setSelected(False)
425                         self.OnToolSelect(0)
426
427         def OnViewChange(self):
428                 if self.viewSelection.getValue() == 4:
429                         self.viewMode = 'gcode'
430                         self.tool = previewTools.toolNone(self)
431                 elif self.viewSelection.getValue() == 1:
432                         self.viewMode = 'overhang'
433                 elif self.viewSelection.getValue() == 2:
434                         self.viewMode = 'transparent'
435                 elif self.viewSelection.getValue() == 3:
436                         self.viewMode = 'xray'
437                 else:
438                         self.viewMode = 'normal'
439                 self._engineResultView.setEnabled(self.viewMode == 'gcode')
440                 self.QueueRefresh()
441
442         def OnRotateReset(self, button):
443                 if self._selectedObj is None:
444                         return
445                 self._selectedObj.resetRotation()
446                 self._scene.pushFree(self._selectedObj)
447                 self._selectObject(self._selectedObj)
448                 self.sceneUpdated()
449
450         def OnLayFlat(self, button):
451                 if self._selectedObj is None:
452                         return
453                 self._selectedObj.layFlat()
454                 self._scene.pushFree(self._selectedObj)
455                 self._selectObject(self._selectedObj)
456                 self.sceneUpdated()
457
458         def OnScaleReset(self, button):
459                 if self._selectedObj is None:
460                         return
461                 self._selectedObj.resetScale()
462                 self._selectObject(self._selectedObj)
463                 self.updateProfileToControls()
464                 self.sceneUpdated()
465
466         def OnScaleMax(self, button):
467                 if self._selectedObj is None:
468                         return
469                 machine = profile.getMachineSetting('machine_type')
470                 self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
471                 self._scene.pushFree(self._selectedObj)
472                 #self.sceneUpdated()
473                 if machine == "ultimaker2":
474                         #This is bad and Jaime should feel bad!
475                         self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
476                         self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
477                         self._selectedObj.setPosition(numpy.array([0.0,0.0]))
478                         self._scene.pushFree(self._selectedObj)
479                 else:
480                         self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
481                         self._scene.pushFree(self._selectedObj)
482                         self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([3,3,3], numpy.float32))
483                 self._scene.pushFree(self._selectedObj)
484                 self._selectObject(self._selectedObj)
485                 self.updateProfileToControls()
486                 self.sceneUpdated()
487
488         def OnMirror(self, axis):
489                 if self._selectedObj is None:
490                         return
491                 self._selectedObj.mirror(axis)
492                 self.sceneUpdated()
493
494         def OnScaleEntry(self, value, axis):
495                 if self._selectedObj is None:
496                         return
497                 try:
498                         value = float(value)
499                 except:
500                         return
501                 self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
502                 self.updateProfileToControls()
503                 self._scene.pushFree(self._selectedObj)
504                 self._selectObject(self._selectedObj)
505                 self.sceneUpdated()
506
507         def OnScaleEntryMM(self, value, axis):
508                 if self._selectedObj is None:
509                         return
510                 try:
511                         value = float(value)
512                 except:
513                         return
514                 self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
515                 self.updateProfileToControls()
516                 self._scene.pushFree(self._selectedObj)
517                 self._selectObject(self._selectedObj)
518                 self.sceneUpdated()
519
520         def OnDeleteAll(self, e):
521                 while len(self._scene.objects()) > 0:
522                         self._deleteObject(self._scene.objects()[0])
523                 self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
524                 self._engineResultView.setResult(None)
525
526         def OnMultiply(self, e):
527                 if self._focusObj is None:
528                         return
529                 obj = self._focusObj
530                 dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
531                 if dlg.ShowModal() != wx.ID_OK:
532                         dlg.Destroy()
533                         return
534                 cnt = dlg.GetValue()
535                 dlg.Destroy()
536
537                 # 0:unrequested arrange all. Objects should not move.
538                 # 1:requested arrange all but refused.
539                 # 2:arrange all and center from now on.
540                 requestedArrangeAll = 0
541
542                 n = 0
543                 while True:
544                         n += 1
545                         newObj = obj.copy()
546                         self._scene.add(newObj)
547                         if requestedArrangeAll == 2:
548                                 self._scene.centerAll()
549
550                         if not self._scene.checkPlatform(newObj):
551                                 if requestedArrangeAll == 0:
552                                         requestedArrangeAll = 1
553                                         dlg = wx.MessageDialog(self, _("Cannot fit all the requested duplicates. Do you want to try and reset object positions?"), _("Reset Positions"), wx.YES_NO)
554
555                                         if dlg.ShowModal() == wx.ID_YES:
556                                                 dlg.Destroy()
557                                                 requestedArrangeAll = 2
558                                                 self._scene.remove(newObj)
559                                                 self.OnResetPositions(None)
560                                                 n -= 1
561                                                 continue
562
563                                         dlg.Destroy()
564
565                                 break
566                         if n > cnt:
567                                 break
568                 if n <= cnt:
569                         self.notification.message("Could not create more than %d items" % (n - 1))
570                 self._scene.remove(newObj)
571                 if requestedArrangeAll == 2:
572                         self._scene.centerAll()
573
574                 self.sceneUpdated()
575
576         def OnSplitObject(self, e):
577                 if self._focusObj is None:
578                         return
579                 self._scene.remove(self._focusObj)
580                 for obj in self._focusObj.split(self._splitCallback):
581                         if numpy.max(obj.getSize()) > 2.0:
582                                 self._scene.add(obj)
583                 self._scene.centerAll()
584                 self._selectObject(None)
585                 self.sceneUpdated()
586
587         def OnCenter(self, e):
588                 if self._focusObj is None:
589                         return
590                 self._focusObj.setPosition(numpy.array([0.0, 0.0]))
591                 self._scene.pushFree(self._selectedObj)
592                 newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
593                 self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
594                 self.sceneUpdated()
595
596         def _splitCallback(self, progress):
597                 print progress
598
599         def OnMergeObjects(self, e):
600                 if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
601                         if len(self._scene.objects()) == 2:
602                                 self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
603                                 self.sceneUpdated()
604                         return
605                 self._scene.merge(self._selectedObj, self._focusObj)
606                 self.sceneUpdated()
607
608         def sceneUpdated(self):
609
610                 objectSink = profile.getProfileSettingFloat("object_sink")
611                 if self._lastObjectSink != objectSink:
612                         self._lastObjectSink = objectSink
613                         self._scene.updateHeadSize()
614
615                 wx.CallAfter(self._sceneUpdateTimer.Start, 500, True)
616                 self._engine.abortEngine()
617                 self._scene.updateSizeOffsets()
618                 self.QueueRefresh()
619
620         def _onRunEngine(self, e):
621                 if self._isSimpleMode:
622                         self._engine.runEngine(self._scene, self.GetTopLevelParent().simpleSettingsPanel.getSettingOverrides())
623                 else:
624                         self._engine.runEngine(self._scene)
625
626         def _updateEngineProgress(self, progressValue):
627                 result = self._engine.getResult()
628                 finished = result is not None and result.isFinished()
629                 if not finished:
630                         if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
631                                 return
632                 self.printButton.setDisabled(not finished)
633                 if progressValue >= 0.0:
634                         self.printButton.setProgressBar(progressValue)
635                 else:
636                         self.printButton.setProgressBar(None)
637                 self._engineResultView.setResult(result)
638                 if finished:
639                         self.printButton.setProgressBar(None)
640                         text = '%s' % (result.getPrintTime())
641                         for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
642                                 amount = result.getFilamentAmount(e)
643                                 if amount is None:
644                                         continue
645                                 text += '\n%s' % (amount)
646                                 cost = result.getFilamentCost(e)
647                                 if cost is not None:
648                                         text += '\n%s' % (cost)
649                         self.printButton.setBottomText(text)
650                 else:
651                         self.printButton.setBottomText('')
652                 self.QueueRefresh()
653
654         def loadScene(self, fileList, pms_transforms=None):
655                 objIndex = -1
656                 for filename in fileList:
657                         objIndex += 1
658                         try:
659                                 ext = os.path.splitext(filename)[1].lower()
660                                 if ext in imageToMesh.supportedExtensions():
661                                         imageToMesh.convertImageDialog(self, filename).Show()
662                                         objList = []
663                                 else:
664                                         objList = meshLoader.loadMeshes(filename)
665                         except:
666                                 traceback.print_exc()
667                         else:
668                                 for obj in objList:
669                                         if self._objectLoadShader is not None:
670                                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
671                                         else:
672                                                 obj._loadAnim = None
673                                         self._scene.add(obj)
674                                         if pms_transforms is not None and len(pms_transforms) == len(fileList):
675                                                 obj.setPosition(pms_transforms[objIndex][0])
676                                                 obj.applyMatrix(pms_transforms[objIndex][1])
677                                                 obj.setScale(pms_transforms[objIndex][2][0], 0, False)
678                                                 obj.setScale(pms_transforms[objIndex][2][1], 1, False)
679                                                 obj.setScale(pms_transforms[objIndex][2][2], 2, False)
680                                         else:
681                                                 if not self._scene.checkPlatform(obj):
682                                                         self._scene.centerAll()
683                                                 self._selectObject(obj)
684                                                 if obj.getScale()[0] < 1.0:
685                                                         self.notification.message("Warning: Object scaled down.")
686                 self.sceneUpdated()
687
688         def _deleteObject(self, obj):
689                 if obj == self._selectedObj:
690                         self._selectObject(None)
691                 if obj == self._focusObj:
692                         self._focusObj = None
693                 self._scene.remove(obj)
694                 for m in obj._meshList:
695                         if m.vbo is not None and m.vbo.decRef():
696                                 self.glReleaseList.append(m.vbo)
697                 if len(self._scene.objects()) == 0:
698                         self._engineResultView.setResult(None)
699                 import gc
700                 gc.collect()
701                 self.sceneUpdated()
702
703         def _selectObject(self, obj, zoom = True):
704                 if obj != self._selectedObj:
705                         self._selectedObj = obj
706                         self.updateModelSettingsToControls()
707                         self.updateToolButtons()
708                 if zoom and obj is not None:
709                         newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
710                         self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
711                         newZoom = obj.getBoundaryCircle() * 6
712                         if newZoom > numpy.max(self._machineSize) * 3:
713                                 newZoom = numpy.max(self._machineSize) * 3
714                         self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
715
716         def updateProfileToControls(self):
717                 oldSimpleMode = self._isSimpleMode
718                 self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
719                 if self._isSimpleMode != oldSimpleMode:
720                         self._scene.arrangeAll()
721                         self.sceneUpdated()
722                 self._scene.updateSizeOffsets(True)
723                 self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
724                 self._objColors[0] = profile.getPreferenceColour('model_colour')
725                 self._objColors[1] = profile.getPreferenceColour('model_colour2')
726                 self._objColors[2] = profile.getPreferenceColour('model_colour3')
727                 self._objColors[3] = profile.getPreferenceColour('model_colour4')
728                 self._scene.updateMachineDimensions()
729                 if self._zoom > numpy.max(self._machineSize) * 3:
730                         self._animZoom = openglGui.animation(self, self._zoom, numpy.max(self._machineSize) * 3, 0.5)
731                 self.updateModelSettingsToControls()
732
733         def updateModelSettingsToControls(self):
734                 if self._selectedObj is not None:
735                         scale = self._selectedObj.getScale()
736                         size = self._selectedObj.getSize()
737                         self.scaleXctrl.setValue(round(scale[0], 2))
738                         self.scaleYctrl.setValue(round(scale[1], 2))
739                         self.scaleZctrl.setValue(round(scale[2], 2))
740                         self.scaleXmmctrl.setValue(round(size[0], 2))
741                         self.scaleYmmctrl.setValue(round(size[1], 2))
742                         self.scaleZmmctrl.setValue(round(size[2], 2))
743
744         def OnKeyChar(self, keyCode):
745                 if self._engineResultView.OnKeyChar(keyCode):
746                         return
747                 if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and sys.platform.startswith("darwin")):
748                         if self._selectedObj is not None:
749                                 self._deleteObject(self._selectedObj)
750                                 self.QueueRefresh()
751                 if keyCode == wx.WXK_UP:
752                         if wx.GetKeyState(wx.WXK_SHIFT):
753                                 self._zoom /= 1.2
754                                 if self._zoom < 1:
755                                         self._zoom = 1
756                         else:
757                                 self._pitch -= 15
758                         self.QueueRefresh()
759                 elif keyCode == wx.WXK_DOWN:
760                         if wx.GetKeyState(wx.WXK_SHIFT):
761                                 self._zoom *= 1.2
762                                 if self._zoom > numpy.max(self._machineSize) * 3:
763                                         self._zoom = numpy.max(self._machineSize) * 3
764                         else:
765                                 self._pitch += 15
766                         self.QueueRefresh()
767                 elif keyCode == wx.WXK_LEFT:
768                         self._yaw -= 15
769                         self.QueueRefresh()
770                 elif keyCode == wx.WXK_RIGHT:
771                         self._yaw += 15
772                         self.QueueRefresh()
773                 elif keyCode == wx.WXK_NUMPAD_ADD or keyCode == wx.WXK_ADD or keyCode == ord('+') or keyCode == ord('='):
774                         self._zoom /= 1.2
775                         if self._zoom < 1:
776                                 self._zoom = 1
777                         self.QueueRefresh()
778                 elif keyCode == wx.WXK_NUMPAD_SUBTRACT or keyCode == wx.WXK_SUBTRACT or keyCode == ord('-'):
779                         self._zoom *= 1.2
780                         if self._zoom > numpy.max(self._machineSize) * 3:
781                                 self._zoom = numpy.max(self._machineSize) * 3
782                         self.QueueRefresh()
783                 elif keyCode == wx.WXK_HOME:
784                         self._yaw = 30
785                         self._pitch = 60
786                         self.QueueRefresh()
787                 elif keyCode == wx.WXK_PAGEUP:
788                         self._yaw = 0
789                         self._pitch = 0
790                         self.QueueRefresh()
791                 elif keyCode == wx.WXK_PAGEDOWN:
792                         self._yaw = 0
793                         self._pitch = 90
794                         self.QueueRefresh()
795                 elif keyCode == wx.WXK_END:
796                         self._yaw = 90
797                         self._pitch = 90
798                         self.QueueRefresh()
799
800                 if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
801                         shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
802                 if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
803                         from collections import defaultdict
804                         from gc import get_objects
805                         self._beforeLeakTest = defaultdict(int)
806                         for i in get_objects():
807                                 self._beforeLeakTest[type(i)] += 1
808                 if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
809                         from collections import defaultdict
810                         from gc import get_objects
811                         self._afterLeakTest = defaultdict(int)
812                         for i in get_objects():
813                                 self._afterLeakTest[type(i)] += 1
814                         for k in self._afterLeakTest:
815                                 if self._afterLeakTest[k]-self._beforeLeakTest[k]:
816                                         print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
817
818         def ShaderUpdate(self, v, f):
819                 s = openglHelpers.GLShader(v, f)
820                 if s.isValid():
821                         self._objectLoadShader.release()
822                         self._objectLoadShader = s
823                         for obj in self._scene.objects():
824                                 obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
825                         self.QueueRefresh()
826
827         def OnMouseDown(self,e):
828                 self._mouseX = e.GetX()
829                 self._mouseY = e.GetY()
830                 self._mouseClick3DPos = self._mouse3Dpos
831                 self._mouseClickFocus = self._focusObj
832                 if e.ButtonDClick():
833                         self._mouseState = 'doubleClick'
834                 else:
835                         if self._mouseState == 'dragObject' and self._selectedObj is not None:
836                                 self._scene.pushFree(self._selectedObj)
837                                 self.sceneUpdated()
838                         self._mouseState = 'dragOrClick'
839                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
840                 p0 -= self.getObjectCenterPos() - self._viewTarget
841                 p1 -= self.getObjectCenterPos() - self._viewTarget
842                 if self.tool.OnDragStart(p0, p1):
843                         self._mouseState = 'tool'
844                 if self._mouseState == 'dragOrClick':
845                         if e.GetButton() == 1:
846                                 if self._focusObj is not None:
847                                         self._selectObject(self._focusObj, False)
848                                         self.QueueRefresh()
849
850         def OnMouseUp(self, e):
851                 if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
852                         return
853                 if self._mouseState == 'dragOrClick':
854                         if e.GetButton() == 1:
855                                 self._selectObject(self._focusObj)
856                         if e.GetButton() == 3:
857                                         menu = wx.Menu()
858                                         if self._focusObj is not None:
859
860                                                 self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
861                                                 self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
862                                                 self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
863                                                 self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
864
865                                         if ((self._selectedObj != self._focusObj and self._focusObj is not None and self._selectedObj is not None) or len(self._scene.objects()) == 2) and int(profile.getMachineSetting('extruder_amount')) > 1:
866                                                 self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
867                                         if len(self._scene.objects()) > 0:
868                                                 self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
869                                                 self.Bind(wx.EVT_MENU, self.reloadScene, menu.Append(-1, _("Reload all objects")))
870                                                 self.Bind(wx.EVT_MENU, self.OnResetPositions, menu.Append(-1, _("Reset all objects positions")))
871                                                 self.Bind(wx.EVT_MENU, self.OnResetTransformations, menu.Append(-1, _("Reset all objects transformations")))
872
873                                         if menu.MenuItemCount > 0:
874                                                 self.PopupMenu(menu)
875                                         menu.Destroy()
876                 elif self._mouseState == 'dragObject' and self._selectedObj is not None:
877                         self._scene.pushFree(self._selectedObj)
878                         self.sceneUpdated()
879                 elif self._mouseState == 'tool':
880                         if self.tempMatrix is not None and self._selectedObj is not None:
881                                 self._selectedObj.applyMatrix(self.tempMatrix)
882                                 self._scene.pushFree(self._selectedObj)
883                                 self._selectObject(self._selectedObj)
884                         self.tempMatrix = None
885                         self.tool.OnDragEnd()
886                         self.sceneUpdated()
887                 self._mouseState = None
888
889         def OnMouseMotion(self,e):
890                 p0, p1 = self.getMouseRay(e.GetX(), e.GetY())
891                 p0 -= self.getObjectCenterPos() - self._viewTarget
892                 p1 -= self.getObjectCenterPos() - self._viewTarget
893
894                 if e.Dragging() and self._mouseState is not None:
895                         if self._mouseState == 'tool':
896                                 self.tool.OnDrag(p0, p1)
897                         elif not e.LeftIsDown() and e.RightIsDown():
898                                 self._mouseState = 'drag'
899                                 if wx.GetKeyState(wx.WXK_SHIFT):
900                                         a = math.cos(math.radians(self._yaw)) / 3.0
901                                         b = math.sin(math.radians(self._yaw)) / 3.0
902                                         self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
903                                         self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
904                                         self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
905                                         self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
906                                 else:
907                                         self._yaw += e.GetX() - self._mouseX
908                                         self._pitch -= e.GetY() - self._mouseY
909                                 if self._pitch > 170:
910                                         self._pitch = 170
911                                 if self._pitch < 10:
912                                         self._pitch = 10
913                         elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
914                                 self._mouseState = 'drag'
915                                 self._zoom += e.GetY() - self._mouseY
916                                 if self._zoom < 1:
917                                         self._zoom = 1
918                                 if self._zoom > numpy.max(self._machineSize) * 3:
919                                         self._zoom = numpy.max(self._machineSize) * 3
920                         elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
921                                 self._mouseState = 'dragObject'
922                                 z = max(0, self._mouseClick3DPos[2])
923                                 p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
924                                 p2, p3 = self.getMouseRay(e.GetX(), e.GetY())
925                                 p0[2] -= z
926                                 p1[2] -= z
927                                 p2[2] -= z
928                                 p3[2] -= z
929                                 cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
930                                 cursorZ1 = p2 - (p3 - p2) * (p2[2] / (p3[2] - p2[2]))
931                                 diff = cursorZ1 - cursorZ0
932                                 self._selectedObj.setPosition(self._selectedObj.getPosition() + diff[0:2])
933                 if not e.Dragging() or self._mouseState != 'tool':
934                         self.tool.OnMouseMove(p0, p1)
935
936                 self._mouseX = e.GetX()
937                 self._mouseY = e.GetY()
938
939         def OnMouseWheel(self, e):
940                 delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
941                 delta = max(min(delta,4),-4)
942                 self._zoom *= 1.0 - delta / 10.0
943                 if self._zoom < 1.0:
944                         self._zoom = 1.0
945                 if self._zoom > numpy.max(self._machineSize) * 3:
946                         self._zoom = numpy.max(self._machineSize) * 3
947                 self.Refresh()
948
949         def OnMouseLeave(self, e):
950                 #self._mouseX = -1
951                 pass
952
953         def getMouseRay(self, x, y):
954                 if self._viewport is None:
955                         return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
956                 p0 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 0, self._modelMatrix, self._projMatrix, self._viewport)
957                 p1 = openglHelpers.unproject(x, self._viewport[1] + self._viewport[3] - y, 1, self._modelMatrix, self._projMatrix, self._viewport)
958                 p0 -= self._viewTarget
959                 p1 -= self._viewTarget
960                 return p0, p1
961
962         def _init3DView(self):
963                 # set viewing projection
964                 size = self.GetSize()
965                 glViewport(0, 0, size.GetWidth(), size.GetHeight())
966                 glLoadIdentity()
967
968                 glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
969
970                 glDisable(GL_RESCALE_NORMAL)
971                 glDisable(GL_LIGHTING)
972                 glDisable(GL_LIGHT0)
973                 glEnable(GL_DEPTH_TEST)
974                 glDisable(GL_CULL_FACE)
975                 glDisable(GL_BLEND)
976                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
977
978                 glClearColor(0.8, 0.8, 0.8, 1.0)
979                 glClearStencil(0)
980                 glClearDepth(1.0)
981
982                 glMatrixMode(GL_PROJECTION)
983                 glLoadIdentity()
984                 aspect = float(size.GetWidth()) / float(size.GetHeight())
985                 gluPerspective(45.0, aspect, 1.0, numpy.max(self._machineSize) * 4)
986
987                 glMatrixMode(GL_MODELVIEW)
988                 glLoadIdentity()
989                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
990
991         def OnPaint(self,e):
992                 connectionGroup = self._printerConnectionManager.getAvailableGroup()
993                 if len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionGroup is None or connectionGroup.getPriority() < 0):
994                         self.printButton._imageID = 2
995                         self.printButton._tooltip = _("Toolpath to SD")
996                 elif connectionGroup is not None:
997                         self.printButton._imageID = connectionGroup.getIconID()
998                         self.printButton._tooltip = _("Print with %s") % (connectionGroup.getName())
999                 else:
1000                         self.printButton._imageID = 3
1001                         self.printButton._tooltip = _("Save toolpath")
1002
1003                 if self._animView is not None:
1004                         self._viewTarget = self._animView.getPosition()
1005                         if self._animView.isDone():
1006                                 self._animView = None
1007                 if self._animZoom is not None:
1008                         self._zoom = self._animZoom.getPosition()
1009                         if self._animZoom.isDone():
1010                                 self._animZoom = None
1011                 if self._objectShader is None: #TODO: add loading shaders from file(s)
1012                         if openglHelpers.hasShaderSupport():
1013                                 self._objectShader = openglHelpers.GLShader("""
1014                                         varying float light_amount;
1015
1016                                         void main(void)
1017                                         {
1018                                                 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1019                                                 gl_FrontColor = gl_Color;
1020
1021                                                 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1022                                                 light_amount += 0.2;
1023                                         }
1024                                                                         ""","""
1025                                         varying float light_amount;
1026
1027                                         void main(void)
1028                                         {
1029                                                 gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1030                                         }
1031                                 """)
1032                                 self._objectOverhangShader = openglHelpers.GLShader("""
1033                                         uniform float cosAngle;
1034                                         uniform mat3 rotMatrix;
1035                                         varying float light_amount;
1036
1037                                         void main(void)
1038                                         {
1039                                                 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
1040                                                 gl_FrontColor = gl_Color;
1041
1042                                                 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1043                                                 light_amount += 0.2;
1044                                                 if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
1045                                                 {
1046                                                         light_amount = -10.0;
1047                                                 }
1048                                         }
1049                                 ""","""
1050                                         varying float light_amount;
1051
1052                                         void main(void)
1053                                         {
1054                                                 if (light_amount == -10.0)
1055                                                 {
1056                                                         gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
1057                                                 }else{
1058                                                         gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
1059                                                 }
1060                                         }
1061                                                                         """)
1062                                 self._objectLoadShader = openglHelpers.GLShader("""
1063                                         uniform float intensity;
1064                                         uniform float scale;
1065                                         varying float light_amount;
1066
1067                                         void main(void)
1068                                         {
1069                                                 vec4 tmp = gl_Vertex;
1070                                                 tmp.x += sin(tmp.z/5.0+intensity*30.0) * scale * intensity;
1071                                                 tmp.y += sin(tmp.z/3.0+intensity*40.0) * scale * intensity;
1072                                                 gl_Position = gl_ModelViewProjectionMatrix * tmp;
1073                                                 gl_FrontColor = gl_Color;
1074
1075                                                 light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
1076                                                 light_amount += 0.2;
1077                                         }
1078                         ""","""
1079                                 uniform float intensity;
1080                                 varying float light_amount;
1081
1082                                 void main(void)
1083                                 {
1084                                         gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
1085                                 }
1086                                 """)
1087                         if self._objectShader is None or not self._objectShader.isValid(): #Could not make shader.
1088                                 self._objectShader = openglHelpers.GLFakeShader()
1089                                 self._objectOverhangShader = openglHelpers.GLFakeShader()
1090                                 self._objectLoadShader = None
1091                 self._init3DView()
1092                 glTranslate(0,0,-self._zoom)
1093                 glRotate(-self._pitch, 1,0,0)
1094                 glRotate(self._yaw, 0,0,1)
1095                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1096
1097                 self._viewport = glGetIntegerv(GL_VIEWPORT)
1098                 self._modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
1099                 self._projMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
1100
1101                 glClearColor(1,1,1,1)
1102                 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
1103
1104                 if self.viewMode != 'gcode':
1105                         for n in xrange(0, len(self._scene.objects())):
1106                                 obj = self._scene.objects()[n]
1107                                 glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
1108                                 self._renderObject(obj)
1109
1110                 if self._mouseX > -1: # mouse has not passed over the opengl window.
1111                         glFlush()
1112                         n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
1113                         if n < len(self._scene.objects()):
1114                                 self._focusObj = self._scene.objects()[n]
1115                         else:
1116                                 self._focusObj = None
1117                         f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
1118                         #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
1119                         self._mouse3Dpos = openglHelpers.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
1120                         self._mouse3Dpos -= self._viewTarget
1121
1122                 self._init3DView()
1123                 glTranslate(0,0,-self._zoom)
1124                 glRotate(-self._pitch, 1,0,0)
1125                 glRotate(self._yaw, 0,0,1)
1126                 glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
1127
1128                 self._objectShader.unbind()
1129                 self._engineResultView.OnDraw()
1130                 if self.viewMode != 'gcode':
1131                         glStencilFunc(GL_ALWAYS, 1, 1)
1132                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1133
1134                         if self.viewMode == 'overhang':
1135                                 self._objectOverhangShader.bind()
1136                                 self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - profile.getProfileSettingFloat('support_angle'))))
1137                         else:
1138                                 self._objectShader.bind()
1139                         for obj in self._scene.objects():
1140                                 if obj._loadAnim is not None:
1141                                         if obj._loadAnim.isDone():
1142                                                 obj._loadAnim = None
1143                                         else:
1144                                                 continue
1145                                 brightness = 1.0
1146                                 if self._focusObj == obj:
1147                                         brightness = 1.2
1148                                 elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
1149                                         brightness = 0.8
1150
1151                                 if self._selectedObj == obj or self._selectedObj is None:
1152                                         #If we want transparent, then first render a solid black model to remove the printer size lines.
1153                                         if self.viewMode == 'transparent':
1154                                                 glColor4f(0, 0, 0, 0)
1155                                                 self._renderObject(obj)
1156                                                 glEnable(GL_BLEND)
1157                                                 glBlendFunc(GL_ONE, GL_ONE)
1158                                                 glDisable(GL_DEPTH_TEST)
1159                                                 brightness *= 0.5
1160                                         if self.viewMode == 'xray':
1161                                                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
1162                                         glStencilOp(GL_INCR, GL_INCR, GL_INCR)
1163                                         glEnable(GL_STENCIL_TEST)
1164
1165                                 if self.viewMode == 'overhang':
1166                                         if self._selectedObj == obj and self.tempMatrix is not None:
1167                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
1168                                         else:
1169                                                 self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
1170
1171                                 if not self._scene.checkPlatform(obj):
1172                                         glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
1173                                         self._renderObject(obj)
1174                                 else:
1175                                         self._renderObject(obj, brightness)
1176                                 glDisable(GL_STENCIL_TEST)
1177                                 glDisable(GL_BLEND)
1178                                 glEnable(GL_DEPTH_TEST)
1179                                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
1180
1181                         if self.viewMode == 'xray':
1182                                 glPushMatrix()
1183                                 glLoadIdentity()
1184                                 glEnable(GL_STENCIL_TEST)
1185                                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) #Keep values
1186                                 glDisable(GL_DEPTH_TEST)
1187                                 for i in xrange(2, 15, 2): #All even values
1188                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1189                                         glColor(float(i)/10, float(i)/10, float(i)/5)
1190                                         glBegin(GL_QUADS)
1191                                         glVertex3f(-1000,-1000,-10)
1192                                         glVertex3f( 1000,-1000,-10)
1193                                         glVertex3f( 1000, 1000,-10)
1194                                         glVertex3f(-1000, 1000,-10)
1195                                         glEnd()
1196                                 for i in xrange(1, 15, 2): #All odd values
1197                                         glStencilFunc(GL_EQUAL, i, 0xFF)
1198                                         glColor(float(i)/10, 0, 0)
1199                                         glBegin(GL_QUADS)
1200                                         glVertex3f(-1000,-1000,-10)
1201                                         glVertex3f( 1000,-1000,-10)
1202                                         glVertex3f( 1000, 1000,-10)
1203                                         glVertex3f(-1000, 1000,-10)
1204                                         glEnd()
1205                                 glPopMatrix()
1206                                 glDisable(GL_STENCIL_TEST)
1207                                 glEnable(GL_DEPTH_TEST)
1208
1209                         self._objectShader.unbind()
1210
1211                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1212                         glEnable(GL_BLEND)
1213                         if self._objectLoadShader is not None:
1214                                 self._objectLoadShader.bind()
1215                                 glColor4f(0.2, 0.6, 1.0, 1.0)
1216                                 for obj in self._scene.objects():
1217                                         if obj._loadAnim is None:
1218                                                 continue
1219                                         self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
1220                                         self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
1221                                         self._renderObject(obj)
1222                                 self._objectLoadShader.unbind()
1223                                 glDisable(GL_BLEND)
1224
1225                 self._drawMachine()
1226
1227                 if self.viewMode != 'gcode':
1228                         #Draw the object box-shadow, so you can see where it will collide with other objects.
1229                         if self._selectedObj is not None:
1230                                 glEnable(GL_BLEND)
1231                                 glEnable(GL_CULL_FACE)
1232                                 glColor4f(0,0,0,0.16)
1233                                 glDepthMask(False)
1234                                 for obj in self._scene.objects():
1235                                         glPushMatrix()
1236                                         glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
1237                                         glBegin(GL_TRIANGLE_FAN)
1238                                         for p in obj._boundaryHull[::-1]:
1239                                                 glVertex3f(p[0], p[1], 0)
1240                                         glEnd()
1241                                         glPopMatrix()
1242                                 if self._scene.isOneAtATime(): #Check print sequence mode.
1243                                         glPushMatrix()
1244                                         glColor4f(0,0,0,0.06)
1245                                         glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
1246                                         glBegin(GL_TRIANGLE_FAN)
1247                                         for p in self._selectedObj._printAreaHull[::-1]:
1248                                                 glVertex3f(p[0], p[1], 0)
1249                                         glEnd()
1250                                         glBegin(GL_TRIANGLE_FAN)
1251                                         for p in self._selectedObj._headAreaMinHull[::-1]:
1252                                                 glVertex3f(p[0], p[1], 0)
1253                                         glEnd()
1254                                         glPopMatrix()
1255                                 glDepthMask(True)
1256                                 glDisable(GL_CULL_FACE)
1257
1258                         #Draw the outline of the selected object on top of everything else except the GUI.
1259                         if self._selectedObj is not None and self._selectedObj._loadAnim is None:
1260                                 glDisable(GL_DEPTH_TEST)
1261                                 glEnable(GL_CULL_FACE)
1262                                 glEnable(GL_STENCIL_TEST)
1263                                 glDisable(GL_BLEND)
1264                                 glStencilFunc(GL_EQUAL, 0, 255)
1265
1266                                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
1267                                 glLineWidth(2)
1268                                 glColor4f(1,1,1,0.5)
1269                                 self._renderObject(self._selectedObj)
1270                                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
1271
1272                                 glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
1273                                 glDisable(GL_STENCIL_TEST)
1274                                 glDisable(GL_CULL_FACE)
1275                                 glEnable(GL_DEPTH_TEST)
1276
1277                         if self._selectedObj is not None:
1278                                 glPushMatrix()
1279                                 pos = self.getObjectCenterPos()
1280                                 glTranslate(pos[0], pos[1], pos[2])
1281                                 self.tool.OnDraw()
1282                                 glPopMatrix()
1283                 if self.viewMode == 'overhang' and not openglHelpers.hasShaderSupport():
1284                         glDisable(GL_DEPTH_TEST)
1285                         glPushMatrix()
1286                         glLoadIdentity()
1287                         glTranslate(0,-4,-10)
1288                         glColor4ub(60,60,60,255)
1289                         openglHelpers.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
1290                         glPopMatrix()
1291
1292         def _renderObject(self, obj, brightness = 0, addSink = True):
1293                 glPushMatrix()
1294                 if addSink:
1295                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
1296                 else:
1297                         glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
1298
1299                 if self.tempMatrix is not None and obj == self._selectedObj:
1300                         glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(self.tempMatrix))
1301
1302                 offset = obj.getDrawOffset()
1303                 glTranslate(-offset[0], -offset[1], -offset[2] - obj.getSize()[2] / 2)
1304
1305                 glMultMatrixf(openglHelpers.convert3x3MatrixTo4x4(obj.getMatrix()))
1306
1307                 n = 0
1308                 for m in obj._meshList:
1309                         if m.vbo is None:
1310                                 m.vbo = openglHelpers.GLVBO(GL_TRIANGLES, m.vertexes, m.normal)
1311                         if brightness != 0:
1312                                 glColor4fv(map(lambda idx: idx * brightness, self._objColors[n]))
1313                                 n += 1
1314                         m.vbo.render()
1315                 glPopMatrix()
1316
1317         def _drawMachine(self):
1318                 glEnable(GL_CULL_FACE)
1319                 glEnable(GL_BLEND)
1320
1321                 size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
1322
1323                 machine_type = profile.getMachineSetting('machine_type')
1324                 if machine_type not in self._platformMesh:
1325                         self._platformMesh[machine_type] = None
1326
1327                         filename = None
1328                         texture_name = None
1329                         offset = [0,0,0]
1330                         texture_offset = [0,0,0]
1331                         texture_scale = 1.0
1332                         if machine_type == 'ultimaker2' or machine_type == 'ultimaker2extended':
1333                                 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1334                                 offset = [-9,-37,145]
1335                                 texture_name = 'Ultimaker2backplate.png'
1336                                 texture_offset = [9,150,-5]
1337                         elif machine_type == 'ultimaker2go':
1338                                 filename = resources.getPathForMesh('ultimaker2go_platform.stl')
1339                                 offset = [0,-42,145]
1340                                 texture_offset = [0,105,-5]
1341                                 texture_name = 'Ultimaker2backplate.png'
1342                                 texture_scale = 0.9
1343                         elif machine_type == 'ultimaker_plus':
1344                                 filename = resources.getPathForMesh('ultimaker2_platform.stl')
1345                                 offset = [0,-37,145]
1346                                 texture_offset = [0,150,-5]
1347                                 texture_name = 'UltimakerPlusbackplate.png'
1348                         elif machine_type == 'ultimaker':
1349                                 filename = resources.getPathForMesh('ultimaker_platform.stl')
1350                                 offset = [0,0,2.5]
1351                         elif machine_type == 'Witbox':
1352                                 filename = resources.getPathForMesh('Witbox_platform.stl')
1353                                 offset = [0,-37,145]
1354
1355                         if filename is not None:
1356                                 meshes = meshLoader.loadMeshes(filename)
1357                                 if len(meshes) > 0:
1358                                         self._platformMesh[machine_type] = meshes[0]
1359                                         self._platformMesh[machine_type]._drawOffset = numpy.array(offset, numpy.float32)
1360                                         self._platformMesh[machine_type].texture = None
1361                                         if texture_name is not None:
1362                                                 self._platformMesh[machine_type].texture = openglHelpers.loadGLTexture(texture_name)
1363                                                 self._platformMesh[machine_type].texture_offset = texture_offset
1364                                                 self._platformMesh[machine_type].texture_scale = texture_scale
1365                 if self._platformMesh[machine_type] is not None:
1366                         mesh = self._platformMesh[machine_type]
1367                         glColor4f(1,1,1,0.5)
1368                         self._objectShader.bind()
1369                         self._renderObject(mesh, False, False)
1370                         self._objectShader.unbind()
1371
1372                         #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
1373                         if mesh.texture is not None:
1374                                 glBindTexture(GL_TEXTURE_2D, mesh.texture)
1375                                 glEnable(GL_TEXTURE_2D)
1376                                 glPushMatrix()
1377                                 glColor4f(1,1,1,1)
1378
1379                                 glTranslate(mesh.texture_offset[0], mesh.texture_offset[1], mesh.texture_offset[2])
1380                                 glScalef(mesh.texture_scale, mesh.texture_scale, mesh.texture_scale)
1381                                 h = 50
1382                                 d = 8
1383                                 w = 100
1384                                 glEnable(GL_BLEND)
1385                                 glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA)
1386                                 glEnable(GL_ALPHA_TEST)
1387                                 glAlphaFunc(GL_GREATER, 0.0)
1388                                 glBegin(GL_QUADS)
1389                                 glTexCoord2f(1, 0)
1390                                 glVertex3f( w, 0, h)
1391                                 glTexCoord2f(0, 0)
1392                                 glVertex3f(-w, 0, h)
1393                                 glTexCoord2f(0, 1)
1394                                 glVertex3f(-w, 0, 0)
1395                                 glTexCoord2f(1, 1)
1396                                 glVertex3f( w, 0, 0)
1397
1398                                 glTexCoord2f(1, 0)
1399                                 glVertex3f(-w, d, h)
1400                                 glTexCoord2f(0, 0)
1401                                 glVertex3f( w, d, h)
1402                                 glTexCoord2f(0, 1)
1403                                 glVertex3f( w, d, 0)
1404                                 glTexCoord2f(1, 1)
1405                                 glVertex3f(-w, d, 0)
1406                                 glEnd()
1407                                 glDisable(GL_TEXTURE_2D)
1408                                 glDisable(GL_ALPHA_TEST)
1409                                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
1410                                 glPopMatrix()
1411                 else:
1412                         glColor4f(0,0,0,1)
1413                         glLineWidth(3)
1414                         glBegin(GL_LINES)
1415                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1416                         glVertex3f(-size[0] / 2, -size[1] / 2, 10)
1417                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1418                         glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
1419                         glVertex3f(-size[0] / 2, -size[1] / 2, 0)
1420                         glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
1421                         glEnd()
1422
1423                 glDepthMask(False)
1424
1425                 polys = profile.getMachineSizePolygons()
1426                 height = profile.getMachineSettingFloat('machine_height')
1427                 circular = profile.getMachineSetting('machine_shape') == 'Circular'
1428                 glBegin(GL_QUADS)
1429                 # Draw the sides of the build volume.
1430                 for n in xrange(0, len(polys[0])):
1431                         if not circular:
1432                                 if n % 2 == 0:
1433                                         glColor4ub(5, 171, 231, 96)
1434                                 else:
1435                                         glColor4ub(5, 171, 231, 64)
1436                         else:
1437                                 glColor4ub(5, 171, 231, 96)
1438
1439                         glVertex3f(polys[0][n][0], polys[0][n][1], height)
1440                         glVertex3f(polys[0][n][0], polys[0][n][1], 0)
1441                         glVertex3f(polys[0][n-1][0], polys[0][n-1][1], 0)
1442                         glVertex3f(polys[0][n-1][0], polys[0][n-1][1], height)
1443                 glEnd()
1444
1445                 #Draw top of build volume.
1446                 glColor4ub(5, 171, 231, 128)
1447                 glBegin(GL_TRIANGLE_FAN)
1448                 for p in polys[0][::-1]:
1449                         glVertex3f(p[0], p[1], height)
1450                 glEnd()
1451
1452                 #Draw checkerboard
1453                 if self._platformTexture is None:
1454                         self._platformTexture = openglHelpers.loadGLTexture('checkerboard.png')
1455                         glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1456                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
1457                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
1458                 glColor4f(1,1,1,0.5)
1459                 glBindTexture(GL_TEXTURE_2D, self._platformTexture)
1460                 glEnable(GL_TEXTURE_2D)
1461                 glBegin(GL_TRIANGLE_FAN)
1462                 for p in polys[0]:
1463                         glTexCoord2f(p[0]/20, p[1]/20)
1464                         glVertex3f(p[0], p[1], 0)
1465                 glEnd()
1466
1467                 #Draw no-go zones. (clips in case of UM2)
1468                 glDisable(GL_TEXTURE_2D)
1469                 glColor4ub(127, 127, 127, 200)
1470                 for poly in polys[1:]:
1471                         glBegin(GL_TRIANGLE_FAN)
1472                         for p in poly:
1473                                 glTexCoord2f(p[0]/20, p[1]/20)
1474                                 glVertex3f(p[0], p[1], 0)
1475                         glEnd()
1476
1477                 glDepthMask(True)
1478                 glDisable(GL_BLEND)
1479                 glDisable(GL_CULL_FACE)
1480
1481         def getObjectCenterPos(self):
1482                 if self._selectedObj is None:
1483                         return [0.0, 0.0, 0.0]
1484                 pos = self._selectedObj.getPosition()
1485                 size = self._selectedObj.getSize()
1486                 return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
1487
1488         def getObjectBoundaryCircle(self):
1489                 if self._selectedObj is None:
1490                         return 0.0
1491                 return self._selectedObj.getBoundaryCircle()
1492
1493         def getObjectSize(self):
1494                 if self._selectedObj is None:
1495                         return [0.0, 0.0, 0.0]
1496                 return self._selectedObj.getSize()
1497
1498         def getObjectMatrix(self):
1499                 if self._selectedObj is None:
1500                         return numpy.matrix(numpy.identity(3))
1501                 return self._selectedObj.getMatrix()
1502
1503 #TODO: Remove this or put it in a seperate file
1504 class shaderEditor(wx.Frame):
1505         def __init__(self, parent, callback, v, f):
1506                 super(shaderEditor, self).__init__(parent, title="Shader editor", style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
1507                 self._callback = callback
1508                 s = wx.BoxSizer(wx.VERTICAL)
1509                 self.SetSizer(s)
1510                 self._vertex = wx.TextCtrl(self, -1, v, style=wx.TE_MULTILINE)
1511                 self._fragment = wx.TextCtrl(self, -1, f, style=wx.TE_MULTILINE)
1512                 s.Add(self._vertex, 1, flag=wx.EXPAND)
1513                 s.Add(self._fragment, 1, flag=wx.EXPAND)
1514
1515                 self._vertex.Bind(wx.EVT_TEXT, self.OnText, self._vertex)
1516                 self._fragment.Bind(wx.EVT_TEXT, self.OnText, self._fragment)
1517
1518                 self.SetPosition(self.GetParent().GetPosition())
1519                 self.SetSize((self.GetSize().GetWidth(), self.GetParent().GetSize().GetHeight()))
1520                 self.Show()
1521
1522         def OnText(self, e):
1523                 self._callback(self._vertex.GetValue(), self._fragment.GetValue())