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