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