chiark / gitweb /
Update the doodle3d class to auto-detect local network boxes, as well as access point...
[cura.git] / Cura / gui / sceneView.py
index 7a1fc5bfc3908418eae4261e002de3535b89da43..01ce44a03da8d3791f7108503fec3ba7870964bf 100644 (file)
@@ -1,11 +1,14 @@
 from __future__ import absolute_import
+__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
 
 import wx
 import numpy
 import time
 import os
 import traceback
-import shutil
+import threading
+import math
+import platform
 
 import OpenGL
 OpenGL.ERROR_CHECKING = False
@@ -20,29 +23,13 @@ from Cura.util import resources
 from Cura.util import sliceEngine
 from Cura.util import machineCom
 from Cura.util import removableStorage
+from Cura.util import gcodeInterpreter
+from Cura.util.printerConnection import printerConnectionManager
 from Cura.gui.util import previewTools
 from Cura.gui.util import opengl
 from Cura.gui.util import openglGui
-
-class anim(object):
-       def __init__(self, start, end, runTime):
-               self._start = start
-               self._end = end
-               self._startTime = time.time()
-               self._runTime = runTime
-
-       def isDone(self):
-               return time.time() > self._startTime + self._runTime
-
-       def getPosition(self):
-               if self.isDone():
-                       return self._end
-               f = (time.time() - self._startTime) / self._runTime
-               ts = f*f
-               tc = f*f*f
-               #f = 6*tc*ts + -15*ts*ts + 10*tc
-               f = tc + -3*ts + 3*f
-               return self._start + (self._end - self._start) * f
+from Cura.gui.tools import youmagineGui
+from Cura.gui.tools import imageToMesh
 
 class SceneView(openglGui.glGuiPanel):
        def __init__(self, parent):
@@ -52,7 +39,12 @@ class SceneView(openglGui.glGuiPanel):
                self._pitch = 60
                self._zoom = 300
                self._scene = objectScene.Scene()
+               self._gcode = None
+               self._gcodeVBOs = []
+               self._gcodeFilename = None
+               self._gcodeLoadThread = None
                self._objectShader = None
+               self._objectLoadShader = None
                self._focusObj = None
                self._selectedObj = None
                self._objColors = [None,None,None,None]
@@ -62,33 +54,34 @@ class SceneView(openglGui.glGuiPanel):
                self._viewTarget = numpy.array([0,0,0], numpy.float32)
                self._animView = None
                self._animZoom = None
-               self._platformMesh = meshLoader.loadMeshes(resources.getPathForMesh('ultimaker_platform.stl'))[0]
-               self._platformMesh._drawOffset = numpy.array([0,0,1.5], numpy.float32)
+               self._platformMesh = {}
                self._isSimpleMode = True
+               self._usbPrintMonitor = printWindow.printProcessMonitor(lambda : self._queueRefresh())
+               self._printerConnectionManager = printerConnectionManager.PrinterConnectionManager()
 
                self._viewport = None
                self._modelMatrix = None
                self._projMatrix = None
                self.tempMatrix = None
 
-               self.openFileButton      = openglGui.glButton(self, 4, 'Load', (0,0), self.ShowLoadModel)
-               self.printButton         = openglGui.glButton(self, 6, 'Print', (1,0), self.ShowPrintWindow)
+               self.openFileButton      = openglGui.glButton(self, 4, _("Load"), (0,0), self.showLoadModel)
+               self.printButton         = openglGui.glButton(self, 6, _("Print"), (1,0), self.OnPrintButton)
                self.printButton.setDisabled(True)
 
                group = []
-               self.rotateToolButton = openglGui.glRadioButton(self, 8, 'Rotate', (0,-1), group, self.OnToolSelect)
-               self.scaleToolButton  = openglGui.glRadioButton(self, 9, 'Scale', (1,-1), group, self.OnToolSelect)
-               self.mirrorToolButton  = openglGui.glRadioButton(self, 10, 'Mirror', (2,-1), group, self.OnToolSelect)
+               self.rotateToolButton = openglGui.glRadioButton(self, 8, _("Rotate"), (0,-1), group, self.OnToolSelect)
+               self.scaleToolButton  = openglGui.glRadioButton(self, 9, _("Scale"), (1,-1), group, self.OnToolSelect)
+               self.mirrorToolButton  = openglGui.glRadioButton(self, 10, _("Mirror"), (2,-1), group, self.OnToolSelect)
 
-               self.resetRotationButton = openglGui.glButton(self, 12, 'Reset', (0,-2), self.OnRotateReset)
-               self.layFlatButton       = openglGui.glButton(self, 16, 'Lay flat', (0,-3), self.OnLayFlat)
+               self.resetRotationButton = openglGui.glButton(self, 12, _("Reset"), (0,-2), self.OnRotateReset)
+               self.layFlatButton       = openglGui.glButton(self, 16, _("Lay flat"), (0,-3), self.OnLayFlat)
 
-               self.resetScaleButton    = openglGui.glButton(self, 13, 'Reset', (1,-2), self.OnScaleReset)
-               self.scaleMaxButton      = openglGui.glButton(self, 17, 'To max', (1,-3), self.OnScaleMax)
+               self.resetScaleButton    = openglGui.glButton(self, 13, _("Reset"), (1,-2), self.OnScaleReset)
+               self.scaleMaxButton      = openglGui.glButton(self, 17, _("To max"), (1,-3), self.OnScaleMax)
 
-               self.mirrorXButton       = openglGui.glButton(self, 14, 'Mirror X', (2,-2), lambda button: self.OnMirror(0))
-               self.mirrorYButton       = openglGui.glButton(self, 18, 'Mirror Y', (2,-3), lambda button: self.OnMirror(1))
-               self.mirrorZButton       = openglGui.glButton(self, 22, 'Mirror Z', (2,-4), lambda button: self.OnMirror(2))
+               self.mirrorXButton       = openglGui.glButton(self, 14, _("Mirror X"), (2,-2), lambda button: self.OnMirror(0))
+               self.mirrorYButton       = openglGui.glButton(self, 18, _("Mirror Y"), (2,-3), lambda button: self.OnMirror(1))
+               self.mirrorZButton       = openglGui.glButton(self, 22, _("Mirror Z"), (2,-4), lambda button: self.OnMirror(2))
 
                self.rotateToolButton.setExpandArrow(True)
                self.scaleToolButton.setExpandArrow(True)
@@ -96,80 +89,225 @@ class SceneView(openglGui.glGuiPanel):
 
                self.scaleForm = openglGui.glFrame(self, (2, -2))
                openglGui.glGuiLayoutGrid(self.scaleForm)
-               openglGui.glLabel(self.scaleForm, 'Scale X', (0,0))
+               openglGui.glLabel(self.scaleForm, _("Scale X"), (0,0))
                self.scaleXctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,0), lambda value: self.OnScaleEntry(value, 0))
-               openglGui.glLabel(self.scaleForm, 'Scale Y', (0,1))
+               openglGui.glLabel(self.scaleForm, _("Scale Y"), (0,1))
                self.scaleYctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,1), lambda value: self.OnScaleEntry(value, 1))
-               openglGui.glLabel(self.scaleForm, 'Scale Z', (0,2))
+               openglGui.glLabel(self.scaleForm, _("Scale Z"), (0,2))
                self.scaleZctrl = openglGui.glNumberCtrl(self.scaleForm, '1.0', (1,2), lambda value: self.OnScaleEntry(value, 2))
-               openglGui.glLabel(self.scaleForm, 'Size X (mm)', (0,4))
+               openglGui.glLabel(self.scaleForm, _("Size X (mm)"), (0,4))
                self.scaleXmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,4), lambda value: self.OnScaleEntryMM(value, 0))
-               openglGui.glLabel(self.scaleForm, 'Size Y (mm)', (0,5))
+               openglGui.glLabel(self.scaleForm, _("Size Y (mm)"), (0,5))
                self.scaleYmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,5), lambda value: self.OnScaleEntryMM(value, 1))
-               openglGui.glLabel(self.scaleForm, 'Size Z (mm)', (0,6))
+               openglGui.glLabel(self.scaleForm, _("Size Z (mm)"), (0,6))
                self.scaleZmmctrl = openglGui.glNumberCtrl(self.scaleForm, '0.0', (1,6), lambda value: self.OnScaleEntryMM(value, 2))
-               openglGui.glLabel(self.scaleForm, 'Uniform scale', (0,8))
+               openglGui.glLabel(self.scaleForm, _("Uniform scale"), (0,8))
                self.scaleUniform = openglGui.glCheckbox(self.scaleForm, True, (1,8), None)
 
+               self.viewSelection = openglGui.glComboButton(self, _("View mode"), [7,19,11,15,23], [_("Normal"), _("Overhang"), _("Transparent"), _("X-Ray"), _("Layers")], (-1,0), self.OnViewChange)
+               self.layerSelect = openglGui.glSlider(self, 10000, 0, 1, (-1,-2), lambda : self.QueueRefresh())
+
+               self.youMagineButton = openglGui.glButton(self, 26, _("Share on YouMagine"), (2,0), lambda button: youmagineGui.youmagineManager(self.GetTopLevelParent(), self._scene))
+               self.youMagineButton.setDisabled(True)
+
+               self.notification = openglGui.glNotification(self, (0, 0))
+
                self._slicer = sliceEngine.Slicer(self._updateSliceProgress)
                self._sceneUpdateTimer = wx.Timer(self)
-               self.Bind(wx.EVT_TIMER, lambda e : self._slicer.runSlicer(self._scene), self._sceneUpdateTimer)
+               self.Bind(wx.EVT_TIMER, self._onRunSlicer, self._sceneUpdateTimer)
                self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
+               self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
 
+               self.OnViewChange()
                self.OnToolSelect(0)
+               self.updateToolButtons()
                self.updateProfileToControls()
-               wx.EVT_IDLE(self, self.OnIdle)
 
-       def ShowLoadModel(self, button):
+       def loadGCodeFile(self, filename):
+               self.OnDeleteAll(None)
+               if self._gcode is not None:
+                       self._gcode = None
+                       for layerVBOlist in self._gcodeVBOs:
+                               for vbo in layerVBOlist:
+                                       self.glReleaseList.append(vbo)
+                       self._gcodeVBOs = []
+               self._gcode = gcodeInterpreter.gcode()
+               self._gcodeFilename = filename
+               self.printButton.setBottomText('')
+               self.viewSelection.setValue(4)
+               self.printButton.setDisabled(False)
+               self.youMagineButton.setDisabled(True)
+               self.OnViewChange()
+
+       def loadSceneFiles(self, filenames):
+               self.youMagineButton.setDisabled(False)
+               #if self.viewSelection.getValue() == 4:
+               #       self.viewSelection.setValue(0)
+               #       self.OnViewChange()
+               self.loadScene(filenames)
+
+       def loadFiles(self, filenames):
+               mainWindow = self.GetParent().GetParent().GetParent()
+               # only one GCODE file can be active
+               # so if single gcode file, process this
+               # otherwise ignore all gcode files
+               gcodeFilename = None
+               if len(filenames) == 1:
+                       filename = filenames[0]
+                       ext = os.path.splitext(filename)[1].lower()
+                       if ext == '.g' or ext == '.gcode':
+                               gcodeFilename = filename
+                               mainWindow.addToModelMRU(filename)
+               if gcodeFilename is not None:
+                       self.loadGCodeFile(gcodeFilename)
+               else:
+                       # process directories and special file types
+                       # and keep scene files for later processing
+                       scene_filenames = []
+                       ignored_types = dict()
+                       # use file list as queue
+                       # pop first entry for processing and append new files at end
+                       while filenames:
+                               filename = filenames.pop(0)
+                               if os.path.isdir(filename):
+                                       # directory: queue all included files and directories
+                                       filenames.extend(os.path.join(filename, f) for f in os.listdir(filename))
+                               else:
+                                       ext = os.path.splitext(filename)[1].lower()
+                                       if ext == '.ini':
+                                               profile.loadProfile(filename)
+                                               mainWindow.addToProfileMRU(filename)
+                                       elif ext in meshLoader.loadSupportedExtensions() or ext in imageToMesh.supportedExtensions():
+                                               scene_filenames.append(filename)
+                                               mainWindow.addToModelMRU(filename)
+                                       else:
+                                               ignored_types[ext] = 1
+                       if ignored_types:
+                               ignored_types = ignored_types.keys()
+                               ignored_types.sort()
+                               self.notification.message("ignored: " + " ".join("*" + type for type in ignored_types))
+                       mainWindow.updateProfileToAllControls()
+                       # now process all the scene files
+                       if scene_filenames:
+                               self.loadSceneFiles(scene_filenames)
+                               self._selectObject(None)
+                               self.sceneUpdated()
+                               newZoom = numpy.max(self._machineSize)
+                               self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
+                               self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
+
+       def showLoadModel(self, button = 1):
                if button == 1:
-                       dlg=wx.FileDialog(self, 'Open 3D model', os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
-                       dlg.SetWildcard(meshLoader.wildcardFilter())
+                       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)
+                       dlg.SetWildcard(meshLoader.loadWildcardFilter() + imageToMesh.wildcardList() + "|GCode file (*.gcode)|*.g;*.gcode;*.G;*.GCODE")
                        if dlg.ShowModal() != wx.ID_OK:
                                dlg.Destroy()
                                return
-                       filename = dlg.GetPath()
+                       filenames = dlg.GetPaths()
                        dlg.Destroy()
-                       if not(os.path.exists(filename)):
+                       if len(filenames) < 1:
                                return False
-                       profile.putPreference('lastFile', filename)
-                       self.GetParent().GetParent().GetParent().addToModelMRU(filename)
-                       self.loadScene([filename])
+                       profile.putPreference('lastFile', filenames[0])
+                       self.loadFiles(filenames)
+
+       def showSaveModel(self):
+               if len(self._scene.objects()) < 1:
+                       return
+               dlg=wx.FileDialog(self, _("Save 3D model"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
+               dlg.SetWildcard(meshLoader.saveWildcardFilter())
+               if dlg.ShowModal() != wx.ID_OK:
+                       dlg.Destroy()
+                       return
+               filename = dlg.GetPath()
+               dlg.Destroy()
+               meshLoader.saveMeshes(filename, self._scene.objects())
 
-       def ShowPrintWindow(self, button):
+       def OnPrintButton(self, button):
                if button == 1:
+
+                       connectionEntry = self._printerConnectionManager.getAvailableConnection()
                        if machineCom.machineIsConnected():
-                               printWindow.printFile(self._slicer.getGCodeFilename())
-                       elif len(removableStorage.getPossibleSDcardDrives()) > 0:
+                               self.showPrintWindow()
+                       elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
                                drives = removableStorage.getPossibleSDcardDrives()
                                if len(drives) > 1:
-                                       drive = drives[0]
+                                       dlg = wx.SingleChoiceDialog(self, "Select SD drive", "Multiple removable drives have been found,\nplease select your SD card drive", map(lambda n: n[0], drives))
+                                       if dlg.ShowModal() != wx.ID_OK:
+                                               dlg.Destroy()
+                                               return
+                                       drive = drives[dlg.GetSelection()]
+                                       dlg.Destroy()
                                else:
                                        drive = drives[0]
-                               filename = os.path.basename(profile.getPreference('lastFile'))
-                               filename = filename[0:filename.rfind('.')] + '.gcode'
-                               shutil.copy(self._slicer.getGCodeFilename(), drive[1] + filename)
+                               filename = self._scene._objectList[0].getName() + '.gcode'
+                               threading.Thread(target=self._copyFile,args=(self._gcodeFilename, drive[1] + filename, drive[1])).start()
+                       elif connectionEntry is not None:
+                               connectionEntry.connection.loadFile(self._gcodeFilename)
+                               connectionEntry.connection.startPrint()
                        else:
-                               defPath = profile.getPreference('lastFile')
-                               defPath = defPath[0:defPath.rfind('.')] + '.gcode'
-                               dlg=wx.FileDialog(self, 'Save toolpath', defPath, style=wx.FD_SAVE)
-                               dlg.SetFilename(defPath)
-                               dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
-                               if dlg.ShowModal() != wx.ID_OK:
-                                       dlg.Destroy()
-                                       return
-                               filename = dlg.GetPath()
-                               dlg.Destroy()
-
-                               shutil.copy(self._slicer.getGCodeFilename(), filename)
+                               self.showSaveGCode()
+               if button == 3:
+                       menu = wx.Menu()
+                       self.Bind(wx.EVT_MENU, lambda e: self.showPrintWindow(), menu.Append(-1, _("Print with USB")))
+                       self.Bind(wx.EVT_MENU, lambda e: self.showSaveGCode(), menu.Append(-1, _("Save GCode...")))
+                       self.Bind(wx.EVT_MENU, lambda e: self._showSliceLog(), menu.Append(-1, _("Slice engine log...")))
+                       self.PopupMenu(menu)
+                       menu.Destroy()
+
+       def showPrintWindow(self):
+               if self._gcodeFilename is None:
+                       return
+               if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
+                       wx.MessageBox(_("USB printing on the Ultimaker2 is not supported."), _("USB Printing Error"), wx.OK | wx.ICON_WARNING)
+                       return
+               self._usbPrintMonitor.loadFile(self._gcodeFilename, self._slicer.getID())
+               if self._gcodeFilename == self._slicer.getGCodeFilename():
+                       self._slicer.submitSliceInfoOnline()
 
-       def OnIdle(self, e):
-               if self._animView is not None or self._animZoom is not None:
-                       self.Refresh()
+       def showSaveGCode(self):
+               if len(self._scene._objectList) < 1:
                        return
-               for obj in self._scene.objects():
-                       if obj._loadAnim is not None:
-                               self.Refresh()
-                               return
+               dlg=wx.FileDialog(self, _("Save toolpath"), os.path.dirname(profile.getPreference('lastFile')), style=wx.FD_SAVE)
+               filename = self._scene._objectList[0].getName() + '.gcode'
+               dlg.SetFilename(filename)
+               dlg.SetWildcard('Toolpath (*.gcode)|*.gcode;*.g')
+               if dlg.ShowModal() != wx.ID_OK:
+                       dlg.Destroy()
+                       return
+               filename = dlg.GetPath()
+               dlg.Destroy()
+
+               threading.Thread(target=self._copyFile,args=(self._gcodeFilename, filename)).start()
+
+       def _copyFile(self, fileA, fileB, allowEject = False):
+               try:
+                       size = float(os.stat(fileA).st_size)
+                       with open(fileA, 'rb') as fsrc:
+                               with open(fileB, 'wb') as fdst:
+                                       while 1:
+                                               buf = fsrc.read(16*1024)
+                                               if not buf:
+                                                       break
+                                               fdst.write(buf)
+                                               self.printButton.setProgressBar(float(fsrc.tell()) / size)
+                                               self._queueRefresh()
+               except:
+                       import sys
+                       print sys.exc_info()
+                       self.notification.message("Failed to save")
+               else:
+                       if allowEject:
+                               self.notification.message("Saved as %s" % (fileB), lambda : self.notification.message('You can now eject the card.') if removableStorage.ejectDrive(allowEject) else self.notification.message('Safe remove failed...'))
+                       else:
+                               self.notification.message("Saved as %s" % (fileB))
+               self.printButton.setProgressBar(None)
+               if fileA == self._slicer.getGCodeFilename():
+                       self._slicer.submitSliceInfoOnline()
+
+       def _showSliceLog(self):
+               dlg = wx.TextEntryDialog(self, _("The slicing engine reported the following"), _("Engine log..."), '\n'.join(self._slicer.getSliceLog()), wx.TE_MULTILINE | wx.OK | wx.CENTRE)
+               dlg.ShowModal()
+               dlg.Destroy()
 
        def OnToolSelect(self, button):
                if self.rotateToolButton.getSelected():
@@ -189,25 +327,82 @@ class SceneView(openglGui.glGuiPanel):
                self.mirrorYButton.setHidden(not self.mirrorToolButton.getSelected())
                self.mirrorZButton.setHidden(not self.mirrorToolButton.getSelected())
 
+       def updateToolButtons(self):
+               if self._selectedObj is None:
+                       hidden = True
+               else:
+                       hidden = False
+               self.rotateToolButton.setHidden(hidden)
+               self.scaleToolButton.setHidden(hidden)
+               self.mirrorToolButton.setHidden(hidden)
+               if hidden:
+                       self.rotateToolButton.setSelected(False)
+                       self.scaleToolButton.setSelected(False)
+                       self.mirrorToolButton.setSelected(False)
+                       self.OnToolSelect(0)
+
+       def OnViewChange(self):
+               if self.viewSelection.getValue() == 4:
+                       self.viewMode = 'gcode'
+                       if self._gcode is not None and self._gcode.layerList is not None:
+                               self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
+                       self._selectObject(None)
+               elif self.viewSelection.getValue() == 1:
+                       self.viewMode = 'overhang'
+               elif self.viewSelection.getValue() == 2:
+                       self.viewMode = 'transparent'
+               elif self.viewSelection.getValue() == 3:
+                       self.viewMode = 'xray'
+               else:
+                       self.viewMode = 'normal'
+               self.layerSelect.setHidden(self.viewMode != 'gcode')
+               self.QueueRefresh()
+
        def OnRotateReset(self, button):
                if self._selectedObj is None:
                        return
-               pass
+               self._selectedObj.resetRotation()
+               self._scene.pushFree()
+               self._selectObject(self._selectedObj)
+               self.sceneUpdated()
 
        def OnLayFlat(self, button):
                if self._selectedObj is None:
                        return
-               pass
+               self._selectedObj.layFlat()
+               self._scene.pushFree()
+               self._selectObject(self._selectedObj)
+               self.sceneUpdated()
 
        def OnScaleReset(self, button):
                if self._selectedObj is None:
                        return
-               pass
+               self._selectedObj.resetScale()
+               self._selectObject(self._selectedObj)
+               self.updateProfileToControls()
+               self.sceneUpdated()
 
        def OnScaleMax(self, button):
                if self._selectedObj is None:
                        return
-               pass
+               machine = profile.getMachineSetting('machine_type')
+               self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
+               self._scene.pushFree()
+               #self.sceneUpdated()
+               if machine == "ultimaker2":
+                       #This is bad and Jaime should feel bad!
+                       self._selectedObj.setPosition(numpy.array([0.0,-10.0]))
+                       self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
+                       self._selectedObj.setPosition(numpy.array([0.0,0.0]))
+                       self._scene.pushFree()
+               else:
+                       self._selectedObj.setPosition(numpy.array([0.0, 0.0]))
+                       self._scene.pushFree()
+                       self._selectedObj.scaleUpTo(self._machineSize - numpy.array(profile.calculateObjectSizeOffsets() + [0.0], numpy.float32) * 2 - numpy.array([1,1,1], numpy.float32))
+               self._scene.pushFree()
+               self._selectObject(self._selectedObj)
+               self.updateProfileToControls()
+               self.sceneUpdated()
 
        def OnMirror(self, axis):
                if self._selectedObj is None:
@@ -224,79 +419,211 @@ class SceneView(openglGui.glGuiPanel):
                        return
                self._selectedObj.setScale(value, axis, self.scaleUniform.getValue())
                self.updateProfileToControls()
+               self._scene.pushFree()
+               self._selectObject(self._selectedObj)
                self.sceneUpdated()
 
        def OnScaleEntryMM(self, value, axis):
+               if self._selectedObj is None:
+                       return
                try:
                        value = float(value)
                except:
                        return
                self._selectedObj.setSize(value, axis, self.scaleUniform.getValue())
                self.updateProfileToControls()
+               self._scene.pushFree()
+               self._selectObject(self._selectedObj)
+               self.sceneUpdated()
+
+       def OnDeleteAll(self, e):
+               while len(self._scene.objects()) > 0:
+                       self._deleteObject(self._scene.objects()[0])
+               self._animView = openglGui.animation(self, self._viewTarget.copy(), numpy.array([0,0,0], numpy.float32), 0.5)
+
+       def OnMultiply(self, e):
+               if self._focusObj is None:
+                       return
+               obj = self._focusObj
+               dlg = wx.NumberEntryDialog(self, _("How many copies do you want?"), _("Number of copies"), _("Multiply"), 1, 1, 100)
+               if dlg.ShowModal() != wx.ID_OK:
+                       dlg.Destroy()
+                       return
+               cnt = dlg.GetValue()
+               dlg.Destroy()
+               n = 0
+               while True:
+                       n += 1
+                       newObj = obj.copy()
+                       self._scene.add(newObj)
+                       self._scene.centerAll()
+                       if not self._scene.checkPlatform(newObj):
+                               break
+                       if n > cnt:
+                               break
+               if n <= cnt:
+                       self.notification.message("Could not create more then %d items" % (n - 1))
+               self._scene.remove(newObj)
+               self._scene.centerAll()
+               self.sceneUpdated()
+
+       def OnSplitObject(self, e):
+               if self._focusObj is None:
+                       return
+               self._scene.remove(self._focusObj)
+               for obj in self._focusObj.split(self._splitCallback):
+                       if numpy.max(obj.getSize()) > 2.0:
+                               self._scene.add(obj)
+               self._scene.centerAll()
+               self._selectObject(None)
+               self.sceneUpdated()
+
+       def OnCenter(self, e):
+               if self._focusObj is None:
+                       return
+               self._focusObj.setPosition(numpy.array([0.0, 0.0]))
+               self._scene.pushFree()
+               newViewPos = numpy.array([self._focusObj.getPosition()[0], self._focusObj.getPosition()[1], self._focusObj.getSize()[2] / 2])
+               self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
+               self.sceneUpdated()
+
+       def _splitCallback(self, progress):
+               print progress
+
+       def OnMergeObjects(self, e):
+               if self._selectedObj is None or self._focusObj is None or self._selectedObj == self._focusObj:
+                       if len(self._scene.objects()) == 2:
+                               self._scene.merge(self._scene.objects()[0], self._scene.objects()[1])
+                               self.sceneUpdated()
+                       return
+               self._scene.merge(self._selectedObj, self._focusObj)
                self.sceneUpdated()
 
        def sceneUpdated(self):
-               self._sceneUpdateTimer.Start(1, True)
+               self._sceneUpdateTimer.Start(500, True)
                self._slicer.abortSlicer()
-               self._scene.setSizeOffsets(numpy.array(profile.calculateObjectSizeOffsets(), numpy.float32))
-               self.Refresh()
+               self._scene.updateSizeOffsets()
+               self.QueueRefresh()
+
+       def _onRunSlicer(self, e):
+               if self._isSimpleMode:
+                       self.GetTopLevelParent().simpleSettingsPanel.setupSlice()
+               self._slicer.runSlicer(self._scene)
+               if self._isSimpleMode:
+                       profile.resetTempOverride()
 
        def _updateSliceProgress(self, progressValue, ready):
+               if not ready:
+                       if self.printButton.getProgressBar() is not None and progressValue >= 0.0 and abs(self.printButton.getProgressBar() - progressValue) < 0.01:
+                               return
                self.printButton.setDisabled(not ready)
-               self.printButton.setProgressBar(progressValue)
-               self.Refresh()
+               if progressValue >= 0.0:
+                       self.printButton.setProgressBar(progressValue)
+               else:
+                       self.printButton.setProgressBar(None)
+               if self._gcode is not None:
+                       self._gcode = None
+                       for layerVBOlist in self._gcodeVBOs:
+                               for vbo in layerVBOlist:
+                                       self.glReleaseList.append(vbo)
+                       self._gcodeVBOs = []
+               if ready:
+                       self.printButton.setProgressBar(None)
+                       text = '%s' % (self._slicer.getPrintTime())
+                       for e in xrange(0, int(profile.getMachineSetting('extruder_amount'))):
+                               amount = self._slicer.getFilamentAmount(e)
+                               if amount is None:
+                                       continue
+                               text += '\n%s' % (amount)
+                               cost = self._slicer.getFilamentCost(e)
+                               if cost is not None:
+                                       text += '\n%s' % (cost)
+                       self.printButton.setBottomText(text)
+                       self._gcode = gcodeInterpreter.gcode()
+                       self._gcodeFilename = self._slicer.getGCodeFilename()
+               else:
+                       self.printButton.setBottomText('')
+               self.QueueRefresh()
+
+       def _loadGCode(self):
+               self._gcode.progressCallback = self._gcodeLoadCallback
+               self._gcode.load(self._gcodeFilename)
+
+       def _gcodeLoadCallback(self, progress):
+               if not self or self._gcode is None:
+                       return True
+               if len(self._gcode.layerList) % 15 == 0:
+                       time.sleep(0.1)
+               if self._gcode is None:
+                       return True
+               self.layerSelect.setRange(1, len(self._gcode.layerList) - 1)
+               if self.viewMode == 'gcode':
+                       self._queueRefresh()
+               return False
 
        def loadScene(self, fileList):
                for filename in fileList:
                        try:
-                               objList = meshLoader.loadMeshes(filename)
+                               ext = os.path.splitext(filename)[1].lower()
+                               if ext in imageToMesh.supportedExtensions():
+                                       imageToMesh.convertImageDialog(self, filename).Show()
+                                       objList = []
+                               else:
+                                       objList = meshLoader.loadMeshes(filename)
                        except:
                                traceback.print_exc()
                        else:
                                for obj in objList:
-                                       obj._loadAnim = anim(1, 0, 1.5)
+                                       if self._objectLoadShader is not None:
+                                               obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
+                                       else:
+                                               obj._loadAnim = None
                                        self._scene.add(obj)
                                        self._scene.centerAll()
                                        self._selectObject(obj)
+                                       if obj.getScale()[0] < 1.0:
+                                               self.notification.message("Warning: Object scaled down.")
                self.sceneUpdated()
 
        def _deleteObject(self, obj):
                if obj == self._selectedObj:
-                       self._selectedObj = None
+                       self._selectObject(None)
                if obj == self._focusObj:
                        self._focusObj = None
                self._scene.remove(obj)
                for m in obj._meshList:
-                       if m.vbo is not None:
+                       if m.vbo is not None and m.vbo.decRef():
                                self.glReleaseList.append(m.vbo)
-               if self._isSimpleMode:
-                       self._scene.arrangeAll()
+               import gc
+               gc.collect()
                self.sceneUpdated()
 
        def _selectObject(self, obj, zoom = True):
                if obj != self._selectedObj:
                        self._selectedObj = obj
                        self.updateProfileToControls()
-               if zoom:
-                       newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getMaximum()[2] / 2])
-                       self._animView = anim(self._viewTarget.copy(), newViewPos, 0.5)
+                       self.updateToolButtons()
+               if zoom and obj is not None:
+                       newViewPos = numpy.array([obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2])
+                       self._animView = openglGui.animation(self, self._viewTarget.copy(), newViewPos, 0.5)
                        newZoom = obj.getBoundaryCircle() * 6
                        if newZoom > numpy.max(self._machineSize) * 3:
                                newZoom = numpy.max(self._machineSize) * 3
-                       self._animZoom = anim(self._zoom, newZoom, 0.5)
+                       self._animZoom = openglGui.animation(self, self._zoom, newZoom, 0.5)
 
        def updateProfileToControls(self):
                oldSimpleMode = self._isSimpleMode
                self._isSimpleMode = profile.getPreference('startMode') == 'Simple'
-               if self._isSimpleMode and not oldSimpleMode:
+               if self._isSimpleMode != oldSimpleMode:
                        self._scene.arrangeAll()
                        self.sceneUpdated()
-               self._machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
+               self._machineSize = numpy.array([profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')])
                self._objColors[0] = profile.getPreferenceColour('model_colour')
                self._objColors[1] = profile.getPreferenceColour('model_colour2')
                self._objColors[2] = profile.getPreferenceColour('model_colour3')
                self._objColors[3] = profile.getPreferenceColour('model_colour4')
                self._scene.setMachineSize(self._machineSize)
+               self._scene.setHeadSize(profile.getMachineSettingFloat('extruder_head_size_min_x'), profile.getMachineSettingFloat('extruder_head_size_max_x'), profile.getMachineSettingFloat('extruder_head_size_min_y'), profile.getMachineSettingFloat('extruder_head_size_max_y'), profile.getMachineSettingFloat('extruder_head_size_height'))
 
                if self._selectedObj is not None:
                        scale = self._selectedObj.getScale()
@@ -309,13 +636,40 @@ class SceneView(openglGui.glGuiPanel):
                        self.scaleZmmctrl.setValue(round(size[2], 2))
 
        def OnKeyChar(self, keyCode):
-               if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE:
+               if keyCode == wx.WXK_DELETE or keyCode == wx.WXK_NUMPAD_DELETE or (keyCode == wx.WXK_BACK and platform.system() == "Darwin"):
                        if self._selectedObj is not None:
                                self._deleteObject(self._selectedObj)
-                               self.Refresh()
+                               self.QueueRefresh()
+               if keyCode == wx.WXK_UP:
+                       self.layerSelect.setValue(self.layerSelect.getValue() + 1)
+                       self.QueueRefresh()
+               elif keyCode == wx.WXK_DOWN:
+                       self.layerSelect.setValue(self.layerSelect.getValue() - 1)
+                       self.QueueRefresh()
+               elif keyCode == wx.WXK_PAGEUP:
+                       self.layerSelect.setValue(self.layerSelect.getValue() + 10)
+                       self.QueueRefresh()
+               elif keyCode == wx.WXK_PAGEDOWN:
+                       self.layerSelect.setValue(self.layerSelect.getValue() - 10)
+                       self.QueueRefresh()
 
                if keyCode == wx.WXK_F3 and wx.GetKeyState(wx.WXK_SHIFT):
                        shaderEditor(self, self.ShaderUpdate, self._objectLoadShader.getVertexShader(), self._objectLoadShader.getFragmentShader())
+               if keyCode == wx.WXK_F4 and wx.GetKeyState(wx.WXK_SHIFT):
+                       from collections import defaultdict
+                       from gc import get_objects
+                       self._beforeLeakTest = defaultdict(int)
+                       for i in get_objects():
+                               self._beforeLeakTest[type(i)] += 1
+               if keyCode == wx.WXK_F5 and wx.GetKeyState(wx.WXK_SHIFT):
+                       from collections import defaultdict
+                       from gc import get_objects
+                       self._afterLeakTest = defaultdict(int)
+                       for i in get_objects():
+                               self._afterLeakTest[type(i)] += 1
+                       for k in self._afterLeakTest:
+                               if self._afterLeakTest[k]-self._beforeLeakTest[k]:
+                                       print k, self._afterLeakTest[k], self._beforeLeakTest[k], self._afterLeakTest[k] - self._beforeLeakTest[k]
 
        def ShaderUpdate(self, v, f):
                s = opengl.GLShader(v, f)
@@ -323,8 +677,8 @@ class SceneView(openglGui.glGuiPanel):
                        self._objectLoadShader.release()
                        self._objectLoadShader = s
                        for obj in self._scene.objects():
-                               obj._loadAnim = anim(1, 0, 1.5)
-                       self.Refresh()
+                               obj._loadAnim = openglGui.animation(self, 1, 0, 1.5)
+                       self.QueueRefresh()
 
        def OnMouseDown(self,e):
                self._mouseX = e.GetX()
@@ -344,30 +698,36 @@ class SceneView(openglGui.glGuiPanel):
                        if e.GetButton() == 1:
                                if self._focusObj is not None:
                                        self._selectObject(self._focusObj, False)
-                                       self.Refresh()
+                                       self.QueueRefresh()
 
        def OnMouseUp(self, e):
                if e.LeftIsDown() or e.MiddleIsDown() or e.RightIsDown():
                        return
                if self._mouseState == 'dragOrClick':
                        if e.GetButton() == 1:
-                               if self._focusObj is not None:
-                                       self._selectObject(self._focusObj)
-                               else:
-                                       self._selectedObj = None
-                                       self.Refresh()
-                       if e.GetButton() == 3 and self._selectedObj == self._focusObj:
-                               #menu = wx.Menu()
-                               #menu.Append(-1, 'Test')
-                               #self.PopupMenu(menu)
-                               #menu.Destroy()
-                               pass
+                               self._selectObject(self._focusObj)
+                       if e.GetButton() == 3:
+                                       menu = wx.Menu()
+                                       if self._focusObj is not None:
+                                               self.Bind(wx.EVT_MENU, lambda e: self._deleteObject(self._focusObj), menu.Append(-1, _("Delete object")))
+                                               self.Bind(wx.EVT_MENU, self.OnCenter, menu.Append(-1, _("Center on platform")))
+                                               self.Bind(wx.EVT_MENU, self.OnMultiply, menu.Append(-1, _("Multiply object")))
+                                               self.Bind(wx.EVT_MENU, self.OnSplitObject, menu.Append(-1, _("Split object into parts")))
+                                       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:
+                                               self.Bind(wx.EVT_MENU, self.OnMergeObjects, menu.Append(-1, _("Dual extrusion merge")))
+                                       if len(self._scene.objects()) > 0:
+                                               self.Bind(wx.EVT_MENU, self.OnDeleteAll, menu.Append(-1, _("Delete all objects")))
+                                       if menu.MenuItemCount > 0:
+                                               self.PopupMenu(menu)
+                                       menu.Destroy()
                elif self._mouseState == 'dragObject' and self._selectedObj is not None:
                        self._scene.pushFree()
                        self.sceneUpdated()
                elif self._mouseState == 'tool':
                        if self.tempMatrix is not None and self._selectedObj is not None:
                                self._selectedObj.applyMatrix(self.tempMatrix)
+                               self._scene.pushFree()
+                               self._selectObject(self._selectedObj)
                        self.tempMatrix = None
                        self.tool.OnDragEnd()
                        self.sceneUpdated()
@@ -382,19 +742,29 @@ class SceneView(openglGui.glGuiPanel):
                        if self._mouseState == 'tool':
                                self.tool.OnDrag(p0, p1)
                        elif not e.LeftIsDown() and e.RightIsDown():
-                               self._yaw += e.GetX() - self._mouseX
-                               self._pitch -= e.GetY() - self._mouseY
+                               self._mouseState = 'drag'
+                               if wx.GetKeyState(wx.WXK_SHIFT):
+                                       a = math.cos(math.radians(self._yaw)) / 3.0
+                                       b = math.sin(math.radians(self._yaw)) / 3.0
+                                       self._viewTarget[0] += float(e.GetX() - self._mouseX) * -a
+                                       self._viewTarget[1] += float(e.GetX() - self._mouseX) * b
+                                       self._viewTarget[0] += float(e.GetY() - self._mouseY) * b
+                                       self._viewTarget[1] += float(e.GetY() - self._mouseY) * a
+                               else:
+                                       self._yaw += e.GetX() - self._mouseX
+                                       self._pitch -= e.GetY() - self._mouseY
                                if self._pitch > 170:
                                        self._pitch = 170
                                if self._pitch < 10:
                                        self._pitch = 10
                        elif (e.LeftIsDown() and e.RightIsDown()) or e.MiddleIsDown():
+                               self._mouseState = 'drag'
                                self._zoom += e.GetY() - self._mouseY
                                if self._zoom < 1:
                                        self._zoom = 1
                                if self._zoom > numpy.max(self._machineSize) * 3:
                                        self._zoom = numpy.max(self._machineSize) * 3
-                       elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus and not self._isSimpleMode:
+                       elif e.LeftIsDown() and self._selectedObj is not None and self._selectedObj == self._mouseClickFocus:
                                self._mouseState = 'dragObject'
                                z = max(0, self._mouseClick3DPos[2])
                                p0, p1 = self.getMouseRay(self._mouseX, self._mouseY)
@@ -414,13 +784,19 @@ class SceneView(openglGui.glGuiPanel):
                self._mouseY = e.GetY()
 
        def OnMouseWheel(self, e):
-               self._zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
+               delta = float(e.GetWheelRotation()) / float(e.GetWheelDelta())
+               delta = max(min(delta,4),-4)
+               self._zoom *= 1.0 - delta / 10.0
                if self._zoom < 1.0:
                        self._zoom = 1.0
                if self._zoom > numpy.max(self._machineSize) * 3:
                        self._zoom = numpy.max(self._machineSize) * 3
                self.Refresh()
 
+       def OnMouseLeave(self, e):
+               #self._mouseX = -1
+               pass
+
        def getMouseRay(self, x, y):
                if self._viewport is None:
                        return numpy.array([0,0,0],numpy.float32), numpy.array([0,0,1],numpy.float32)
@@ -460,15 +836,19 @@ class SceneView(openglGui.glGuiPanel):
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
 
        def OnPaint(self,e):
+               connectionEntry = self._printerConnectionManager.getAvailableConnection()
                if machineCom.machineIsConnected():
                        self.printButton._imageID = 6
-                       self.printButton._tooltip = 'Print'
-               elif len(removableStorage.getPossibleSDcardDrives()) > 0:
+                       self.printButton._tooltip = _("Print")
+               elif len(removableStorage.getPossibleSDcardDrives()) > 0 and (connectionEntry is None or connectionEntry.priority < 0):
                        self.printButton._imageID = 2
-                       self.printButton._tooltip = 'Toolpath to SD'
+                       self.printButton._tooltip = _("Toolpath to SD")
+               elif connectionEntry is not None:
+                       self.printButton._imageID = connectionEntry.icon
+                       self.printButton._tooltip = _("Print with %s") % (connectionEntry.name)
                else:
                        self.printButton._imageID = 3
-                       self.printButton._tooltip = 'Save toolpath'
+                       self.printButton._tooltip = _("Save toolpath")
 
                if self._animView is not None:
                        self._viewTarget = self._animView.getPosition()
@@ -478,8 +858,14 @@ class SceneView(openglGui.glGuiPanel):
                        self._zoom = self._animZoom.getPosition()
                        if self._animZoom.isDone():
                                self._animZoom = None
+               if self.viewMode == 'gcode' and self._gcode is not None:
+                       try:
+                               self._viewTarget[2] = self._gcode.layerList[self.layerSelect.getValue()][-1]['points'][0][2]
+                       except:
+                               pass
                if self._objectShader is None:
-                       self._objectShader = opengl.GLShader("""
+                       if opengl.hasShaderSupport():
+                               self._objectShader = opengl.GLShader("""
 varying float light_amount;
 
 void main(void)
@@ -490,15 +876,45 @@ void main(void)
        light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
        light_amount += 0.2;
 }
-                       ""","""
+                               ""","""
 varying float light_amount;
 
 void main(void)
 {
        gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
 }
-                       """)
-                       self._objectLoadShader = opengl.GLShader("""
+                               """)
+                               self._objectOverhangShader = opengl.GLShader("""
+uniform float cosAngle;
+uniform mat3 rotMatrix;
+varying float light_amount;
+
+void main(void)
+{
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+    gl_FrontColor = gl_Color;
+
+       light_amount = abs(dot(normalize(gl_NormalMatrix * gl_Normal), normalize(gl_LightSource[0].position.xyz)));
+       light_amount += 0.2;
+       if (normalize(rotMatrix * gl_Normal).z < -cosAngle)
+       {
+               light_amount = -10.0;
+       }
+}
+                               ""","""
+varying float light_amount;
+
+void main(void)
+{
+       if (light_amount == -10.0)
+       {
+               gl_FragColor = vec4(1.0, 0.0, 0.0, gl_Color[3]);
+       }else{
+               gl_FragColor = vec4(gl_Color.xyz * light_amount, gl_Color[3]);
+       }
+}
+                               """)
+                               self._objectLoadShader = opengl.GLShader("""
 uniform float intensity;
 uniform float scale;
 varying float light_amount;
@@ -522,7 +938,11 @@ void main(void)
 {
        gl_FragColor = vec4(gl_Color.xyz * light_amount, 1.0-intensity);
 }
-                       """)
+                               """)
+                       if self._objectShader is None or not self._objectShader.isValid():
+                               self._objectShader = opengl.GLFakeShader()
+                               self._objectOverhangShader = opengl.GLFakeShader()
+                               self._objectLoadShader = None
                self._init3DView()
                glTranslate(0,0,-self._zoom)
                glRotate(-self._pitch, 1,0,0)
@@ -536,18 +956,21 @@ void main(void)
                glClearColor(1,1,1,1)
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
 
-               for n in xrange(0, len(self._scene.objects())):
-                       obj = self._scene.objects()[n]
-                       glColor4ub((n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF)
-                       self._renderObject(obj)
+               if self.viewMode != 'gcode':
+                       for n in xrange(0, len(self._scene.objects())):
+                               obj = self._scene.objects()[n]
+                               glColor4ub((n >> 16) & 0xFF, (n >> 8) & 0xFF, (n >> 0) & 0xFF, 0xFF)
+                               self._renderObject(obj)
 
                if self._mouseX > -1:
-                       n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0]
+                       glFlush()
+                       n = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8)[0][0] >> 8
                        if n < len(self._scene.objects()):
                                self._focusObj = self._scene.objects()[n]
                        else:
                                self._focusObj = None
                        f = glReadPixels(self._mouseX, self.GetSize().GetHeight() - 1 - self._mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
+                       #self.GetTopLevelParent().SetTitle(hex(n) + " " + str(f))
                        self._mouse3Dpos = opengl.unproject(self._mouseX, self._viewport[1] + self._viewport[3] - self._mouseY, f, self._modelMatrix, self._projMatrix, self._viewport)
                        self._mouse3Dpos -= self._viewTarget
 
@@ -557,89 +980,253 @@ void main(void)
                glRotate(self._yaw, 0,0,1)
                glTranslate(-self._viewTarget[0],-self._viewTarget[1],-self._viewTarget[2])
 
-               glStencilFunc(GL_ALWAYS, 1, 1)
-               glStencilOp(GL_INCR, GL_INCR, GL_INCR)
-               self._objectShader.bind()
-               for obj in self._scene.objects():
-                       if obj._loadAnim is not None:
-                               if obj._loadAnim.isDone():
-                                       obj._loadAnim = None
+               if self.viewMode == 'gcode':
+                       if self._gcode is not None and self._gcode.layerList is None:
+                               self._gcodeLoadThread = threading.Thread(target=self._loadGCode)
+                               self._gcodeLoadThread.daemon = True
+                               self._gcodeLoadThread.start()
+                       if self._gcode is not None and self._gcode.layerList is not None:
+                               glPushMatrix()
+                               if profile.getMachineSetting('machine_center_is_zero') != 'True':
+                                       glTranslate(-self._machineSize[0] / 2, -self._machineSize[1] / 2, 0)
+                               t = time.time()
+                               drawUpTill = min(len(self._gcode.layerList), self.layerSelect.getValue() + 1)
+                               for n in xrange(0, drawUpTill):
+                                       c = 1.0 - float(drawUpTill - n) / 15
+                                       c = max(0.3, c)
+                                       if len(self._gcodeVBOs) < n + 1:
+                                               self._gcodeVBOs.append(self._generateGCodeVBOs(self._gcode.layerList[n]))
+                                               if time.time() - t > 0.5:
+                                                       self.QueueRefresh()
+                                                       break
+                                       #['WALL-OUTER', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']
+                                       if n == drawUpTill - 1:
+                                               if len(self._gcodeVBOs[n]) < 9:
+                                                       self._gcodeVBOs[n] += self._generateGCodeVBOs2(self._gcode.layerList[n])
+                                               glColor3f(c, 0, 0)
+                                               self._gcodeVBOs[n][8].render(GL_QUADS)
+                                               glColor3f(c/2, 0, c)
+                                               self._gcodeVBOs[n][9].render(GL_QUADS)
+                                               glColor3f(0, c, c/2)
+                                               self._gcodeVBOs[n][10].render(GL_QUADS)
+                                               glColor3f(c, 0, 0)
+                                               self._gcodeVBOs[n][11].render(GL_QUADS)
+
+                                               glColor3f(0, c, 0)
+                                               self._gcodeVBOs[n][12].render(GL_QUADS)
+                                               glColor3f(c/2, c/2, 0.0)
+                                               self._gcodeVBOs[n][13].render(GL_QUADS)
+                                               glColor3f(0, c, c)
+                                               self._gcodeVBOs[n][14].render(GL_QUADS)
+                                               self._gcodeVBOs[n][15].render(GL_QUADS)
+                                               glColor3f(0, 0, c)
+                                               self._gcodeVBOs[n][16].render(GL_LINES)
+                                       else:
+                                               glColor3f(c, 0, 0)
+                                               self._gcodeVBOs[n][0].render(GL_LINES)
+                                               glColor3f(c/2, 0, c)
+                                               self._gcodeVBOs[n][1].render(GL_LINES)
+                                               glColor3f(0, c, c/2)
+                                               self._gcodeVBOs[n][2].render(GL_LINES)
+                                               glColor3f(c, 0, 0)
+                                               self._gcodeVBOs[n][3].render(GL_LINES)
+
+                                               glColor3f(0, c, 0)
+                                               self._gcodeVBOs[n][4].render(GL_LINES)
+                                               glColor3f(c/2, c/2, 0.0)
+                                               self._gcodeVBOs[n][5].render(GL_LINES)
+                                               glColor3f(0, c, c)
+                                               self._gcodeVBOs[n][6].render(GL_LINES)
+                                               self._gcodeVBOs[n][7].render(GL_LINES)
+                               glPopMatrix()
+               else:
+                       glStencilFunc(GL_ALWAYS, 1, 1)
+                       glStencilOp(GL_INCR, GL_INCR, GL_INCR)
+
+                       if self.viewMode == 'overhang':
+                               self._objectOverhangShader.bind()
+                               self._objectOverhangShader.setUniform('cosAngle', math.cos(math.radians(90 - 60)))
+                       else:
+                               self._objectShader.bind()
+                       for obj in self._scene.objects():
+                               if obj._loadAnim is not None:
+                                       if obj._loadAnim.isDone():
+                                               obj._loadAnim = None
+                                       else:
+                                               continue
+                               brightness = 1.0
+                               if self._focusObj == obj:
+                                       brightness = 1.2
+                               elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
+                                       brightness = 0.8
+
+                               if self._selectedObj == obj or self._selectedObj is None:
+                                       #If we want transparent, then first render a solid black model to remove the printer size lines.
+                                       if self.viewMode == 'transparent':
+                                               glColor4f(0, 0, 0, 0)
+                                               self._renderObject(obj)
+                                               glEnable(GL_BLEND)
+                                               glBlendFunc(GL_ONE, GL_ONE)
+                                               glDisable(GL_DEPTH_TEST)
+                                               brightness *= 0.5
+                                       if self.viewMode == 'xray':
+                                               glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
+                                       glStencilOp(GL_INCR, GL_INCR, GL_INCR)
+                                       glEnable(GL_STENCIL_TEST)
+
+                               if self.viewMode == 'overhang':
+                                       if self._selectedObj == obj and self.tempMatrix is not None:
+                                               self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix() * self.tempMatrix)
+                                       else:
+                                               self._objectOverhangShader.setUniform('rotMatrix', obj.getMatrix())
+
+                               if not self._scene.checkPlatform(obj):
+                                       glColor4f(0.5 * brightness, 0.5 * brightness, 0.5 * brightness, 0.8 * brightness)
+                                       self._renderObject(obj)
                                else:
-                                       continue
-                       col = self._objColors[0]
-                       if not self._scene.checkPlatform(obj):
-                               col = [0.5,0.5,0.5,0.8]
-                       glDisable(GL_STENCIL_TEST)
-                       if self._selectedObj == obj:
+                                       self._renderObject(obj, brightness)
+                               glDisable(GL_STENCIL_TEST)
+                               glDisable(GL_BLEND)
+                               glEnable(GL_DEPTH_TEST)
+                               glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
+
+                       if self.viewMode == 'xray':
+                               glPushMatrix()
+                               glLoadIdentity()
                                glEnable(GL_STENCIL_TEST)
-                       if self._focusObj == obj:
-                               col = map(lambda n: n * 1.2, col)
-                       elif self._focusObj is not None or self._selectedObj is not None and obj != self._selectedObj:
-                               col = map(lambda n: n * 0.8, col)
-                       glColor4f(col[0], col[1], col[2], col[3])
-                       self._renderObject(obj)
-               self._objectShader.unbind()
-
-               glDisable(GL_STENCIL_TEST)
-               glEnable(GL_BLEND)
-               self._objectLoadShader.bind()
-               glColor4f(0.2, 0.6, 1.0, 1.0)
-               for obj in self._scene.objects():
-                       if obj._loadAnim is None:
-                               continue
-                       self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
-                       self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
-                       self._renderObject(obj)
-               self._objectLoadShader.unbind()
-               glDisable(GL_BLEND)
+                               glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
+                               glDisable(GL_DEPTH_TEST)
+                               for i in xrange(2, 15, 2):
+                                       glStencilFunc(GL_EQUAL, i, 0xFF)
+                                       glColor(float(i)/10, float(i)/10, float(i)/5)
+                                       glBegin(GL_QUADS)
+                                       glVertex3f(-1000,-1000,-10)
+                                       glVertex3f( 1000,-1000,-10)
+                                       glVertex3f( 1000, 1000,-10)
+                                       glVertex3f(-1000, 1000,-10)
+                                       glEnd()
+                               for i in xrange(1, 15, 2):
+                                       glStencilFunc(GL_EQUAL, i, 0xFF)
+                                       glColor(float(i)/10, 0, 0)
+                                       glBegin(GL_QUADS)
+                                       glVertex3f(-1000,-1000,-10)
+                                       glVertex3f( 1000,-1000,-10)
+                                       glVertex3f( 1000, 1000,-10)
+                                       glVertex3f(-1000, 1000,-10)
+                                       glEnd()
+                               glPopMatrix()
+                               glDisable(GL_STENCIL_TEST)
+                               glEnable(GL_DEPTH_TEST)
+
+                       self._objectShader.unbind()
+
+                       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+                       glEnable(GL_BLEND)
+                       if self._objectLoadShader is not None:
+                               self._objectLoadShader.bind()
+                               glColor4f(0.2, 0.6, 1.0, 1.0)
+                               for obj in self._scene.objects():
+                                       if obj._loadAnim is None:
+                                               continue
+                                       self._objectLoadShader.setUniform('intensity', obj._loadAnim.getPosition())
+                                       self._objectLoadShader.setUniform('scale', obj.getBoundaryCircle() / 10)
+                                       self._renderObject(obj)
+                               self._objectLoadShader.unbind()
+                               glDisable(GL_BLEND)
 
                self._drawMachine()
 
-               #Draw the object box-shadow, so you can see where it will collide with other objects.
-               if self._selectedObj is not None and len(self._scene.objects()) > 1:
-                       size = self._selectedObj.getSize()[0:2] / 2 + self._scene.getObjectExtend()
-                       glPushMatrix()
-                       glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0.0)
+               if self._usbPrintMonitor.getState() == 'PRINTING' and self._usbPrintMonitor.getID() == self._slicer.getID():
                        glEnable(GL_BLEND)
-                       glEnable(GL_CULL_FACE)
-                       glColor4f(0,0,0,0.12)
+                       z = self._usbPrintMonitor.getZ()
+                       size = self._machineSize
+                       glColor4ub(255,255,0,128)
                        glBegin(GL_QUADS)
-                       glVertex3f(-size[0],  size[1], 0)
-                       glVertex3f(-size[0], -size[1], 0)
-                       glVertex3f( size[0], -size[1], 0)
-                       glVertex3f( size[0],  size[1], 0)
+                       glVertex3f(-size[0]/2,-size[1]/2, z)
+                       glVertex3f( size[0]/2,-size[1]/2, z)
+                       glVertex3f( size[0]/2, size[1]/2, z)
+                       glVertex3f(-size[0]/2, size[1]/2, z)
                        glEnd()
-                       glDisable(GL_CULL_FACE)
-                       glPopMatrix()
 
-               #Draw the outline of the selected object, on top of everything else except the GUI.
-               if self._selectedObj is not None and self._selectedObj._loadAnim is None:
-                       glDisable(GL_DEPTH_TEST)
-                       glEnable(GL_CULL_FACE)
-                       glEnable(GL_STENCIL_TEST)
-                       glStencilFunc(GL_EQUAL, 0, 255)
+               if self.viewMode == 'gcode':
+                       if self._gcodeLoadThread is not None and self._gcodeLoadThread.isAlive():
+                               glDisable(GL_DEPTH_TEST)
+                               glPushMatrix()
+                               glLoadIdentity()
+                               glTranslate(0,-4,-10)
+                               glColor4ub(60,60,60,255)
+                               opengl.glDrawStringCenter(_("Loading toolpath for visualization..."))
+                               glPopMatrix()
+               else:
+                       #Draw the object box-shadow, so you can see where it will collide with other objects.
+                       if self._selectedObj is not None:
+                               glEnable(GL_BLEND)
+                               glEnable(GL_CULL_FACE)
+                               glColor4f(0,0,0,0.16)
+                               glDepthMask(False)
+                               for obj in self._scene.objects():
+                                       glPushMatrix()
+                                       glTranslatef(obj.getPosition()[0], obj.getPosition()[1], 0)
+                                       glBegin(GL_TRIANGLE_FAN)
+                                       for p in obj._boundaryHull[::-1]:
+                                               glVertex3f(p[0], p[1], 0)
+                                       glEnd()
+                                       glPopMatrix()
+                               glPushMatrix()
+                               glColor4f(0,0,0,0.06)
+                               glTranslatef(self._selectedObj.getPosition()[0], self._selectedObj.getPosition()[1], 0)
+                               glBegin(GL_TRIANGLE_FAN)
+                               for p in self._selectedObj._printAreaHull[::-1]:
+                                       glVertex3f(p[0], p[1], 0)
+                               glEnd()
+                               glBegin(GL_TRIANGLE_FAN)
+                               for p in self._selectedObj._headAreaHull[::-1]:
+                                       glVertex3f(p[0], p[1], 0)
+                               glEnd()
+                               glDepthMask(True)
+                               glDisable(GL_CULL_FACE)
+                               glPopMatrix()
+
+                       #Draw the outline of the selected object, on top of everything else except the GUI.
+                       if self._selectedObj is not None and self._selectedObj._loadAnim is None:
+                               glDisable(GL_DEPTH_TEST)
+                               glEnable(GL_CULL_FACE)
+                               glEnable(GL_STENCIL_TEST)
+                               glDisable(GL_BLEND)
+                               glStencilFunc(GL_EQUAL, 0, 255)
 
-                       glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
-                       glLineWidth(2)
-                       glColor4f(1,1,1,0.5)
-                       self._renderObject(self._selectedObj)
-                       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
+                               glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
+                               glLineWidth(2)
+                               glColor4f(1,1,1,0.5)
+                               self._renderObject(self._selectedObj)
+                               glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
 
-                       glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
-                       glDisable(GL_STENCIL_TEST)
-                       glDisable(GL_CULL_FACE)
-                       glEnable(GL_DEPTH_TEST)
+                               glViewport(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
+                               glDisable(GL_STENCIL_TEST)
+                               glDisable(GL_CULL_FACE)
+                               glEnable(GL_DEPTH_TEST)
 
-               if self._selectedObj is not None:
+                       if self._selectedObj is not None:
+                               glPushMatrix()
+                               pos = self.getObjectCenterPos()
+                               glTranslate(pos[0], pos[1], pos[2])
+                               self.tool.OnDraw()
+                               glPopMatrix()
+               if self.viewMode == 'overhang' and not opengl.hasShaderSupport():
+                       glDisable(GL_DEPTH_TEST)
                        glPushMatrix()
-                       pos = self.getObjectCenterPos()
-                       glTranslate(pos[0], pos[1], pos[2])
-                       self.tool.OnDraw()
+                       glLoadIdentity()
+                       glTranslate(0,-4,-10)
+                       glColor4ub(60,60,60,255)
+                       opengl.glDrawStringCenter(_("Overhang view not working due to lack of OpenGL shaders support."))
                        glPopMatrix()
 
-       def _renderObject(self, obj):
+       def _renderObject(self, obj, brightness = False, addSink = True):
                glPushMatrix()
-               glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
+               if addSink:
+                       glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2 - profile.getProfileSettingFloat('object_sink'))
+               else:
+                       glTranslate(obj.getPosition()[0], obj.getPosition()[1], obj.getSize()[2] / 2)
 
                if self.tempMatrix is not None and obj == self._selectedObj:
                        tempMatrix = opengl.convert3x3MatrixTo4x4(self.tempMatrix)
@@ -651,9 +1238,13 @@ void main(void)
                tempMatrix = opengl.convert3x3MatrixTo4x4(obj.getMatrix())
                glMultMatrixf(tempMatrix)
 
+               n = 0
                for m in obj._meshList:
                        if m.vbo is None:
                                m.vbo = opengl.GLVBO(m.vertexes, m.normal)
+                       if brightness:
+                               glColor4fv(map(lambda n: n * brightness, self._objColors[n]))
+                               n += 1
                        m.vbo.render()
                glPopMatrix()
 
@@ -661,13 +1252,75 @@ void main(void)
                glEnable(GL_CULL_FACE)
                glEnable(GL_BLEND)
 
-               if profile.getPreference('machine_type') == 'ultimaker':
+               size = [profile.getMachineSettingFloat('machine_width'), profile.getMachineSettingFloat('machine_depth'), profile.getMachineSettingFloat('machine_height')]
+
+               machine = profile.getMachineSetting('machine_type')
+               if profile.getMachineSetting('machine_type').startswith('ultimaker'):
+                       if machine not in self._platformMesh:
+                               meshes = meshLoader.loadMeshes(resources.getPathForMesh(machine + '_platform.stl'))
+                               if len(meshes) > 0:
+                                       self._platformMesh[machine] = meshes[0]
+                               else:
+                                       self._platformMesh[machine] = None
+                               if machine == 'ultimaker2':
+                                       self._platformMesh[machine]._drawOffset = numpy.array([0,-37,145], numpy.float32)
+                               else:
+                                       self._platformMesh[machine]._drawOffset = numpy.array([0,0,2.5], numpy.float32)
                        glColor4f(1,1,1,0.5)
                        self._objectShader.bind()
-                       self._renderObject(self._platformMesh)
+                       self._renderObject(self._platformMesh[machine], False, False)
                        self._objectShader.unbind()
 
-               size = [profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')]
+                       #For the Ultimaker 2 render the texture on the back plate to show the Ultimaker2 text.
+                       if machine == 'ultimaker2':
+                               if not hasattr(self._platformMesh[machine], 'texture'):
+                                       self._platformMesh[machine].texture = opengl.loadGLTexture('Ultimaker2backplate.png')
+                               glBindTexture(GL_TEXTURE_2D, self._platformMesh[machine].texture)
+                               glEnable(GL_TEXTURE_2D)
+                               glPushMatrix()
+                               glColor4f(1,1,1,1)
+
+                               glTranslate(0,150,-5)
+                               h = 50
+                               d = 8
+                               w = 100
+                               glEnable(GL_BLEND)
+                               glBlendFunc(GL_DST_COLOR, GL_ZERO)
+                               glBegin(GL_QUADS)
+                               glTexCoord2f(1, 0)
+                               glVertex3f( w, 0, h)
+                               glTexCoord2f(0, 0)
+                               glVertex3f(-w, 0, h)
+                               glTexCoord2f(0, 1)
+                               glVertex3f(-w, 0, 0)
+                               glTexCoord2f(1, 1)
+                               glVertex3f( w, 0, 0)
+
+                               glTexCoord2f(1, 0)
+                               glVertex3f(-w, d, h)
+                               glTexCoord2f(0, 0)
+                               glVertex3f( w, d, h)
+                               glTexCoord2f(0, 1)
+                               glVertex3f( w, d, 0)
+                               glTexCoord2f(1, 1)
+                               glVertex3f(-w, d, 0)
+                               glEnd()
+                               glDisable(GL_TEXTURE_2D)
+                               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+                               glPopMatrix()
+               else:
+                       glColor4f(0,0,0,1)
+                       glLineWidth(3)
+                       glBegin(GL_LINES)
+                       glVertex3f(-size[0] / 2, -size[1] / 2, 0)
+                       glVertex3f(-size[0] / 2, -size[1] / 2, 10)
+                       glVertex3f(-size[0] / 2, -size[1] / 2, 0)
+                       glVertex3f(-size[0] / 2+10, -size[1] / 2, 0)
+                       glVertex3f(-size[0] / 2, -size[1] / 2, 0)
+                       glVertex3f(-size[0] / 2, -size[1] / 2+10, 0)
+                       glEnd()
+
+               #Cornerpoints for big blue square
                v0 = [ size[0] / 2, size[1] / 2, size[2]]
                v1 = [ size[0] / 2,-size[1] / 2, size[2]]
                v2 = [-size[0] / 2, size[1] / 2, size[2]]
@@ -681,13 +1334,16 @@ void main(void)
                glEnableClientState(GL_VERTEX_ARRAY)
                glVertexPointer(3, GL_FLOAT, 3*4, vList)
 
+               glDepthMask(False)
                glColor4ub(5, 171, 231, 64)
                glDrawArrays(GL_QUADS, 0, 4)
                glColor4ub(5, 171, 231, 96)
                glDrawArrays(GL_QUADS, 4, 8)
                glColor4ub(5, 171, 231, 128)
                glDrawArrays(GL_QUADS, 12, 8)
+               glDisableClientState(GL_VERTEX_ARRAY)
 
+               #Draw checkerboard
                sx = self._machineSize[0]
                sy = self._machineSize[1]
                for x in xrange(-int(sx/20)-1, int(sx / 20) + 1):
@@ -700,27 +1356,177 @@ void main(void)
                                y1 = max(min(y1, sy/2), -sy/2)
                                x2 = max(min(x2, sx/2), -sx/2)
                                y2 = max(min(y2, sy/2), -sy/2)
+                               #Black or "white"  checker
                                if (x & 1) == (y & 1):
                                        glColor4ub(5, 171, 231, 127)
                                else:
                                        glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
                                glBegin(GL_QUADS)
-                               glVertex3f(x1, y1, -0.02)
-                               glVertex3f(x2, y1, -0.02)
-                               glVertex3f(x2, y2, -0.02)
-                               glVertex3f(x1, y2, -0.02)
+                               glVertex3f(x1, y1, 0)
+                               glVertex3f(x2, y1, 0)
+                               glVertex3f(x2, y2, 0)
+                               glVertex3f(x1, y2, 0)
                                glEnd()
 
-               glDisableClientState(GL_VERTEX_ARRAY)
+               if machine == 'ultimaker2':
+
+                       glColor4ub(127, 127, 127, 200)
+                       #if UM2, draw bat-area zone for head. THe head can't stop there, because its bat-area.
+                       #UpperRight
+                       clipWidth = 25
+                       clipHeight = 10
+                       posX = sx / 2 - clipWidth
+                       posY = sy / 2 - clipHeight
+                       glBegin(GL_QUADS)
+                       glVertex3f(posX, posY, 0)
+                       glVertex3f(posX+clipWidth, posY, 0)
+                       glVertex3f(posX+clipWidth, posY+clipHeight, 0)
+                       glVertex3f(posX, posY+clipHeight, 0)
+                       glEnd()
+                       #UpperLeft
+                       clipWidth = 25
+                       clipHeight = 10
+                       posX = -sx / 2
+                       posY = sy / 2 - clipHeight
+                       glBegin(GL_QUADS)
+                       glVertex3f(posX, posY, 0)
+                       glVertex3f(posX+clipWidth, posY, 0)
+                       glVertex3f(posX+clipWidth, posY+clipHeight, 0)
+                       glVertex3f(posX, posY+clipHeight, 0)
+                       glEnd()
+                       #LowerRight
+                       clipWidth = 25
+                       clipHeight = 10
+                       posX = sx / 2 - clipWidth
+                       posY = -sy / 2
+                       glBegin(GL_QUADS)
+                       glVertex3f(posX, posY, 0)
+                       glVertex3f(posX+clipWidth, posY, 0)
+                       glVertex3f(posX+clipWidth, posY+clipHeight, 0)
+                       glVertex3f(posX, posY+clipHeight, 0)
+                       glEnd()
+                       #LowerLeft
+                       clipWidth = 25
+                       clipHeight = 10
+                       posX = -sx / 2
+                       posY = -sy / 2
+                       glBegin(GL_QUADS)
+                       glVertex3f(posX, posY, 0)
+                       glVertex3f(posX+clipWidth, posY, 0)
+                       glVertex3f(posX+clipWidth, posY+clipHeight, 0)
+                       glVertex3f(posX, posY+clipHeight, 0)
+                       glEnd()
+
+               glDepthMask(True)
                glDisable(GL_BLEND)
                glDisable(GL_CULL_FACE)
 
+       def _generateGCodeVBOs(self, layer):
+               ret = []
+               for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
+                       if ':' in extrudeType:
+                               extruder = int(extrudeType[extrudeType.find(':')+1:])
+                               extrudeType = extrudeType[0:extrudeType.find(':')]
+                       else:
+                               extruder = None
+                       pointList = numpy.zeros((0,3), numpy.float32)
+                       for path in layer:
+                               if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
+                                       a = path['points']
+                                       a = numpy.concatenate((a[:-1], a[1:]), 1)
+                                       a = a.reshape((len(a) * 2, 3))
+                                       pointList = numpy.concatenate((pointList, a))
+                       ret.append(opengl.GLVBO(pointList))
+               return ret
+
+       def _generateGCodeVBOs2(self, layer):
+               filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
+               filamentArea = math.pi * filamentRadius * filamentRadius
+               useFilamentArea = profile.getMachineSetting('gcode_flavor') == 'UltiGCode'
+
+               ret = []
+               for extrudeType in ['WALL-OUTER:0', 'WALL-OUTER:1', 'WALL-OUTER:2', 'WALL-OUTER:3', 'WALL-INNER', 'FILL', 'SUPPORT', 'SKIRT']:
+                       if ':' in extrudeType:
+                               extruder = int(extrudeType[extrudeType.find(':')+1:])
+                               extrudeType = extrudeType[0:extrudeType.find(':')]
+                       else:
+                               extruder = None
+                       pointList = numpy.zeros((0,3), numpy.float32)
+                       for path in layer:
+                               if path['type'] == 'extrude' and path['pathType'] == extrudeType and (extruder is None or path['extruder'] == extruder):
+                                       a = path['points']
+                                       if extrudeType == 'FILL':
+                                               a[:,2] += 0.01
+
+                                       normal = a[1:] - a[:-1]
+                                       lens = numpy.sqrt(normal[:,0]**2 + normal[:,1]**2)
+                                       normal[:,0], normal[:,1] = -normal[:,1] / lens, normal[:,0] / lens
+                                       normal[:,2] /= lens
+
+                                       ePerDist = path['extrusion'][1:] / lens
+                                       if useFilamentArea:
+                                               lineWidth = ePerDist / path['layerThickness'] / 2.0
+                                       else:
+                                               lineWidth = ePerDist * (filamentArea / path['layerThickness'] / 2)
+
+                                       normal[:,0] *= lineWidth
+                                       normal[:,1] *= lineWidth
+
+                                       b = numpy.zeros((len(a)-1, 0), numpy.float32)
+                                       b = numpy.concatenate((b, a[1:] + normal), 1)
+                                       b = numpy.concatenate((b, a[1:] - normal), 1)
+                                       b = numpy.concatenate((b, a[:-1] - normal), 1)
+                                       b = numpy.concatenate((b, a[:-1] + normal), 1)
+                                       b = b.reshape((len(b) * 4, 3))
+
+                                       if len(a) > 2:
+                                               normal2 = normal[:-1] + normal[1:]
+                                               lens2 = numpy.sqrt(normal2[:,0]**2 + normal2[:,1]**2)
+                                               normal2[:,0] /= lens2
+                                               normal2[:,1] /= lens2
+                                               normal2[:,0] *= lineWidth[:-1]
+                                               normal2[:,1] *= lineWidth[:-1]
+
+                                               c = numpy.zeros((len(a)-2, 0), numpy.float32)
+                                               c = numpy.concatenate((c, a[1:-1]), 1)
+                                               c = numpy.concatenate((c, a[1:-1]+normal[1:]), 1)
+                                               c = numpy.concatenate((c, a[1:-1]+normal2), 1)
+                                               c = numpy.concatenate((c, a[1:-1]+normal[:-1]), 1)
+
+                                               c = numpy.concatenate((c, a[1:-1]), 1)
+                                               c = numpy.concatenate((c, a[1:-1]-normal[1:]), 1)
+                                               c = numpy.concatenate((c, a[1:-1]-normal2), 1)
+                                               c = numpy.concatenate((c, a[1:-1]-normal[:-1]), 1)
+
+                                               c = c.reshape((len(c) * 8, 3))
+
+                                               pointList = numpy.concatenate((pointList, b, c))
+                                       else:
+                                               pointList = numpy.concatenate((pointList, b))
+                       ret.append(opengl.GLVBO(pointList))
+
+               pointList = numpy.zeros((0,3), numpy.float32)
+               for path in layer:
+                       if path['type'] == 'move':
+                               a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
+                               a = numpy.concatenate((a[:-1], a[1:]), 1)
+                               a = a.reshape((len(a) * 2, 3))
+                               pointList = numpy.concatenate((pointList, a))
+                       if path['type'] == 'retract':
+                               a = path['points'] + numpy.array([0,0,0.01], numpy.float32)
+                               a = numpy.concatenate((a[:-1], a[1:] + numpy.array([0,0,1], numpy.float32)), 1)
+                               a = a.reshape((len(a) * 2, 3))
+                               pointList = numpy.concatenate((pointList, a))
+               ret.append(opengl.GLVBO(pointList))
+
+               return ret
+
        def getObjectCenterPos(self):
                if self._selectedObj is None:
                        return [0.0, 0.0, 0.0]
                pos = self._selectedObj.getPosition()
                size = self._selectedObj.getSize()
-               return [pos[0], pos[1], size[2]/2]
+               return [pos[0], pos[1], size[2]/2 - profile.getProfileSettingFloat('object_sink')]
 
        def getObjectBoundaryCircle(self):
                if self._selectedObj is None: