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