chiark / gitweb /
Updated all lineends for py files to unix style.
authorDaid <daid303@gmail.com>
Thu, 6 Dec 2012 13:12:13 +0000 (14:12 +0100)
committerDaid <daid303@gmail.com>
Thu, 6 Dec 2012 13:12:13 +0000 (14:12 +0100)
Cura/avr_isp/chipDB.py
Cura/avr_isp/intelHex.py
Cura/avr_isp/ispBase.py
Cura/avr_isp/stk500v2.py
Cura/gui/alterationPanel.py
Cura/gui/configWizard.py
Cura/gui/opengl.py
Cura/gui/preferencesDialog.py
Cura/gui/preview3d.py
Cura/gui/projectPlanner.py
Cura/util/profile.py

index 5c193225949e77b2202daffb1b5e9f1f4cbaf002..be381c2a8ddb3bf37e43dcdc40ef1720d1d254e1 100644 (file)
@@ -1,20 +1,20 @@
-\r
-avrChipDB = {\r
-       'ATMega1280': {\r
-               'signature': [0x1E, 0x97, 0x03],\r
-               'pageSize': 128,\r
-               'pageCount': 512,\r
-       },\r
-       'ATMega2560': {\r
-               'signature': [0x1E, 0x98, 0x01],\r
-               'pageSize': 128,\r
-               'pageCount': 1024,\r
-       },\r
-}\r
-\r
-def getChipFromDB(sig):\r
-       for chip in avrChipDB.values():\r
-               if chip['signature'] == sig:\r
-                       return chip\r
-       return False\r
-\r
+
+avrChipDB = {
+       'ATMega1280': {
+               'signature': [0x1E, 0x97, 0x03],
+               'pageSize': 128,
+               'pageCount': 512,
+       },
+       'ATMega2560': {
+               'signature': [0x1E, 0x98, 0x01],
+               'pageSize': 128,
+               'pageCount': 1024,
+       },
+}
+
+def getChipFromDB(sig):
+       for chip in avrChipDB.values():
+               if chip['signature'] == sig:
+                       return chip
+       return False
+
index 2023873eded56c996839ceb1a97485710ae251f1..a7150dc774d2fe3c6deee2ec75b97bdfbdefb807 100644 (file)
@@ -1,35 +1,35 @@
-import io\r
-\r
-def readHex(filename):\r
-       data = []\r
-       extraAddr = 0\r
-       f = io.open(filename, "r")\r
-       for line in f:\r
-               line = line.strip()\r
-               if line[0] != ':':\r
-                       raise Exception("Hex file has a line not starting with ':'")\r
-               recLen = int(line[1:3], 16)\r
-               addr = int(line[3:7], 16) + extraAddr\r
-               recType = int(line[7:9], 16)\r
-               if len(line) != recLen * 2 + 11:\r
-                       raise Exception("Error in hex file: " + line)\r
-               checkSum = 0\r
-               for i in xrange(0, recLen + 5):\r
-                       checkSum += int(line[i*2+1:i*2+3], 16)\r
-               checkSum &= 0xFF\r
-               if checkSum != 0:\r
-                       raise Exception("Checksum error in hex file: " + line)\r
-               \r
-               if recType == 0:#Data record\r
-                       while len(data) < addr + recLen:\r
-                               data.append(0)\r
-                       for i in xrange(0, recLen):\r
-                               data[addr + i] = int(line[i*2+9:i*2+11], 16)\r
-               elif recType == 1:      #End Of File record\r
-                       pass\r
-               elif recType == 2:      #Extended Segment Address Record\r
-                       extraAddr = int(line[9:13], 16) * 16\r
-               else:\r
-                       print(recType, recLen, addr, checkSum, line)\r
-       f.close()\r
+import io
+
+def readHex(filename):
+       data = []
+       extraAddr = 0
+       f = io.open(filename, "r")
+       for line in f:
+               line = line.strip()
+               if line[0] != ':':
+                       raise Exception("Hex file has a line not starting with ':'")
+               recLen = int(line[1:3], 16)
+               addr = int(line[3:7], 16) + extraAddr
+               recType = int(line[7:9], 16)
+               if len(line) != recLen * 2 + 11:
+                       raise Exception("Error in hex file: " + line)
+               checkSum = 0
+               for i in xrange(0, recLen + 5):
+                       checkSum += int(line[i*2+1:i*2+3], 16)
+               checkSum &= 0xFF
+               if checkSum != 0:
+                       raise Exception("Checksum error in hex file: " + line)
+               
+               if recType == 0:#Data record
+                       while len(data) < addr + recLen:
+                               data.append(0)
+                       for i in xrange(0, recLen):
+                               data[addr + i] = int(line[i*2+9:i*2+11], 16)
+               elif recType == 1:      #End Of File record
+                       pass
+               elif recType == 2:      #Extended Segment Address Record
+                       extraAddr = int(line[9:13], 16) * 16
+               else:
+                       print(recType, recLen, addr, checkSum, line)
+       f.close()
        return data
\ No newline at end of file
index 5330d53e5205891bfc50a1535b27e0d62a735578..6c3537216d2244fcee404f9da588d6471039b117 100644 (file)
@@ -1,35 +1,35 @@
-import os, struct, sys, time\r
-\r
-from serial import Serial\r
-\r
-import chipDB\r
-\r
-class IspBase():\r
-       def programChip(self, flashData):\r
-               self.curExtAddr = -1\r
-               self.chip = chipDB.getChipFromDB(self.getSignature())\r
-               if self.chip == False:\r
-                       raise IspError("Chip with signature: " + str(self.getSignature()) + "not found")\r
-               self.chipErase()\r
-               \r
-               print("Flashing %i bytes" % len(flashData))\r
-               self.writeFlash(flashData)\r
-               print("Verifying %i bytes" % len(flashData))\r
-               self.verifyFlash(flashData)\r
-\r
-       #low level ISP commands\r
-       def getSignature(self):\r
-               sig = []\r
-               sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3])\r
-               sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3])\r
-               sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3])\r
-               return sig\r
-       \r
-       def chipErase(self):\r
-               self.sendISP([0xAC, 0x80, 0x00, 0x00])\r
-\r
-class IspError():\r
-       def __init__(self, value):\r
-               self.value = value\r
-       def __str__(self):\r
-               return repr(self.value)\r
+import os, struct, sys, time
+
+from serial import Serial
+
+import chipDB
+
+class IspBase():
+       def programChip(self, flashData):
+               self.curExtAddr = -1
+               self.chip = chipDB.getChipFromDB(self.getSignature())
+               if self.chip == False:
+                       raise IspError("Chip with signature: " + str(self.getSignature()) + "not found")
+               self.chipErase()
+               
+               print("Flashing %i bytes" % len(flashData))
+               self.writeFlash(flashData)
+               print("Verifying %i bytes" % len(flashData))
+               self.verifyFlash(flashData)
+
+       #low level ISP commands
+       def getSignature(self):
+               sig = []
+               sig.append(self.sendISP([0x30, 0x00, 0x00, 0x00])[3])
+               sig.append(self.sendISP([0x30, 0x00, 0x01, 0x00])[3])
+               sig.append(self.sendISP([0x30, 0x00, 0x02, 0x00])[3])
+               return sig
+       
+       def chipErase(self):
+               self.sendISP([0xAC, 0x80, 0x00, 0x00])
+
+class IspError():
+       def __init__(self, value):
+               self.value = value
+       def __str__(self):
+               return repr(self.value)
index 8577d86d8088bc9a0039bdfe8ae11853af40b1e3..7365d2cd5cd2aa9f72f1f53fc31fdabaefabefb5 100644 (file)
-import os, struct, sys, time\r
-\r
-from serial import Serial\r
-from serial import SerialException\r
-\r
-import ispBase, intelHex\r
-\r
-class Stk500v2(ispBase.IspBase):\r
-       def __init__(self):\r
-               self.serial = None\r
-               self.seq = 1\r
-               self.lastAddr = -1\r
-               self.progressCallback = None\r
-       \r
-       def connect(self, port = 'COM22', speed = 115200):\r
-               if self.serial != None:\r
-                       self.close()\r
-               try:\r
-                       self.serial = Serial(port, speed, timeout=1, writeTimeout=10000)\r
-               except SerialException as e:\r
-                       raise ispBase.IspError("Failed to open serial port")\r
-               except:\r
-                       raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0]))\r
-               self.seq = 1\r
-               \r
-               #Reset the controller\r
-               self.serial.setDTR(1)\r
-               time.sleep(0.1)\r
-               self.serial.setDTR(0)\r
-               time.sleep(0.2)\r
-               \r
-               self.sendMessage([1])\r
-               if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]:\r
-                       self.close()\r
-                       raise ispBase.IspError("Failed to enter programming mode")\r
-\r
-       def close(self):\r
-               if self.serial != None:\r
-                       self.serial.close()\r
-                       self.serial = None\r
-\r
-       #Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface.\r
-       #       This allows you to use the serial port without opening it again.\r
-       def leaveISP(self):\r
-               if self.serial != None:\r
-                       if self.sendMessage([0x11]) != [0x11, 0x00]:\r
-                               raise ispBase.IspError("Failed to leave programming mode")\r
-                       ret = self.serial\r
-                       self.serial = None\r
-                       return ret\r
-               return None\r
-       \r
-       def isConnected(self):\r
-               return self.serial != None\r
-       \r
-       def sendISP(self, data):\r
-               recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]])\r
-               return recv[2:6]\r
-       \r
-       def writeFlash(self, flashData):\r
-               #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension\r
-               pageSize = self.chip['pageSize'] * 2\r
-               flashSize = pageSize * self.chip['pageCount']\r
-               if flashSize > 0xFFFF:\r
-                       self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])\r
-               else:\r
-                       self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])\r
-               \r
-               loadCount = (len(flashData) + pageSize - 1) / pageSize\r
-               for i in xrange(0, loadCount):\r
-                       recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)])\r
-                       if self.progressCallback != None:\r
-                               self.progressCallback(i + 1, loadCount*2)\r
-       \r
-       def verifyFlash(self, flashData):\r
-               #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension\r
-               flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount']\r
-               if flashSize > 0xFFFF:\r
-                       self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])\r
-               else:\r
-                       self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])\r
-               \r
-               loadCount = (len(flashData) + 0xFF) / 0x100\r
-               for i in xrange(0, loadCount):\r
-                       recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102]\r
-                       if self.progressCallback != None:\r
-                               self.progressCallback(loadCount + i + 1, loadCount*2)\r
-                       for j in xrange(0, 0x100):\r
-                               if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]:\r
-                                       raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j))\r
-\r
-       def sendMessage(self, data):\r
-               message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E)\r
-               for c in data:\r
-                       message += struct.pack(">B", c)\r
-               checksum = 0\r
-               for c in message:\r
-                       checksum ^= ord(c)\r
-               message += struct.pack(">B", checksum)\r
-               try:\r
-                       self.serial.write(message)\r
-                       self.serial.flush()\r
-               except SerialTimeoutException:\r
-                       raise ispBase.IspError('Serial send timeout')\r
-               self.seq = (self.seq + 1) & 0xFF\r
-               return self.recvMessage()\r
-       \r
-       def recvMessage(self):\r
-               state = 'Start'\r
-               checksum = 0\r
-               while True:\r
-                       s = self.serial.read()\r
-                       if len(s) < 1:\r
-                               raise ispBase.IspError("Timeout")\r
-                       b = struct.unpack(">B", s)[0]\r
-                       checksum ^= b\r
-                       #print(hex(b))\r
-                       if state == 'Start':\r
-                               if b == 0x1B:\r
-                                       state = 'GetSeq'\r
-                                       checksum = 0x1B\r
-                       elif state == 'GetSeq':\r
-                               state = 'MsgSize1'\r
-                       elif state == 'MsgSize1':\r
-                               msgSize = b << 8\r
-                               state = 'MsgSize2'\r
-                       elif state == 'MsgSize2':\r
-                               msgSize |= b\r
-                               state = 'Token'\r
-                       elif state == 'Token':\r
-                               if b != 0x0E:\r
-                                       state = 'Start'\r
-                               else:\r
-                                       state = 'Data'\r
-                                       data = []\r
-                       elif state == 'Data':\r
-                               data.append(b)\r
-                               if len(data) == msgSize:\r
-                                       state = 'Checksum'\r
-                       elif state == 'Checksum':\r
-                               if checksum != 0:\r
-                                       state = 'Start'\r
-                               else:\r
-                                       return data\r
-\r
-\r
-def main():\r
-       programmer = Stk500v2()\r
-       programmer.connect(port = sys.argv[1])\r
-       programmer.programChip(intelHex.readHex(sys.argv[2]))\r
-       sys.exit(1)\r
-\r
-if __name__ == '__main__':\r
-       main()\r
+import os, struct, sys, time
+
+from serial import Serial
+from serial import SerialException
+
+import ispBase, intelHex
+
+class Stk500v2(ispBase.IspBase):
+       def __init__(self):
+               self.serial = None
+               self.seq = 1
+               self.lastAddr = -1
+               self.progressCallback = None
+       
+       def connect(self, port = 'COM22', speed = 115200):
+               if self.serial != None:
+                       self.close()
+               try:
+                       self.serial = Serial(port, speed, timeout=1, writeTimeout=10000)
+               except SerialException as e:
+                       raise ispBase.IspError("Failed to open serial port")
+               except:
+                       raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0]))
+               self.seq = 1
+               
+               #Reset the controller
+               self.serial.setDTR(1)
+               time.sleep(0.1)
+               self.serial.setDTR(0)
+               time.sleep(0.2)
+               
+               self.sendMessage([1])
+               if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]:
+                       self.close()
+                       raise ispBase.IspError("Failed to enter programming mode")
+
+       def close(self):
+               if self.serial != None:
+                       self.serial.close()
+                       self.serial = None
+
+       #Leave ISP does not reset the serial port, only resets the device, and returns the serial port after disconnecting it from the programming interface.
+       #       This allows you to use the serial port without opening it again.
+       def leaveISP(self):
+               if self.serial != None:
+                       if self.sendMessage([0x11]) != [0x11, 0x00]:
+                               raise ispBase.IspError("Failed to leave programming mode")
+                       ret = self.serial
+                       self.serial = None
+                       return ret
+               return None
+       
+       def isConnected(self):
+               return self.serial != None
+       
+       def sendISP(self, data):
+               recv = self.sendMessage([0x1D, 4, 4, 0, data[0], data[1], data[2], data[3]])
+               return recv[2:6]
+       
+       def writeFlash(self, flashData):
+               #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension
+               pageSize = self.chip['pageSize'] * 2
+               flashSize = pageSize * self.chip['pageCount']
+               if flashSize > 0xFFFF:
+                       self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
+               else:
+                       self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])
+               
+               loadCount = (len(flashData) + pageSize - 1) / pageSize
+               for i in xrange(0, loadCount):
+                       recv = self.sendMessage([0x13, pageSize >> 8, pageSize & 0xFF, 0xc1, 0x0a, 0x40, 0x4c, 0x20, 0x00, 0x00] + flashData[(i * pageSize):(i * pageSize + pageSize)])
+                       if self.progressCallback != None:
+                               self.progressCallback(i + 1, loadCount*2)
+       
+       def verifyFlash(self, flashData):
+               #Set load addr to 0, in case we have more then 64k flash we need to enable the address extension
+               flashSize = self.chip['pageSize'] * 2 * self.chip['pageCount']
+               if flashSize > 0xFFFF:
+                       self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
+               else:
+                       self.sendMessage([0x06, 0x00, 0x00, 0x00, 0x00])
+               
+               loadCount = (len(flashData) + 0xFF) / 0x100
+               for i in xrange(0, loadCount):
+                       recv = self.sendMessage([0x14, 0x01, 0x00, 0x20])[2:0x102]
+                       if self.progressCallback != None:
+                               self.progressCallback(loadCount + i + 1, loadCount*2)
+                       for j in xrange(0, 0x100):
+                               if i * 0x100 + j < len(flashData) and flashData[i * 0x100 + j] != recv[j]:
+                                       raise ispBase.IspError('Verify error at: 0x%x' % (i * 0x100 + j))
+
+       def sendMessage(self, data):
+               message = struct.pack(">BBHB", 0x1B, self.seq, len(data), 0x0E)
+               for c in data:
+                       message += struct.pack(">B", c)
+               checksum = 0
+               for c in message:
+                       checksum ^= ord(c)
+               message += struct.pack(">B", checksum)
+               try:
+                       self.serial.write(message)
+                       self.serial.flush()
+               except SerialTimeoutException:
+                       raise ispBase.IspError('Serial send timeout')
+               self.seq = (self.seq + 1) & 0xFF
+               return self.recvMessage()
+       
+       def recvMessage(self):
+               state = 'Start'
+               checksum = 0
+               while True:
+                       s = self.serial.read()
+                       if len(s) < 1:
+                               raise ispBase.IspError("Timeout")
+                       b = struct.unpack(">B", s)[0]
+                       checksum ^= b
+                       #print(hex(b))
+                       if state == 'Start':
+                               if b == 0x1B:
+                                       state = 'GetSeq'
+                                       checksum = 0x1B
+                       elif state == 'GetSeq':
+                               state = 'MsgSize1'
+                       elif state == 'MsgSize1':
+                               msgSize = b << 8
+                               state = 'MsgSize2'
+                       elif state == 'MsgSize2':
+                               msgSize |= b
+                               state = 'Token'
+                       elif state == 'Token':
+                               if b != 0x0E:
+                                       state = 'Start'
+                               else:
+                                       state = 'Data'
+                                       data = []
+                       elif state == 'Data':
+                               data.append(b)
+                               if len(data) == msgSize:
+                                       state = 'Checksum'
+                       elif state == 'Checksum':
+                               if checksum != 0:
+                                       state = 'Start'
+                               else:
+                                       return data
+
+
+def main():
+       programmer = Stk500v2()
+       programmer.connect(port = sys.argv[1])
+       programmer.programChip(intelHex.readHex(sys.argv[2]))
+       sys.exit(1)
+
+if __name__ == '__main__':
+       main()
index 6c532eeef2be6d1036c27a2e251c6248aad5e57a..e2a6edd0ddb6e86e69811657bc80f908b2ee5273 100644 (file)
@@ -1,48 +1,48 @@
-import wx,wx.stc\r
-import sys,math,threading,os\r
-\r
-from gui import gcodeTextArea\r
-from util import profile\r
-\r
-class alterationPanel(wx.Panel):\r
-       def __init__(self, parent):\r
-               wx.Panel.__init__(self, parent,-1)\r
-\r
-               self.alterationFileList = ['start.gcode', 'end.gcode', 'nextobject.gcode', 'replace.csv']\r
-               if int(profile.getPreference('extruder_amount')) > 1:\r
-                       self.alterationFileList.append('switchExtruder.gcode')\r
-               self.currentFile = None\r
-\r
-               #self.textArea = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_PROCESS_TAB)\r
-               #self.textArea.SetFont(wx.Font(wx.SystemSettings.GetFont(wx.SYS_ANSI_VAR_FONT).GetPointSize(), wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))\r
-               self.textArea = gcodeTextArea.GcodeTextArea(self)\r
-               self.list = wx.ListBox(self, choices=self.alterationFileList, style=wx.LB_SINGLE)\r
-               self.list.SetSelection(0)\r
-               self.Bind(wx.EVT_LISTBOX, self.OnSelect, self.list)\r
-               self.textArea.Bind(wx.EVT_KILL_FOCUS, self.OnFocusLost, self.textArea)\r
-               self.textArea.Bind(wx.stc.EVT_STC_CHANGE, self.OnFocusLost, self.textArea)\r
-               \r
-               sizer = wx.GridBagSizer()\r
-               sizer.Add(self.list, (0,0), span=(1,1), flag=wx.EXPAND)\r
-               sizer.Add(self.textArea, (0,1), span=(1,1), flag=wx.EXPAND)\r
-               sizer.AddGrowableCol(1)\r
-               sizer.AddGrowableRow(0)\r
-               self.SetSizer(sizer)\r
-               \r
-               self.loadFile(self.alterationFileList[self.list.GetSelection()])\r
-               self.currentFile = self.list.GetSelection()\r
-\r
-       def OnSelect(self, e):\r
-               self.loadFile(self.alterationFileList[self.list.GetSelection()])\r
-               self.currentFile = self.list.GetSelection()\r
-\r
-       def loadFile(self, filename):\r
-               self.textArea.SetValue(profile.getAlterationFile(filename))\r
-\r
-       def OnFocusLost(self, e):\r
-               if self.currentFile == self.list.GetSelection():\r
-                       profile.setAlterationFile(self.alterationFileList[self.list.GetSelection()], self.textArea.GetValue())\r
-\r
-       def updateProfileToControls(self):\r
-               self.OnSelect(None)\r
-\r
+import wx,wx.stc
+import sys,math,threading,os
+
+from gui import gcodeTextArea
+from util import profile
+
+class alterationPanel(wx.Panel):
+       def __init__(self, parent):
+               wx.Panel.__init__(self, parent,-1)
+
+               self.alterationFileList = ['start.gcode', 'end.gcode', 'nextobject.gcode', 'replace.csv']
+               if int(profile.getPreference('extruder_amount')) > 1:
+                       self.alterationFileList.append('switchExtruder.gcode')
+               self.currentFile = None
+
+               #self.textArea = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_PROCESS_TAB)
+               #self.textArea.SetFont(wx.Font(wx.SystemSettings.GetFont(wx.SYS_ANSI_VAR_FONT).GetPointSize(), wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
+               self.textArea = gcodeTextArea.GcodeTextArea(self)
+               self.list = wx.ListBox(self, choices=self.alterationFileList, style=wx.LB_SINGLE)
+               self.list.SetSelection(0)
+               self.Bind(wx.EVT_LISTBOX, self.OnSelect, self.list)
+               self.textArea.Bind(wx.EVT_KILL_FOCUS, self.OnFocusLost, self.textArea)
+               self.textArea.Bind(wx.stc.EVT_STC_CHANGE, self.OnFocusLost, self.textArea)
+               
+               sizer = wx.GridBagSizer()
+               sizer.Add(self.list, (0,0), span=(1,1), flag=wx.EXPAND)
+               sizer.Add(self.textArea, (0,1), span=(1,1), flag=wx.EXPAND)
+               sizer.AddGrowableCol(1)
+               sizer.AddGrowableRow(0)
+               self.SetSizer(sizer)
+               
+               self.loadFile(self.alterationFileList[self.list.GetSelection()])
+               self.currentFile = self.list.GetSelection()
+
+       def OnSelect(self, e):
+               self.loadFile(self.alterationFileList[self.list.GetSelection()])
+               self.currentFile = self.list.GetSelection()
+
+       def loadFile(self, filename):
+               self.textArea.SetValue(profile.getAlterationFile(filename))
+
+       def OnFocusLost(self, e):
+               if self.currentFile == self.list.GetSelection():
+                       profile.setAlterationFile(self.alterationFileList[self.list.GetSelection()], self.textArea.GetValue())
+
+       def updateProfileToControls(self):
+               self.OnSelect(None)
+
index 54d734a2511ed808b074c54881385dda7c7f5e44..f782555c7f5098ad9f678f1e068badb690ab8edf 100644 (file)
-# coding=utf-8\r
-from __future__ import absolute_import\r
-\r
-import webbrowser\r
-import threading\r
-import time\r
-\r
-import wx\r
-import wx.wizard\r
-\r
-from gui import firmwareInstall\r
-from gui import toolbarUtil\r
-from gui import printWindow\r
-from util import machineCom\r
-from util import profile\r
-from util.resources import getPathForImage\r
-\r
-class InfoBox(wx.Panel):\r
-       def __init__(self, parent):\r
-               super(InfoBox, self).__init__(parent)\r
-               self.SetBackgroundColour('#FFFF80')\r
-\r
-               self.sizer = wx.GridBagSizer(5, 5)\r
-               self.SetSizer(self.sizer)\r
-\r
-               self.attentionBitmap = wx.Bitmap(getPathForImage('attention.png'))\r
-               self.errorBitmap = wx.Bitmap(getPathForImage('error.png'))\r
-               self.readyBitmap = wx.Bitmap(getPathForImage('ready.png'))\r
-               self.busyBitmap = [\r
-                       wx.Bitmap(getPathForImage('busy-0.png')),\r
-                       wx.Bitmap(getPathForImage('busy-1.png')),\r
-                       wx.Bitmap(getPathForImage('busy-2.png')),\r
-                       wx.Bitmap(getPathForImage('busy-3.png'))\r
-               ]\r
-\r
-               self.bitmap = wx.StaticBitmap(self, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))\r
-               self.text = wx.StaticText(self, -1, '')\r
-               self.extraInfoButton = wx.Button(self, -1, 'i', style=wx.BU_EXACTFIT)\r
-               self.sizer.Add(self.bitmap, pos=(0, 0), flag=wx.ALL, border=5)\r
-               self.sizer.Add(self.text, pos=(0, 1), flag=wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, border=5)\r
-               self.sizer.Add(self.extraInfoButton, pos=(0,2), flag=wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, border=5)\r
-               self.sizer.AddGrowableCol(1)\r
-\r
-               self.extraInfoButton.Show(False)\r
-\r
-               self.extraInfoUrl = ''\r
-               self.busyState = None\r
-               self.timer = wx.Timer(self)\r
-               self.Bind(wx.EVT_TIMER, self.doBusyUpdate, self.timer)\r
-               self.Bind(wx.EVT_BUTTON, self.doExtraInfo, self.extraInfoButton)\r
-               self.timer.Start(100)\r
-\r
-       def SetInfo(self, info):\r
-               self.SetBackgroundColour('#FFFF80')\r
-               self.text.SetLabel(info)\r
-               self.extraInfoButton.Show(False)\r
-               self.Refresh()\r
-\r
-       def SetError(self, info, extraInfoUrl):\r
-               self.extraInfoUrl = extraInfoUrl\r
-               self.SetBackgroundColour('#FF8080')\r
-               self.text.SetLabel(info)\r
-               self.extraInfoButton.Show(True)\r
-               self.Layout()\r
-               self.SetErrorIndicator()\r
-               self.Refresh()\r
-\r
-       def SetAttention(self, info):\r
-               self.SetBackgroundColour('#FFFF80')\r
-               self.text.SetLabel(info)\r
-               self.extraInfoButton.Show(False)\r
-               self.SetAttentionIndicator()\r
-               self.Refresh()\r
-\r
-       def SetBusyIndicator(self):\r
-               self.busyState = 0\r
-               self.bitmap.SetBitmap(self.busyBitmap[self.busyState])\r
-\r
-       def doExtraInfo(self, e):\r
-               webbrowser.open(self.extraInfoUrl)\r
-\r
-       def doBusyUpdate(self, e):\r
-               if self.busyState == None:\r
-                       return\r
-               self.busyState += 1\r
-               if self.busyState >= len(self.busyBitmap):\r
-                       self.busyState = 0\r
-               self.bitmap.SetBitmap(self.busyBitmap[self.busyState])\r
-\r
-       def SetReadyIndicator(self):\r
-               self.busyState = None\r
-               self.bitmap.SetBitmap(self.readyBitmap)\r
-\r
-       def SetErrorIndicator(self):\r
-               self.busyState = None\r
-               self.bitmap.SetBitmap(self.errorBitmap)\r
-\r
-       def SetAttentionIndicator(self):\r
-               self.busyState = None\r
-               self.bitmap.SetBitmap(self.attentionBitmap)\r
-\r
-\r
-class InfoPage(wx.wizard.WizardPageSimple):\r
-       def __init__(self, parent, title):\r
-               wx.wizard.WizardPageSimple.__init__(self, parent)\r
-\r
-               sizer = wx.GridBagSizer(5, 5)\r
-               self.sizer = sizer\r
-               self.SetSizer(sizer)\r
-\r
-               title = wx.StaticText(self, -1, title)\r
-               title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))\r
-               sizer.Add(title, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTRE | wx.ALL)\r
-               sizer.Add(wx.StaticLine(self, -1), pos=(1, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)\r
-               sizer.AddGrowableCol(1)\r
-\r
-               self.rowNr = 2\r
-\r
-       def AddText(self, info):\r
-               text = wx.StaticText(self, -1, info)\r
-               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)\r
-               self.rowNr += 1\r
-               return text\r
-\r
-       def AddSeperator(self):\r
-               self.GetSizer().Add(wx.StaticLine(self, -1), pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)\r
-               self.rowNr += 1\r
-\r
-       def AddHiddenSeperator(self):\r
-               self.AddText('')\r
-\r
-       def AddInfoBox(self):\r
-               infoBox = InfoBox(self)\r
-               self.GetSizer().Add(infoBox, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT | wx.EXPAND)\r
-               self.rowNr += 1\r
-               return infoBox\r
-\r
-       def AddRadioButton(self, label, style=0):\r
-               radio = wx.RadioButton(self, -1, label, style=style)\r
-               self.GetSizer().Add(radio, pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)\r
-               self.rowNr += 1\r
-               return radio\r
-\r
-       def AddCheckbox(self, label, checked=False):\r
-               check = wx.CheckBox(self, -1)\r
-               text = wx.StaticText(self, -1, label)\r
-               check.SetValue(checked)\r
-               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)\r
-               self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 2), flag=wx.ALL)\r
-               self.rowNr += 1\r
-               return check\r
-\r
-       def AddButton(self, label):\r
-               button = wx.Button(self, -1, label)\r
-               self.GetSizer().Add(button, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)\r
-               self.rowNr += 1\r
-               return button\r
-\r
-       def AddDualButton(self, label1, label2):\r
-               button1 = wx.Button(self, -1, label1)\r
-               self.GetSizer().Add(button1, pos=(self.rowNr, 0), flag=wx.RIGHT)\r
-               button2 = wx.Button(self, -1, label2)\r
-               self.GetSizer().Add(button2, pos=(self.rowNr, 1))\r
-               self.rowNr += 1\r
-               return button1, button2\r
-\r
-       def AddTextCtrl(self, value):\r
-               ret = wx.TextCtrl(self, -1, value)\r
-               self.GetSizer().Add(ret, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)\r
-               self.rowNr += 1\r
-               return ret\r
-\r
-       def AddLabelTextCtrl(self, info, value):\r
-               text = wx.StaticText(self, -1, info)\r
-               ret = wx.TextCtrl(self, -1, value)\r
-               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)\r
-               self.GetSizer().Add(ret, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)\r
-               self.rowNr += 1\r
-               return ret\r
-\r
-       def AddTextCtrlButton(self, value, buttonText):\r
-               text = wx.TextCtrl(self, -1, value)\r
-               button = wx.Button(self, -1, buttonText)\r
-               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)\r
-               self.GetSizer().Add(button, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)\r
-               self.rowNr += 1\r
-               return text, button\r
-\r
-       def AddBitmap(self, bitmap):\r
-               bitmap = wx.StaticBitmap(self, -1, bitmap)\r
-               self.GetSizer().Add(bitmap, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)\r
-               self.rowNr += 1\r
-               return bitmap\r
-\r
-       def AddCheckmark(self, label, bitmap):\r
-               check = wx.StaticBitmap(self, -1, bitmap)\r
-               text = wx.StaticText(self, -1, label)\r
-               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)\r
-               self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 1), flag=wx.ALL)\r
-               self.rowNr += 1\r
-               return check\r
-\r
-       def AllowNext(self):\r
-               return True\r
-\r
-       def StoreData(self):\r
-               pass\r
-\r
-\r
-class FirstInfoPage(InfoPage):\r
-       def __init__(self, parent):\r
-               super(FirstInfoPage, self).__init__(parent, "First time run wizard")\r
-               self.AddText('Welcome, and thanks for trying Cura!')\r
-               self.AddSeperator()\r
-               self.AddText('This wizard will help you with the following steps:')\r
-               self.AddText('* Configure Cura for your machine')\r
-               self.AddText('* Upgrade your firmware')\r
-               self.AddText('* Check if your machine is working safely')\r
-\r
-               #self.AddText('* Calibrate your machine')\r
-               #self.AddText('* Do your first print')\r
-\r
-\r
-class RepRapInfoPage(InfoPage):\r
-       def __init__(self, parent):\r
-               super(RepRapInfoPage, self).__init__(parent, "RepRap information")\r
-               self.AddText(\r
-                       'RepRap machines are vastly different, and there is no\ndefault configuration in Cura for any of them.')\r
-               self.AddText('If you like a default profile for your machine added,\nthen make an issue on github.')\r
-               self.AddSeperator()\r
-               self.AddText('You will have to manually install Marlin or Sprinter firmware.')\r
-               self.AddSeperator()\r
-               self.machineWidth = self.AddLabelTextCtrl('Machine width (mm)', '80')\r
-               self.machineDepth = self.AddLabelTextCtrl('Machine depth (mm)', '80')\r
-               self.machineHeight = self.AddLabelTextCtrl('Machine height (mm)', '60')\r
-               self.nozzleSize = self.AddLabelTextCtrl('Nozzle size (mm)', '0.5')\r
-               self.heatedBed = self.AddCheckbox('Heated bed')\r
-\r
-       def StoreData(self):\r
-               profile.putPreference('machine_width', self.machineWidth.GetValue())\r
-               profile.putPreference('machine_depth', self.machineDepth.GetValue())\r
-               profile.putPreference('machine_height', self.machineHeight.GetValue())\r
-               profile.putProfileSetting('nozzle_size', self.nozzleSize.GetValue())\r
-               profile.putProfileSetting('wall_thickness', float(profile.getProfileSettingFloat('nozzle_size')) * 2)\r
-               profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))\r
-\r
-\r
-class MachineSelectPage(InfoPage):\r
-       def __init__(self, parent):\r
-               super(MachineSelectPage, self).__init__(parent, "Select your machine")\r
-               self.AddText('What kind of machine do you have:')\r
-\r
-               self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)\r
-               self.UltimakerRadio.SetValue(True)\r
-               self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)\r
-               self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")\r
-               self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)\r
-\r
-       def OnUltimakerSelect(self, e):\r
-               wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)\r
-\r
-       def OnOtherSelect(self, e):\r
-               wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)\r
-\r
-       def StoreData(self):\r
-               if self.UltimakerRadio.GetValue():\r
-                       profile.putPreference('machine_width', '205')\r
-                       profile.putPreference('machine_depth', '205')\r
-                       profile.putPreference('machine_height', '200')\r
-                       profile.putPreference('machine_type', 'ultimaker')\r
-                       profile.putProfileSetting('nozzle_size', '0.4')\r
-               else:\r
-                       profile.putPreference('machine_width', '80')\r
-                       profile.putPreference('machine_depth', '80')\r
-                       profile.putPreference('machine_height', '60')\r
-                       profile.putPreference('machine_type', 'reprap')\r
-                       profile.putPreference('startMode', 'Normal')\r
-                       profile.putProfileSetting('nozzle_size', '0.5')\r
-               profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)\r
-\r
-\r
-class SelectParts(InfoPage):\r
-       def __init__(self, parent):\r
-               super(SelectParts, self).__init__(parent, "Select upgraded parts you have")\r
-               self.AddText('To assist you in having better default settings for your Ultimaker\nCura would like to know which upgrades you have in your machine.')\r
-               self.AddSeperator()\r
-               self.springExtruder = self.AddCheckbox('Extruder drive upgrade')\r
-               self.heatedBed = self.AddCheckbox('Heated printer bed (self build)')\r
-               self.dualExtrusion = self.AddCheckbox('Dual extrusion (experimental)')\r
-               self.AddSeperator()\r
-               self.AddText('If you have an Ultimaker bought after october 2012 you will have the\nExtruder drive upgrade. If you do not have this upgrade,\nit is highly recommended to improve reliablity.')\r
-               self.AddText('This upgrade can be bought from the Ultimaker webshop shop\nor found on thingiverse as thing:26094')\r
-               self.springExtruder.SetValue(True)\r
-\r
-       def StoreData(self):\r
-               profile.putPreference('ultimaker_extruder_upgrade', str(self.springExtruder.GetValue()))\r
-               profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))\r
-               if self.dualExtrusion.GetValue():\r
-                       profile.putPreference('extruder_amount', '2')\r
-               if getPreference('ultimaker_extruder_upgrade') == 'True':\r
-                       putProfileSetting('retraction_enable', 'True')\r
-\r
-\r
-class FirmwareUpgradePage(InfoPage):\r
-       def __init__(self, parent):\r
-               super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")\r
-               self.AddText(\r
-                       'Firmware is the piece of software running directly on your 3D printer.\nThis firmware controls the step motors, regulates the temperature\nand ultimately makes your printer work.')\r
-               self.AddHiddenSeperator()\r
-               self.AddText(\r
-                       'The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')\r
-               self.AddHiddenSeperator()\r
-               self.AddText(\r
-                       'Cura requires these new features and thus\nyour firmware will most likely need to be upgraded.\nYou will get the chance to do so now.')\r
-               upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')\r
-               upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)\r
-               skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)\r
-               self.AddHiddenSeperator()\r
-               self.AddText('Do not upgrade to this firmware if:')\r
-               self.AddText('* You have an older machine based on ATMega1280')\r
-               self.AddText('* Have other changes in the firmware')\r
-               button = self.AddButton('Goto this page for a custom firmware')\r
-               button.Bind(wx.EVT_BUTTON, self.OnUrlClick)\r
-\r
-       def AllowNext(self):\r
-               return False\r
-\r
-       def OnUpgradeClick(self, e):\r
-               if firmwareInstall.InstallFirmware():\r
-                       self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()\r
-\r
-       def OnSkipClick(self, e):\r
-               self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()\r
-\r
-       def OnUrlClick(self, e):\r
-               webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')\r
-\r
-\r
-class UltimakerCheckupPage(InfoPage):\r
-       def __init__(self, parent):\r
-               super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")\r
-\r
-               self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))\r
-               self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))\r
-               self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))\r
-               self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))\r
-               self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))\r
-               self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))\r
-               self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))\r
-               self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))\r
-               self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))\r
-               self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))\r
-\r
-               self.AddText(\r
-                       'It is a good idea to do a few sanity checks now on your Ultimaker.\nYou can skip these if you know your machine is functional.')\r
-               b1, b2 = self.AddDualButton('Run checks', 'Skip checks')\r
-               b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)\r
-               b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)\r
-               self.AddSeperator()\r
-               self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)\r
-               self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)\r
-               self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)\r
-               self.AddSeperator()\r
-               self.infoBox = self.AddInfoBox()\r
-               self.machineState = self.AddText('')\r
-               self.temperatureLabel = self.AddText('')\r
-               self.errorLogButton = self.AddButton('Show error log')\r
-               self.errorLogButton.Show(False)\r
-               self.AddSeperator()\r
-               self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)\r
-               self.comm = None\r
-               self.xMinStop = False\r
-               self.xMaxStop = False\r
-               self.yMinStop = False\r
-               self.yMaxStop = False\r
-               self.zMinStop = False\r
-               self.zMaxStop = False\r
-\r
-               self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)\r
-\r
-       def __del__(self):\r
-               if self.comm != None:\r
-                       self.comm.close()\r
-\r
-       def AllowNext(self):\r
-               self.endstopBitmap.Show(False)\r
-               return False\r
-\r
-       def OnSkipClick(self, e):\r
-               self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()\r
-\r
-       def OnCheckClick(self, e=None):\r
-               self.errorLogButton.Show(False)\r
-               if self.comm != None:\r
-                       self.comm.close()\r
-                       del self.comm\r
-                       self.comm = None\r
-                       wx.CallAfter(self.OnCheckClick)\r
-                       return\r
-               self.infoBox.SetInfo('Connecting to machine.')\r
-               self.infoBox.SetBusyIndicator()\r
-               self.commState.SetBitmap(self.unknownBitmap)\r
-               self.tempState.SetBitmap(self.unknownBitmap)\r
-               self.stopState.SetBitmap(self.unknownBitmap)\r
-               self.checkupState = 0\r
-               self.comm = machineCom.MachineCom(callbackObject=self)\r
-\r
-       def OnErrorLog(self, e):\r
-               printWindow.LogWindow('\n'.join(self.comm.getLog()))\r
-\r
-       def mcLog(self, message):\r
-               pass\r
-\r
-       def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):\r
-               if not self.comm.isOperational():\r
-                       return\r
-               if self.checkupState == 0:\r
-                       self.tempCheckTimeout = 20\r
-                       if temp > 70:\r
-                               self.checkupState = 1\r
-                               wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')\r
-                               self.comm.sendCommand('M104 S0')\r
-                               self.comm.sendCommand('M104 S0')\r
-                       else:\r
-                               self.startTemp = temp\r
-                               self.checkupState = 2\r
-                               wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')\r
-                               self.comm.sendCommand('M104 S200')\r
-                               self.comm.sendCommand('M104 S200')\r
-               elif self.checkupState == 1:\r
-                       if temp < 60:\r
-                               self.startTemp = temp\r
-                               self.checkupState = 2\r
-                               wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')\r
-                               self.comm.sendCommand('M104 S200')\r
-                               self.comm.sendCommand('M104 S200')\r
-               elif self.checkupState == 2:\r
-                       #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"\r
-                       if temp > self.startTemp + 40:\r
-                               self.checkupState = 3\r
-                               wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')\r
-                               wx.CallAfter(self.endstopBitmap.Show, True)\r
-                               wx.CallAfter(self.Layout)\r
-                               self.comm.sendCommand('M104 S0')\r
-                               self.comm.sendCommand('M104 S0')\r
-                               self.comm.sendCommand('M119')\r
-                               wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)\r
-                       else:\r
-                               self.tempCheckTimeout -= 1\r
-                               if self.tempCheckTimeout < 1:\r
-                                       self.checkupState = -1\r
-                                       wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)\r
-                                       wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura/Temperature_measurement_problems')\r
-                                       self.comm.sendCommand('M104 S0')\r
-                                       self.comm.sendCommand('M104 S0')\r
-               wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp))\r
-\r
-       def mcStateChange(self, state):\r
-               if self.comm == None:\r
-                       return\r
-               if self.comm.isOperational():\r
-                       wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)\r
-                       wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))\r
-               elif self.comm.isError():\r
-                       wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)\r
-                       wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura/Connection_problems')\r
-                       wx.CallAfter(self.endstopBitmap.Show, False)\r
-                       wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))\r
-                       wx.CallAfter(self.errorLogButton.Show, True)\r
-                       wx.CallAfter(self.Layout)\r
-               else:\r
-                       wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))\r
-\r
-       def mcMessage(self, message):\r
-               if self.checkupState >= 3 and self.checkupState < 10 and 'x_min' in message:\r
-                       for data in message.split(' '):\r
-                               if ':' in data:\r
-                                       tag, value = data.split(':', 2)\r
-                                       if tag == 'x_min':\r
-                                               self.xMinStop = (value == 'H')\r
-                                       if tag == 'x_max':\r
-                                               self.xMaxStop = (value == 'H')\r
-                                       if tag == 'y_min':\r
-                                               self.yMinStop = (value == 'H')\r
-                                       if tag == 'y_max':\r
-                                               self.yMaxStop = (value == 'H')\r
-                                       if tag == 'z_min':\r
-                                               self.zMinStop = (value == 'H')\r
-                                       if tag == 'z_max':\r
-                                               self.zMaxStop = (value == 'H')\r
-                       self.comm.sendCommand('M119')\r
-\r
-                       if self.checkupState == 3:\r
-                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:\r
-                                       self.checkupState = 4\r
-                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')\r
-                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)\r
-                       elif self.checkupState == 4:\r
-                               if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:\r
-                                       self.checkupState = 5\r
-                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')\r
-                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)\r
-                       elif self.checkupState == 5:\r
-                               if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:\r
-                                       self.checkupState = 6\r
-                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')\r
-                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)\r
-                       elif self.checkupState == 6:\r
-                               if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:\r
-                                       self.checkupState = 7\r
-                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')\r
-                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)\r
-                       elif self.checkupState == 7:\r
-                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:\r
-                                       self.checkupState = 8\r
-                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')\r
-                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)\r
-                       elif self.checkupState == 8:\r
-                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:\r
-                                       self.checkupState = 9\r
-                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')\r
-                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)\r
-                       elif self.checkupState == 9:\r
-                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:\r
-                                       self.checkupState = 10\r
-                                       self.comm.close()\r
-                                       wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')\r
-                                       wx.CallAfter(self.infoBox.SetReadyIndicator)\r
-                                       wx.CallAfter(self.endstopBitmap.Show, False)\r
-                                       wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)\r
-                                       wx.CallAfter(self.OnSkipClick, None)\r
-\r
-       def mcProgress(self, lineNr):\r
-               pass\r
-\r
-       def mcZChange(self, newZ):\r
-               pass\r
-\r
-\r
-class UltimakerCalibrationPage(InfoPage):\r
-       def __init__(self, parent):\r
-               super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")\r
-\r
-               self.AddText("Your Ultimaker requires some calibration.")\r
-               self.AddText("This calibration is needed for a proper extrusion amount.")\r
-               self.AddSeperator()\r
-               self.AddText("The following values are needed:")\r
-               self.AddText("* Diameter of filament")\r
-               self.AddText("* Number of steps per mm of filament extrusion")\r
-               self.AddSeperator()\r
-               self.AddText("The better you have calibrated these values, the better your prints\nwill become.")\r
-               self.AddSeperator()\r
-               self.AddText("First we need the diameter of your filament:")\r
-               self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))\r
-               self.AddText(\r
-                       "If you do not own digital Calipers that can measure\nat least 2 digits then use 2.89mm.\nWhich is the average diameter of most filament.")\r
-               self.AddText("Note: This value can be changed later at any time.")\r
-\r
-       def StoreData(self):\r
-               profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())\r
-\r
-\r
-class UltimakerCalibrateStepsPerEPage(InfoPage):\r
-       def __init__(self, parent):\r
-               super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")\r
-\r
-               if profile.getPreference('steps_per_e') == '0':\r
-                       profile.putPreference('steps_per_e', '865.888')\r
-\r
-               self.AddText("Calibrating the Steps Per E requires some manual actions.")\r
-               self.AddText("First remove any filament from your machine.")\r
-               self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")\r
-               self.AddText("We'll push the filament 100mm")\r
-               self.extrudeButton = self.AddButton("Extrude 100mm filament")\r
-               self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")\r
-               self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')\r
-               self.AddText("This results in the following steps per E:")\r
-               self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))\r
-               self.AddText("You can repeat these steps to get better calibration.")\r
-               self.AddSeperator()\r
-               self.AddText(\r
-                       "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")\r
-               self.heatButton = self.AddButton("Heatup for filament removal")\r
-\r
-               self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)\r
-               self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)\r
-               self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)\r
-\r
-       def OnSaveLengthClick(self, e):\r
-               currentEValue = float(self.stepsPerEInput.GetValue())\r
-               realExtrudeLength = float(self.lengthInput.GetValue())\r
-               newEValue = currentEValue * 100 / realExtrudeLength\r
-               self.stepsPerEInput.SetValue(str(newEValue))\r
-               self.lengthInput.SetValue("100")\r
-\r
-       def OnExtrudeClick(self, e):\r
-               threading.Thread(target=self.OnExtrudeRun).start()\r
-\r
-       def OnExtrudeRun(self):\r
-               self.heatButton.Enable(False)\r
-               self.extrudeButton.Enable(False)\r
-               currentEValue = float(self.stepsPerEInput.GetValue())\r
-               self.comm = machineCom.MachineCom()\r
-               if not self.comm.isOpen():\r
-                       wx.MessageBox(\r
-                               "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",\r
-                               'Printer error', wx.OK | wx.ICON_INFORMATION)\r
-                       self.heatButton.Enable(True)\r
-                       self.extrudeButton.Enable(True)\r
-                       return\r
-               while True:\r
-                       line = self.comm.readline()\r
-                       if line == '':\r
-                               return\r
-                       if 'start' in line:\r
-                               break\r
-                       #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.\r
-               time.sleep(3)\r
-\r
-               self.sendGCommand('M302') #Disable cold extrusion protection\r
-               self.sendGCommand("M92 E%f" % (currentEValue))\r
-               self.sendGCommand("G92 E0")\r
-               self.sendGCommand("G1 E100 F600")\r
-               time.sleep(15)\r
-               self.comm.close()\r
-               self.extrudeButton.Enable()\r
-               self.heatButton.Enable()\r
-\r
-       def OnHeatClick(self, e):\r
-               threading.Thread(target=self.OnHeatRun).start()\r
-\r
-       def OnHeatRun(self):\r
-               self.heatButton.Enable(False)\r
-               self.extrudeButton.Enable(False)\r
-               self.comm = machineCom.MachineCom()\r
-               if not self.comm.isOpen():\r
-                       wx.MessageBox(\r
-                               "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",\r
-                               'Printer error', wx.OK | wx.ICON_INFORMATION)\r
-                       self.heatButton.Enable(True)\r
-                       self.extrudeButton.Enable(True)\r
-                       return\r
-               while True:\r
-                       line = self.comm.readline()\r
-                       if line == '':\r
-                               self.heatButton.Enable(True)\r
-                               self.extrudeButton.Enable(True)\r
-                               return\r
-                       if 'start' in line:\r
-                               break\r
-                       #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.\r
-               time.sleep(3)\r
-\r
-               self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.\r
-               wx.MessageBox(\r
-                       'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',\r
-                       'Machine heatup', wx.OK | wx.ICON_INFORMATION)\r
-               self.sendGCommand('M104 S0')\r
-               time.sleep(1)\r
-               self.comm.close()\r
-               self.heatButton.Enable(True)\r
-               self.extrudeButton.Enable(True)\r
-\r
-       def sendGCommand(self, cmd):\r
-               self.comm.sendCommand(cmd) #Disable cold extrusion protection\r
-               while True:\r
-                       line = self.comm.readline()\r
-                       if line == '':\r
-                               return\r
-                       if line.startswith('ok'):\r
-                               break\r
-\r
-       def StoreData(self):\r
-               profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())\r
-\r
-\r
-class configWizard(wx.wizard.Wizard):\r
-       def __init__(self):\r
-               super(configWizard, self).__init__(None, -1, "Configuration Wizard")\r
-\r
-               self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)\r
-               self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)\r
-\r
-               self.firstInfoPage = FirstInfoPage(self)\r
-               self.machineSelectPage = MachineSelectPage(self)\r
-               self.ultimakerSelectParts = SelectParts(self)\r
-               self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)\r
-               self.ultimakerCheckupPage = UltimakerCheckupPage(self)\r
-               self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)\r
-               self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)\r
-               self.repRapInfoPage = RepRapInfoPage(self)\r
-\r
-               wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)\r
-               wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)\r
-               wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)\r
-               wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)\r
-               #wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.ultimakerCalibrationPage)\r
-               #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)\r
-\r
-               self.FitToPage(self.firstInfoPage)\r
-               self.GetPageAreaSizer().Add(self.firstInfoPage)\r
-\r
-               self.RunWizard(self.firstInfoPage)\r
-               self.Destroy()\r
-\r
-       def OnPageChanging(self, e):\r
-               e.GetPage().StoreData()\r
-\r
-       def OnPageChanged(self, e):\r
-               if e.GetPage().AllowNext():\r
-                       self.FindWindowById(wx.ID_FORWARD).Enable()\r
-               else:\r
-                       self.FindWindowById(wx.ID_FORWARD).Disable()\r
-               self.FindWindowById(wx.ID_BACKWARD).Disable()\r
+# coding=utf-8
+from __future__ import absolute_import
+
+import webbrowser
+import threading
+import time
+
+import wx
+import wx.wizard
+
+from gui import firmwareInstall
+from gui import toolbarUtil
+from gui import printWindow
+from util import machineCom
+from util import profile
+from util.resources import getPathForImage
+
+class InfoBox(wx.Panel):
+       def __init__(self, parent):
+               super(InfoBox, self).__init__(parent)
+               self.SetBackgroundColour('#FFFF80')
+
+               self.sizer = wx.GridBagSizer(5, 5)
+               self.SetSizer(self.sizer)
+
+               self.attentionBitmap = wx.Bitmap(getPathForImage('attention.png'))
+               self.errorBitmap = wx.Bitmap(getPathForImage('error.png'))
+               self.readyBitmap = wx.Bitmap(getPathForImage('ready.png'))
+               self.busyBitmap = [
+                       wx.Bitmap(getPathForImage('busy-0.png')),
+                       wx.Bitmap(getPathForImage('busy-1.png')),
+                       wx.Bitmap(getPathForImage('busy-2.png')),
+                       wx.Bitmap(getPathForImage('busy-3.png'))
+               ]
+
+               self.bitmap = wx.StaticBitmap(self, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
+               self.text = wx.StaticText(self, -1, '')
+               self.extraInfoButton = wx.Button(self, -1, 'i', style=wx.BU_EXACTFIT)
+               self.sizer.Add(self.bitmap, pos=(0, 0), flag=wx.ALL, border=5)
+               self.sizer.Add(self.text, pos=(0, 1), flag=wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, border=5)
+               self.sizer.Add(self.extraInfoButton, pos=(0,2), flag=wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, border=5)
+               self.sizer.AddGrowableCol(1)
+
+               self.extraInfoButton.Show(False)
+
+               self.extraInfoUrl = ''
+               self.busyState = None
+               self.timer = wx.Timer(self)
+               self.Bind(wx.EVT_TIMER, self.doBusyUpdate, self.timer)
+               self.Bind(wx.EVT_BUTTON, self.doExtraInfo, self.extraInfoButton)
+               self.timer.Start(100)
+
+       def SetInfo(self, info):
+               self.SetBackgroundColour('#FFFF80')
+               self.text.SetLabel(info)
+               self.extraInfoButton.Show(False)
+               self.Refresh()
+
+       def SetError(self, info, extraInfoUrl):
+               self.extraInfoUrl = extraInfoUrl
+               self.SetBackgroundColour('#FF8080')
+               self.text.SetLabel(info)
+               self.extraInfoButton.Show(True)
+               self.Layout()
+               self.SetErrorIndicator()
+               self.Refresh()
+
+       def SetAttention(self, info):
+               self.SetBackgroundColour('#FFFF80')
+               self.text.SetLabel(info)
+               self.extraInfoButton.Show(False)
+               self.SetAttentionIndicator()
+               self.Refresh()
+
+       def SetBusyIndicator(self):
+               self.busyState = 0
+               self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
+
+       def doExtraInfo(self, e):
+               webbrowser.open(self.extraInfoUrl)
+
+       def doBusyUpdate(self, e):
+               if self.busyState == None:
+                       return
+               self.busyState += 1
+               if self.busyState >= len(self.busyBitmap):
+                       self.busyState = 0
+               self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
+
+       def SetReadyIndicator(self):
+               self.busyState = None
+               self.bitmap.SetBitmap(self.readyBitmap)
+
+       def SetErrorIndicator(self):
+               self.busyState = None
+               self.bitmap.SetBitmap(self.errorBitmap)
+
+       def SetAttentionIndicator(self):
+               self.busyState = None
+               self.bitmap.SetBitmap(self.attentionBitmap)
+
+
+class InfoPage(wx.wizard.WizardPageSimple):
+       def __init__(self, parent, title):
+               wx.wizard.WizardPageSimple.__init__(self, parent)
+
+               sizer = wx.GridBagSizer(5, 5)
+               self.sizer = sizer
+               self.SetSizer(sizer)
+
+               title = wx.StaticText(self, -1, title)
+               title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
+               sizer.Add(title, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTRE | wx.ALL)
+               sizer.Add(wx.StaticLine(self, -1), pos=(1, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
+               sizer.AddGrowableCol(1)
+
+               self.rowNr = 2
+
+       def AddText(self, info):
+               text = wx.StaticText(self, -1, info)
+               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
+               self.rowNr += 1
+               return text
+
+       def AddSeperator(self):
+               self.GetSizer().Add(wx.StaticLine(self, -1), pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
+               self.rowNr += 1
+
+       def AddHiddenSeperator(self):
+               self.AddText('')
+
+       def AddInfoBox(self):
+               infoBox = InfoBox(self)
+               self.GetSizer().Add(infoBox, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT | wx.EXPAND)
+               self.rowNr += 1
+               return infoBox
+
+       def AddRadioButton(self, label, style=0):
+               radio = wx.RadioButton(self, -1, label, style=style)
+               self.GetSizer().Add(radio, pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
+               self.rowNr += 1
+               return radio
+
+       def AddCheckbox(self, label, checked=False):
+               check = wx.CheckBox(self, -1)
+               text = wx.StaticText(self, -1, label)
+               check.SetValue(checked)
+               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
+               self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 2), flag=wx.ALL)
+               self.rowNr += 1
+               return check
+
+       def AddButton(self, label):
+               button = wx.Button(self, -1, label)
+               self.GetSizer().Add(button, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
+               self.rowNr += 1
+               return button
+
+       def AddDualButton(self, label1, label2):
+               button1 = wx.Button(self, -1, label1)
+               self.GetSizer().Add(button1, pos=(self.rowNr, 0), flag=wx.RIGHT)
+               button2 = wx.Button(self, -1, label2)
+               self.GetSizer().Add(button2, pos=(self.rowNr, 1))
+               self.rowNr += 1
+               return button1, button2
+
+       def AddTextCtrl(self, value):
+               ret = wx.TextCtrl(self, -1, value)
+               self.GetSizer().Add(ret, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
+               self.rowNr += 1
+               return ret
+
+       def AddLabelTextCtrl(self, info, value):
+               text = wx.StaticText(self, -1, info)
+               ret = wx.TextCtrl(self, -1, value)
+               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
+               self.GetSizer().Add(ret, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
+               self.rowNr += 1
+               return ret
+
+       def AddTextCtrlButton(self, value, buttonText):
+               text = wx.TextCtrl(self, -1, value)
+               button = wx.Button(self, -1, buttonText)
+               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
+               self.GetSizer().Add(button, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
+               self.rowNr += 1
+               return text, button
+
+       def AddBitmap(self, bitmap):
+               bitmap = wx.StaticBitmap(self, -1, bitmap)
+               self.GetSizer().Add(bitmap, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
+               self.rowNr += 1
+               return bitmap
+
+       def AddCheckmark(self, label, bitmap):
+               check = wx.StaticBitmap(self, -1, bitmap)
+               text = wx.StaticText(self, -1, label)
+               self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
+               self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 1), flag=wx.ALL)
+               self.rowNr += 1
+               return check
+
+       def AllowNext(self):
+               return True
+
+       def StoreData(self):
+               pass
+
+
+class FirstInfoPage(InfoPage):
+       def __init__(self, parent):
+               super(FirstInfoPage, self).__init__(parent, "First time run wizard")
+               self.AddText('Welcome, and thanks for trying Cura!')
+               self.AddSeperator()
+               self.AddText('This wizard will help you with the following steps:')
+               self.AddText('* Configure Cura for your machine')
+               self.AddText('* Upgrade your firmware')
+               self.AddText('* Check if your machine is working safely')
+
+               #self.AddText('* Calibrate your machine')
+               #self.AddText('* Do your first print')
+
+
+class RepRapInfoPage(InfoPage):
+       def __init__(self, parent):
+               super(RepRapInfoPage, self).__init__(parent, "RepRap information")
+               self.AddText(
+                       'RepRap machines are vastly different, and there is no\ndefault configuration in Cura for any of them.')
+               self.AddText('If you like a default profile for your machine added,\nthen make an issue on github.')
+               self.AddSeperator()
+               self.AddText('You will have to manually install Marlin or Sprinter firmware.')
+               self.AddSeperator()
+               self.machineWidth = self.AddLabelTextCtrl('Machine width (mm)', '80')
+               self.machineDepth = self.AddLabelTextCtrl('Machine depth (mm)', '80')
+               self.machineHeight = self.AddLabelTextCtrl('Machine height (mm)', '60')
+               self.nozzleSize = self.AddLabelTextCtrl('Nozzle size (mm)', '0.5')
+               self.heatedBed = self.AddCheckbox('Heated bed')
+
+       def StoreData(self):
+               profile.putPreference('machine_width', self.machineWidth.GetValue())
+               profile.putPreference('machine_depth', self.machineDepth.GetValue())
+               profile.putPreference('machine_height', self.machineHeight.GetValue())
+               profile.putProfileSetting('nozzle_size', self.nozzleSize.GetValue())
+               profile.putProfileSetting('wall_thickness', float(profile.getProfileSettingFloat('nozzle_size')) * 2)
+               profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
+
+
+class MachineSelectPage(InfoPage):
+       def __init__(self, parent):
+               super(MachineSelectPage, self).__init__(parent, "Select your machine")
+               self.AddText('What kind of machine do you have:')
+
+               self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
+               self.UltimakerRadio.SetValue(True)
+               self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
+               self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
+               self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
+
+       def OnUltimakerSelect(self, e):
+               wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
+
+       def OnOtherSelect(self, e):
+               wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
+
+       def StoreData(self):
+               if self.UltimakerRadio.GetValue():
+                       profile.putPreference('machine_width', '205')
+                       profile.putPreference('machine_depth', '205')
+                       profile.putPreference('machine_height', '200')
+                       profile.putPreference('machine_type', 'ultimaker')
+                       profile.putProfileSetting('nozzle_size', '0.4')
+               else:
+                       profile.putPreference('machine_width', '80')
+                       profile.putPreference('machine_depth', '80')
+                       profile.putPreference('machine_height', '60')
+                       profile.putPreference('machine_type', 'reprap')
+                       profile.putPreference('startMode', 'Normal')
+                       profile.putProfileSetting('nozzle_size', '0.5')
+               profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
+
+
+class SelectParts(InfoPage):
+       def __init__(self, parent):
+               super(SelectParts, self).__init__(parent, "Select upgraded parts you have")
+               self.AddText('To assist you in having better default settings for your Ultimaker\nCura would like to know which upgrades you have in your machine.')
+               self.AddSeperator()
+               self.springExtruder = self.AddCheckbox('Extruder drive upgrade')
+               self.heatedBed = self.AddCheckbox('Heated printer bed (self build)')
+               self.dualExtrusion = self.AddCheckbox('Dual extrusion (experimental)')
+               self.AddSeperator()
+               self.AddText('If you have an Ultimaker bought after october 2012 you will have the\nExtruder drive upgrade. If you do not have this upgrade,\nit is highly recommended to improve reliablity.')
+               self.AddText('This upgrade can be bought from the Ultimaker webshop shop\nor found on thingiverse as thing:26094')
+               self.springExtruder.SetValue(True)
+
+       def StoreData(self):
+               profile.putPreference('ultimaker_extruder_upgrade', str(self.springExtruder.GetValue()))
+               profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
+               if self.dualExtrusion.GetValue():
+                       profile.putPreference('extruder_amount', '2')
+               if getPreference('ultimaker_extruder_upgrade') == 'True':
+                       putProfileSetting('retraction_enable', 'True')
+
+
+class FirmwareUpgradePage(InfoPage):
+       def __init__(self, parent):
+               super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
+               self.AddText(
+                       'Firmware is the piece of software running directly on your 3D printer.\nThis firmware controls the step motors, regulates the temperature\nand ultimately makes your printer work.')
+               self.AddHiddenSeperator()
+               self.AddText(
+                       'The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
+               self.AddHiddenSeperator()
+               self.AddText(
+                       'Cura requires these new features and thus\nyour firmware will most likely need to be upgraded.\nYou will get the chance to do so now.')
+               upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
+               upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
+               skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
+               self.AddHiddenSeperator()
+               self.AddText('Do not upgrade to this firmware if:')
+               self.AddText('* You have an older machine based on ATMega1280')
+               self.AddText('* Have other changes in the firmware')
+               button = self.AddButton('Goto this page for a custom firmware')
+               button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
+
+       def AllowNext(self):
+               return False
+
+       def OnUpgradeClick(self, e):
+               if firmwareInstall.InstallFirmware():
+                       self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
+
+       def OnSkipClick(self, e):
+               self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
+
+       def OnUrlClick(self, e):
+               webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
+
+
+class UltimakerCheckupPage(InfoPage):
+       def __init__(self, parent):
+               super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
+
+               self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))
+               self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))
+               self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))
+               self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))
+               self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))
+               self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))
+               self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))
+               self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))
+               self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))
+               self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))
+
+               self.AddText(
+                       'It is a good idea to do a few sanity checks now on your Ultimaker.\nYou can skip these if you know your machine is functional.')
+               b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
+               b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
+               b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
+               self.AddSeperator()
+               self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
+               self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
+               self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
+               self.AddSeperator()
+               self.infoBox = self.AddInfoBox()
+               self.machineState = self.AddText('')
+               self.temperatureLabel = self.AddText('')
+               self.errorLogButton = self.AddButton('Show error log')
+               self.errorLogButton.Show(False)
+               self.AddSeperator()
+               self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)
+               self.comm = None
+               self.xMinStop = False
+               self.xMaxStop = False
+               self.yMinStop = False
+               self.yMaxStop = False
+               self.zMinStop = False
+               self.zMaxStop = False
+
+               self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)
+
+       def __del__(self):
+               if self.comm != None:
+                       self.comm.close()
+
+       def AllowNext(self):
+               self.endstopBitmap.Show(False)
+               return False
+
+       def OnSkipClick(self, e):
+               self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
+
+       def OnCheckClick(self, e=None):
+               self.errorLogButton.Show(False)
+               if self.comm != None:
+                       self.comm.close()
+                       del self.comm
+                       self.comm = None
+                       wx.CallAfter(self.OnCheckClick)
+                       return
+               self.infoBox.SetInfo('Connecting to machine.')
+               self.infoBox.SetBusyIndicator()
+               self.commState.SetBitmap(self.unknownBitmap)
+               self.tempState.SetBitmap(self.unknownBitmap)
+               self.stopState.SetBitmap(self.unknownBitmap)
+               self.checkupState = 0
+               self.comm = machineCom.MachineCom(callbackObject=self)
+
+       def OnErrorLog(self, e):
+               printWindow.LogWindow('\n'.join(self.comm.getLog()))
+
+       def mcLog(self, message):
+               pass
+
+       def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
+               if not self.comm.isOperational():
+                       return
+               if self.checkupState == 0:
+                       self.tempCheckTimeout = 20
+                       if temp > 70:
+                               self.checkupState = 1
+                               wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
+                               self.comm.sendCommand('M104 S0')
+                               self.comm.sendCommand('M104 S0')
+                       else:
+                               self.startTemp = temp
+                               self.checkupState = 2
+                               wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
+                               self.comm.sendCommand('M104 S200')
+                               self.comm.sendCommand('M104 S200')
+               elif self.checkupState == 1:
+                       if temp < 60:
+                               self.startTemp = temp
+                               self.checkupState = 2
+                               wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
+                               self.comm.sendCommand('M104 S200')
+                               self.comm.sendCommand('M104 S200')
+               elif self.checkupState == 2:
+                       #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
+                       if temp > self.startTemp + 40:
+                               self.checkupState = 3
+                               wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
+                               wx.CallAfter(self.endstopBitmap.Show, True)
+                               wx.CallAfter(self.Layout)
+                               self.comm.sendCommand('M104 S0')
+                               self.comm.sendCommand('M104 S0')
+                               self.comm.sendCommand('M119')
+                               wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
+                       else:
+                               self.tempCheckTimeout -= 1
+                               if self.tempCheckTimeout < 1:
+                                       self.checkupState = -1
+                                       wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
+                                       wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura/Temperature_measurement_problems')
+                                       self.comm.sendCommand('M104 S0')
+                                       self.comm.sendCommand('M104 S0')
+               wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp))
+
+       def mcStateChange(self, state):
+               if self.comm == None:
+                       return
+               if self.comm.isOperational():
+                       wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
+                       wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
+               elif self.comm.isError():
+                       wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
+                       wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura/Connection_problems')
+                       wx.CallAfter(self.endstopBitmap.Show, False)
+                       wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
+                       wx.CallAfter(self.errorLogButton.Show, True)
+                       wx.CallAfter(self.Layout)
+               else:
+                       wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
+
+       def mcMessage(self, message):
+               if self.checkupState >= 3 and self.checkupState < 10 and 'x_min' in message:
+                       for data in message.split(' '):
+                               if ':' in data:
+                                       tag, value = data.split(':', 2)
+                                       if tag == 'x_min':
+                                               self.xMinStop = (value == 'H')
+                                       if tag == 'x_max':
+                                               self.xMaxStop = (value == 'H')
+                                       if tag == 'y_min':
+                                               self.yMinStop = (value == 'H')
+                                       if tag == 'y_max':
+                                               self.yMaxStop = (value == 'H')
+                                       if tag == 'z_min':
+                                               self.zMinStop = (value == 'H')
+                                       if tag == 'z_max':
+                                               self.zMaxStop = (value == 'H')
+                       self.comm.sendCommand('M119')
+
+                       if self.checkupState == 3:
+                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
+                                       self.checkupState = 4
+                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
+                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
+                       elif self.checkupState == 4:
+                               if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
+                                       self.checkupState = 5
+                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
+                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
+                       elif self.checkupState == 5:
+                               if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
+                                       self.checkupState = 6
+                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
+                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
+                       elif self.checkupState == 6:
+                               if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
+                                       self.checkupState = 7
+                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
+                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
+                       elif self.checkupState == 7:
+                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
+                                       self.checkupState = 8
+                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
+                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
+                       elif self.checkupState == 8:
+                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
+                                       self.checkupState = 9
+                                       wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
+                                       wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
+                       elif self.checkupState == 9:
+                               if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
+                                       self.checkupState = 10
+                                       self.comm.close()
+                                       wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
+                                       wx.CallAfter(self.infoBox.SetReadyIndicator)
+                                       wx.CallAfter(self.endstopBitmap.Show, False)
+                                       wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
+                                       wx.CallAfter(self.OnSkipClick, None)
+
+       def mcProgress(self, lineNr):
+               pass
+
+       def mcZChange(self, newZ):
+               pass
+
+
+class UltimakerCalibrationPage(InfoPage):
+       def __init__(self, parent):
+               super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
+
+               self.AddText("Your Ultimaker requires some calibration.")
+               self.AddText("This calibration is needed for a proper extrusion amount.")
+               self.AddSeperator()
+               self.AddText("The following values are needed:")
+               self.AddText("* Diameter of filament")
+               self.AddText("* Number of steps per mm of filament extrusion")
+               self.AddSeperator()
+               self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
+               self.AddSeperator()
+               self.AddText("First we need the diameter of your filament:")
+               self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
+               self.AddText(
+                       "If you do not own digital Calipers that can measure\nat least 2 digits then use 2.89mm.\nWhich is the average diameter of most filament.")
+               self.AddText("Note: This value can be changed later at any time.")
+
+       def StoreData(self):
+               profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
+
+
+class UltimakerCalibrateStepsPerEPage(InfoPage):
+       def __init__(self, parent):
+               super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
+
+               if profile.getPreference('steps_per_e') == '0':
+                       profile.putPreference('steps_per_e', '865.888')
+
+               self.AddText("Calibrating the Steps Per E requires some manual actions.")
+               self.AddText("First remove any filament from your machine.")
+               self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
+               self.AddText("We'll push the filament 100mm")
+               self.extrudeButton = self.AddButton("Extrude 100mm filament")
+               self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
+               self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
+               self.AddText("This results in the following steps per E:")
+               self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
+               self.AddText("You can repeat these steps to get better calibration.")
+               self.AddSeperator()
+               self.AddText(
+                       "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
+               self.heatButton = self.AddButton("Heatup for filament removal")
+
+               self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
+               self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
+               self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
+
+       def OnSaveLengthClick(self, e):
+               currentEValue = float(self.stepsPerEInput.GetValue())
+               realExtrudeLength = float(self.lengthInput.GetValue())
+               newEValue = currentEValue * 100 / realExtrudeLength
+               self.stepsPerEInput.SetValue(str(newEValue))
+               self.lengthInput.SetValue("100")
+
+       def OnExtrudeClick(self, e):
+               threading.Thread(target=self.OnExtrudeRun).start()
+
+       def OnExtrudeRun(self):
+               self.heatButton.Enable(False)
+               self.extrudeButton.Enable(False)
+               currentEValue = float(self.stepsPerEInput.GetValue())
+               self.comm = machineCom.MachineCom()
+               if not self.comm.isOpen():
+                       wx.MessageBox(
+                               "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
+                               'Printer error', wx.OK | wx.ICON_INFORMATION)
+                       self.heatButton.Enable(True)
+                       self.extrudeButton.Enable(True)
+                       return
+               while True:
+                       line = self.comm.readline()
+                       if line == '':
+                               return
+                       if 'start' in line:
+                               break
+                       #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
+               time.sleep(3)
+
+               self.sendGCommand('M302') #Disable cold extrusion protection
+               self.sendGCommand("M92 E%f" % (currentEValue))
+               self.sendGCommand("G92 E0")
+               self.sendGCommand("G1 E100 F600")
+               time.sleep(15)
+               self.comm.close()
+               self.extrudeButton.Enable()
+               self.heatButton.Enable()
+
+       def OnHeatClick(self, e):
+               threading.Thread(target=self.OnHeatRun).start()
+
+       def OnHeatRun(self):
+               self.heatButton.Enable(False)
+               self.extrudeButton.Enable(False)
+               self.comm = machineCom.MachineCom()
+               if not self.comm.isOpen():
+                       wx.MessageBox(
+                               "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
+                               'Printer error', wx.OK | wx.ICON_INFORMATION)
+                       self.heatButton.Enable(True)
+                       self.extrudeButton.Enable(True)
+                       return
+               while True:
+                       line = self.comm.readline()
+                       if line == '':
+                               self.heatButton.Enable(True)
+                               self.extrudeButton.Enable(True)
+                               return
+                       if 'start' in line:
+                               break
+                       #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
+               time.sleep(3)
+
+               self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
+               wx.MessageBox(
+                       'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
+                       'Machine heatup', wx.OK | wx.ICON_INFORMATION)
+               self.sendGCommand('M104 S0')
+               time.sleep(1)
+               self.comm.close()
+               self.heatButton.Enable(True)
+               self.extrudeButton.Enable(True)
+
+       def sendGCommand(self, cmd):
+               self.comm.sendCommand(cmd) #Disable cold extrusion protection
+               while True:
+                       line = self.comm.readline()
+                       if line == '':
+                               return
+                       if line.startswith('ok'):
+                               break
+
+       def StoreData(self):
+               profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
+
+
+class configWizard(wx.wizard.Wizard):
+       def __init__(self):
+               super(configWizard, self).__init__(None, -1, "Configuration Wizard")
+
+               self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
+               self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
+
+               self.firstInfoPage = FirstInfoPage(self)
+               self.machineSelectPage = MachineSelectPage(self)
+               self.ultimakerSelectParts = SelectParts(self)
+               self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
+               self.ultimakerCheckupPage = UltimakerCheckupPage(self)
+               self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
+               self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
+               self.repRapInfoPage = RepRapInfoPage(self)
+
+               wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
+               wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
+               wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
+               wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
+               #wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.ultimakerCalibrationPage)
+               #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
+
+               self.FitToPage(self.firstInfoPage)
+               self.GetPageAreaSizer().Add(self.firstInfoPage)
+
+               self.RunWizard(self.firstInfoPage)
+               self.Destroy()
+
+       def OnPageChanging(self, e):
+               e.GetPage().StoreData()
+
+       def OnPageChanged(self, e):
+               if e.GetPage().AllowNext():
+                       self.FindWindowById(wx.ID_FORWARD).Enable()
+               else:
+                       self.FindWindowById(wx.ID_FORWARD).Disable()
+               self.FindWindowById(wx.ID_BACKWARD).Disable()
index 8e327a7017f1b44b787ad16a31abe1f4955957f8..78bd8fd4aeeaf3e9de484f6f2932fcbce783b6a3 100644 (file)
-# coding=utf-8\r
-from __future__ import absolute_import\r
-\r
-import math\r
-\r
-from util import meshLoader\r
-from util import util3d\r
-from util import profile\r
-from util.resources import getPathForMesh\r
-\r
-try:\r
-       import OpenGL\r
-\r
-       OpenGL.ERROR_CHECKING = False\r
-       from OpenGL.GLU import *\r
-       from OpenGL.GL import *\r
-\r
-       hasOpenGLlibs = True\r
-except:\r
-       print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
-       hasOpenGLlibs = False\r
-\r
-def InitGL(window, view3D, zoom):\r
-       # set viewing projection\r
-       glMatrixMode(GL_MODELVIEW)\r
-       glLoadIdentity()\r
-       size = window.GetSize()\r
-       glViewport(0, 0, size.GetWidth(), size.GetHeight())\r
-\r
-       glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])\r
-       glLightfv(GL_LIGHT1, GL_POSITION, [1.0, 1.0, 1.0, 0.0])\r
-\r
-       glEnable(GL_RESCALE_NORMAL)\r
-       glEnable(GL_LIGHTING)\r
-       glEnable(GL_LIGHT0)\r
-       glEnable(GL_DEPTH_TEST)\r
-       glEnable(GL_CULL_FACE)\r
-       glDisable(GL_BLEND)\r
-\r
-       glClearColor(1.0, 1.0, 1.0, 1.0)\r
-       glClearStencil(0)\r
-       glClearDepth(1.0)\r
-\r
-       glMatrixMode(GL_PROJECTION)\r
-       glLoadIdentity()\r
-       aspect = float(size.GetWidth()) / float(size.GetHeight())\r
-       if view3D:\r
-               gluPerspective(45.0, aspect, 1.0, 1000.0)\r
-       else:\r
-               glOrtho(-aspect * (zoom), aspect * (zoom), -1.0 * (zoom), 1.0 * (zoom), -1000.0, 1000.0)\r
-\r
-       glMatrixMode(GL_MODELVIEW)\r
-       glLoadIdentity()\r
-       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)\r
-\r
-platformMesh = None\r
-\r
-def DrawMachine(machineSize):\r
-       if profile.getPreference('machine_type') == 'ultimaker':\r
-               glPushMatrix()\r
-               glEnable(GL_LIGHTING)\r
-               glTranslate(100, 200, -5)\r
-               glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.8, 0.8, 0.8])\r
-               glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5])\r
-               glEnable(GL_BLEND)\r
-               glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR)\r
-\r
-               global platformMesh\r
-               if platformMesh == None:\r
-                       platformMesh = meshLoader.loadMesh(getPathForMesh('ultimaker_platform.stl'))\r
-                       platformMesh.setRotateMirror(0, False, False, False, False, False)\r
-\r
-               DrawMesh(platformMesh)\r
-               glPopMatrix()\r
-\r
-       glDisable(GL_LIGHTING)\r
-       if False:\r
-               glColor3f(0.7, 0.7, 0.7)\r
-               glLineWidth(2)\r
-               glBegin(GL_LINES)\r
-               for i in xrange(0, int(machineSize.x), 10):\r
-                       glVertex3f(i, 0, 0)\r
-                       glVertex3f(i, machineSize.y, 0)\r
-               for i in xrange(0, int(machineSize.y), 10):\r
-                       glVertex3f(0, i, 0)\r
-                       glVertex3f(machineSize.x, i, 0)\r
-               glEnd()\r
-\r
-               glEnable(GL_LINE_SMOOTH)\r
-               glEnable(GL_BLEND)\r
-               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)\r
-               glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);\r
-\r
-               glColor3f(0.0, 0.0, 0.0)\r
-               glLineWidth(4)\r
-               glBegin(GL_LINE_LOOP)\r
-               glVertex3f(0, 0, 0)\r
-               glVertex3f(machineSize.x, 0, 0)\r
-               glVertex3f(machineSize.x, machineSize.y, 0)\r
-               glVertex3f(0, machineSize.y, 0)\r
-               glEnd()\r
-\r
-               glLineWidth(2)\r
-               glBegin(GL_LINE_LOOP)\r
-               glVertex3f(0, 0, machineSize.z)\r
-               glVertex3f(machineSize.x, 0, machineSize.z)\r
-               glVertex3f(machineSize.x, machineSize.y, machineSize.z)\r
-               glVertex3f(0, machineSize.y, machineSize.z)\r
-               glEnd()\r
-               glBegin(GL_LINES)\r
-               glVertex3f(0, 0, 0)\r
-               glVertex3f(0, 0, machineSize.z)\r
-               glVertex3f(machineSize.x, 0, 0)\r
-               glVertex3f(machineSize.x, 0, machineSize.z)\r
-               glVertex3f(machineSize.x, machineSize.y, 0)\r
-               glVertex3f(machineSize.x, machineSize.y, machineSize.z)\r
-               glVertex3f(0, machineSize.y, 0)\r
-               glVertex3f(0, machineSize.y, machineSize.z)\r
-               glEnd()\r
-       else:\r
-               glDisable(GL_CULL_FACE)\r
-               glEnable(GL_BLEND)\r
-               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)\r
-               glColor4ub(5, 171, 231, 127)\r
-               glBegin(GL_QUADS)\r
-               for x in xrange(0, int(machineSize.x), 20):\r
-                       for y in xrange(0, int(machineSize.y), 20):\r
-                               glVertex3f(x, y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)\r
-                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)\r
-               for x in xrange(10, int(machineSize.x), 20):\r
-                       for y in xrange(10, int(machineSize.y), 20):\r
-                               glVertex3f(x, y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)\r
-                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)\r
-               glEnd()\r
-               glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)\r
-               glBegin(GL_QUADS)\r
-               for x in xrange(10, int(machineSize.x), 20):\r
-                       for y in xrange(0, int(machineSize.y), 20):\r
-                               glVertex3f(x, y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)\r
-                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)\r
-               for x in xrange(0, int(machineSize.x), 20):\r
-                       for y in xrange(10, int(machineSize.y), 20):\r
-                               glVertex3f(x, y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)\r
-                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)\r
-                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)\r
-               glEnd()\r
-               glEnable(GL_CULL_FACE)\r
-\r
-               glColor4ub(5, 171, 231, 64)\r
-               glBegin(GL_QUADS)\r
-               glVertex3f(0, 0, machineSize.z)\r
-               glVertex3f(0, machineSize.y, machineSize.z)\r
-               glVertex3f(machineSize.x, machineSize.y, machineSize.z)\r
-               glVertex3f(machineSize.x, 0, machineSize.z)\r
-               glEnd()\r
-\r
-               glColor4ub(5, 171, 231, 96)\r
-               glBegin(GL_QUADS)\r
-               glVertex3f(0, 0, 0)\r
-               glVertex3f(0, 0, machineSize.z)\r
-               glVertex3f(machineSize.x, 0, machineSize.z)\r
-               glVertex3f(machineSize.x, 0, 0)\r
-\r
-               glVertex3f(0, machineSize.y, machineSize.z)\r
-               glVertex3f(0, machineSize.y, 0)\r
-               glVertex3f(machineSize.x, machineSize.y, 0)\r
-               glVertex3f(machineSize.x, machineSize.y, machineSize.z)\r
-               glEnd()\r
-\r
-               glColor4ub(5, 171, 231, 128)\r
-               glBegin(GL_QUADS)\r
-               glVertex3f(0, 0, machineSize.z)\r
-               glVertex3f(0, 0, 0)\r
-               glVertex3f(0, machineSize.y, 0)\r
-               glVertex3f(0, machineSize.y, machineSize.z)\r
-\r
-               glVertex3f(machineSize.x, 0, 0)\r
-               glVertex3f(machineSize.x, 0, machineSize.z)\r
-               glVertex3f(machineSize.x, machineSize.y, machineSize.z)\r
-               glVertex3f(machineSize.x, machineSize.y, 0)\r
-               glEnd()\r
-\r
-               glDisable(GL_BLEND)\r
-\r
-       glPushMatrix()\r
-       glTranslate(5, 5, 2)\r
-       glLineWidth(2)\r
-       glColor3f(0.5, 0, 0)\r
-       glBegin(GL_LINES)\r
-       glVertex3f(0, 0, 0)\r
-       glVertex3f(20, 0, 0)\r
-       glEnd()\r
-       glColor3f(0, 0.5, 0)\r
-       glBegin(GL_LINES)\r
-       glVertex3f(0, 0, 0)\r
-       glVertex3f(0, 20, 0)\r
-       glEnd()\r
-       glColor3f(0, 0, 0.5)\r
-       glBegin(GL_LINES)\r
-       glVertex3f(0, 0, 0)\r
-       glVertex3f(0, 0, 20)\r
-       glEnd()\r
-\r
-       glDisable(GL_DEPTH_TEST)\r
-       #X\r
-       glColor3f(1, 0, 0)\r
-       glPushMatrix()\r
-       glTranslate(23, 0, 0)\r
-       noZ = ResetMatrixRotationAndScale()\r
-       glBegin(GL_LINES)\r
-       glVertex3f(-0.8, 1, 0)\r
-       glVertex3f(0.8, -1, 0)\r
-       glVertex3f(0.8, 1, 0)\r
-       glVertex3f(-0.8, -1, 0)\r
-       glEnd()\r
-       glPopMatrix()\r
-\r
-       #Y\r
-       glColor3f(0, 1, 0)\r
-       glPushMatrix()\r
-       glTranslate(0, 23, 0)\r
-       ResetMatrixRotationAndScale()\r
-       glBegin(GL_LINES)\r
-       glVertex3f(-0.8, 1, 0)\r
-       glVertex3f(0.0, 0, 0)\r
-       glVertex3f(0.8, 1, 0)\r
-       glVertex3f(-0.8, -1, 0)\r
-       glEnd()\r
-       glPopMatrix()\r
-\r
-       #Z\r
-       if not noZ:\r
-               glColor3f(0, 0, 1)\r
-               glPushMatrix()\r
-               glTranslate(0, 0, 23)\r
-               ResetMatrixRotationAndScale()\r
-               glBegin(GL_LINES)\r
-               glVertex3f(-0.8, 1, 0)\r
-               glVertex3f(0.8, 1, 0)\r
-               glVertex3f(0.8, 1, 0)\r
-               glVertex3f(-0.8, -1, 0)\r
-               glVertex3f(-0.8, -1, 0)\r
-               glVertex3f(0.8, -1, 0)\r
-               glEnd()\r
-               glPopMatrix()\r
-\r
-       glPopMatrix()\r
-       glEnable(GL_DEPTH_TEST)\r
-\r
-\r
-def ResetMatrixRotationAndScale():\r
-       matrix = glGetFloatv(GL_MODELVIEW_MATRIX)\r
-       noZ = False\r
-       if matrix[3][2] > 0:\r
-               return False\r
-       scale2D = matrix[0][0]\r
-       matrix[0][0] = 1.0\r
-       matrix[1][0] = 0.0\r
-       matrix[2][0] = 0.0\r
-       matrix[0][1] = 0.0\r
-       matrix[1][1] = 1.0\r
-       matrix[2][1] = 0.0\r
-       matrix[0][2] = 0.0\r
-       matrix[1][2] = 0.0\r
-       matrix[2][2] = 1.0\r
-\r
-       if matrix[3][2] != 0.0:\r
-               matrix[3][0] = matrix[3][0] / (-matrix[3][2] / 100)\r
-               matrix[3][1] = matrix[3][1] / (-matrix[3][2] / 100)\r
-               matrix[3][2] = -100\r
-       else:\r
-               matrix[0][0] = scale2D\r
-               matrix[1][1] = scale2D\r
-               matrix[2][2] = scale2D\r
-               matrix[3][2] = -100\r
-               noZ = True\r
-\r
-       glLoadMatrixf(matrix)\r
-       return noZ\r
-\r
-\r
-def DrawBox(vMin, vMax):\r
-       glBegin(GL_LINE_LOOP)\r
-       glVertex3f(vMin[0], vMin[1], vMin[2])\r
-       glVertex3f(vMax[0], vMin[1], vMin[2])\r
-       glVertex3f(vMax[0], vMax[1], vMin[2])\r
-       glVertex3f(vMin[0], vMax[1], vMin[2])\r
-       glEnd()\r
-\r
-       glBegin(GL_LINE_LOOP)\r
-       glVertex3f(vMin[0], vMin[1], vMax[2])\r
-       glVertex3f(vMax[0], vMin[1], vMax[2])\r
-       glVertex3f(vMax[0], vMax[1], vMax[2])\r
-       glVertex3f(vMin[0], vMax[1], vMax[2])\r
-       glEnd()\r
-       glBegin(GL_LINES)\r
-       glVertex3f(vMin[0], vMin[1], vMin[2])\r
-       glVertex3f(vMin[0], vMin[1], vMax[2])\r
-       glVertex3f(vMax[0], vMin[1], vMin[2])\r
-       glVertex3f(vMax[0], vMin[1], vMax[2])\r
-       glVertex3f(vMax[0], vMax[1], vMin[2])\r
-       glVertex3f(vMax[0], vMax[1], vMax[2])\r
-       glVertex3f(vMin[0], vMax[1], vMin[2])\r
-       glVertex3f(vMin[0], vMax[1], vMax[2])\r
-       glEnd()\r
-\r
-\r
-def DrawMeshOutline(mesh):\r
-       glEnable(GL_CULL_FACE)\r
-       glEnableClientState(GL_VERTEX_ARRAY);\r
-       glVertexPointer(3, GL_FLOAT, 0, mesh.vertexes)\r
-\r
-       glCullFace(GL_FRONT)\r
-       glLineWidth(3)\r
-       glPolygonMode(GL_BACK, GL_LINE)\r
-       glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount)\r
-       glPolygonMode(GL_BACK, GL_FILL)\r
-       glCullFace(GL_BACK)\r
-\r
-       glDisableClientState(GL_VERTEX_ARRAY)\r
-\r
-\r
-def DrawMesh(mesh):\r
-       glEnable(GL_CULL_FACE)\r
-       glEnableClientState(GL_VERTEX_ARRAY);\r
-       glEnableClientState(GL_NORMAL_ARRAY);\r
-       glVertexPointer(3, GL_FLOAT, 0, mesh.vertexes)\r
-       glNormalPointer(GL_FLOAT, 0, mesh.normal)\r
-\r
-       #Odd, drawing in batchs is a LOT faster then drawing it all at once.\r
-       batchSize = 999    #Warning, batchSize needs to be dividable by 3\r
-       extraStartPos = int(mesh.vertexCount / batchSize) * batchSize\r
-       extraCount = mesh.vertexCount - extraStartPos\r
-\r
-       glCullFace(GL_BACK)\r
-       for i in xrange(0, int(mesh.vertexCount / batchSize)):\r
-               glDrawArrays(GL_TRIANGLES, i * batchSize, batchSize)\r
-       glDrawArrays(GL_TRIANGLES, extraStartPos, extraCount)\r
-\r
-       glCullFace(GL_FRONT)\r
-       glNormalPointer(GL_FLOAT, 0, mesh.invNormal)\r
-       for i in xrange(0, int(mesh.vertexCount / batchSize)):\r
-               glDrawArrays(GL_TRIANGLES, i * batchSize, batchSize)\r
-       extraStartPos = int(mesh.vertexCount / batchSize) * batchSize\r
-       extraCount = mesh.vertexCount - extraStartPos\r
-       glDrawArrays(GL_TRIANGLES, extraStartPos, extraCount)\r
-       glCullFace(GL_BACK)\r
-\r
-       glDisableClientState(GL_VERTEX_ARRAY)\r
-       glDisableClientState(GL_NORMAL_ARRAY);\r
-\r
-\r
-def DrawMeshSteep(mesh, angle):\r
-       cosAngle = math.sin(angle / 180.0 * math.pi)\r
-       glDisable(GL_LIGHTING)\r
-       glDepthFunc(GL_EQUAL)\r
-       for i in xrange(0, int(mesh.vertexCount), 3):\r
-               if mesh.normal[i][2] < -0.999999:\r
-                       if mesh.vertexes[i + 0][2] > 0.01:\r
-                               glColor3f(0.5, 0, 0)\r
-                               glBegin(GL_TRIANGLES)\r
-                               glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])\r
-                               glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])\r
-                               glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])\r
-                               glEnd()\r
-               elif mesh.normal[i][2] < -cosAngle:\r
-                       glColor3f(-mesh.normal[i][2], 0, 0)\r
-                       glBegin(GL_TRIANGLES)\r
-                       glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])\r
-                       glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])\r
-                       glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])\r
-                       glEnd()\r
-               elif mesh.normal[i][2] > 0.999999:\r
-                       if mesh.vertexes[i + 0][2] > 0.01:\r
-                               glColor3f(0.5, 0, 0)\r
-                               glBegin(GL_TRIANGLES)\r
-                               glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])\r
-                               glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])\r
-                               glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])\r
-                               glEnd()\r
-               elif mesh.normal[i][2] > cosAngle:\r
-                       glColor3f(mesh.normal[i][2], 0, 0)\r
-                       glBegin(GL_TRIANGLES)\r
-                       glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])\r
-                       glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])\r
-                       glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])\r
-                       glEnd()\r
-       glDepthFunc(GL_LESS)\r
-\r
-\r
-def DrawGCodeLayer(layer):\r
-       filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2\r
-       filamentArea = math.pi * filamentRadius * filamentRadius\r
-       lineWidth = profile.getProfileSettingFloat('nozzle_size') / 2 / 10\r
-\r
-       fillCycle = 0\r
-       fillColorCycle = [[0.5, 0.5, 0.0], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5]]\r
-       moveColor = [0, 0, 1]\r
-       retractColor = [1, 0, 0.5]\r
-       supportColor = [0, 1, 1]\r
-       extrudeColor = [1, 0, 0]\r
-       innerWallColor = [0, 1, 0]\r
-       skirtColor = [0, 0.5, 0.5]\r
-       prevPathWasRetract = False\r
-\r
-       glDisable(GL_CULL_FACE)\r
-       for path in layer:\r
-               if path.type == 'move':\r
-                       if prevPathWasRetract:\r
-                               c = retractColor\r
-                       else:\r
-                               c = moveColor\r
-               zOffset = 0.01\r
-               if path.type == 'extrude':\r
-                       if path.pathType == 'FILL':\r
-                               c = fillColorCycle[fillCycle]\r
-                               fillCycle = (fillCycle + 1) % len(fillColorCycle)\r
-                       elif path.pathType == 'WALL-INNER':\r
-                               c = innerWallColor\r
-                               zOffset = 0.02\r
-                       elif path.pathType == 'SUPPORT':\r
-                               c = supportColor\r
-                       elif path.pathType == 'SKIRT':\r
-                               c = skirtColor\r
-                       else:\r
-                               c = extrudeColor\r
-               if path.type == 'retract':\r
-                       c = [0, 1, 1]\r
-               if path.type == 'extrude':\r
-                       drawLength = 0.0\r
-                       prevNormal = None\r
-                       for i in xrange(0, len(path.list) - 1):\r
-                               v0 = path.list[i]\r
-                               v1 = path.list[i + 1]\r
-\r
-                               # Calculate line width from ePerDistance (needs layer thickness and filament diameter)\r
-                               dist = (v0 - v1).vsize()\r
-                               if dist > 0 and path.layerThickness > 0:\r
-                                       extrusionMMperDist = (v1.e - v0.e) / dist\r
-                                       lineWidth = extrusionMMperDist * filamentArea / path.layerThickness / 2 * v1.extrudeAmountMultiply\r
-\r
-                               drawLength += (v0 - v1).vsize()\r
-                               normal = (v0 - v1).cross(util3d.Vector3(0, 0, 1))\r
-                               normal.normalize()\r
-\r
-                               vv2 = v0 + normal * lineWidth\r
-                               vv3 = v1 + normal * lineWidth\r
-                               vv0 = v0 - normal * lineWidth\r
-                               vv1 = v1 - normal * lineWidth\r
-\r
-                               glBegin(GL_QUADS)\r
-                               glColor3fv(c)\r
-                               glVertex3f(vv0.x, vv0.y, vv0.z - zOffset)\r
-                               glVertex3f(vv1.x, vv1.y, vv1.z - zOffset)\r
-                               glVertex3f(vv3.x, vv3.y, vv3.z - zOffset)\r
-                               glVertex3f(vv2.x, vv2.y, vv2.z - zOffset)\r
-                               glEnd()\r
-                               if prevNormal != None:\r
-                                       n = (normal + prevNormal)\r
-                                       n.normalize()\r
-                                       vv4 = v0 + n * lineWidth\r
-                                       vv5 = v0 - n * lineWidth\r
-                                       glBegin(GL_QUADS)\r
-                                       glColor3fv(c)\r
-                                       glVertex3f(vv2.x, vv2.y, vv2.z - zOffset)\r
-                                       glVertex3f(vv4.x, vv4.y, vv4.z - zOffset)\r
-                                       glVertex3f(prevVv3.x, prevVv3.y, prevVv3.z - zOffset)\r
-                                       glVertex3f(v0.x, v0.y, v0.z - zOffset)\r
-\r
-                                       glVertex3f(vv0.x, vv0.y, vv0.z - zOffset)\r
-                                       glVertex3f(vv5.x, vv5.y, vv5.z - zOffset)\r
-                                       glVertex3f(prevVv1.x, prevVv1.y, prevVv1.z - zOffset)\r
-                                       glVertex3f(v0.x, v0.y, v0.z - zOffset)\r
-                                       glEnd()\r
-\r
-                               prevNormal = normal\r
-                               prevVv1 = vv1\r
-                               prevVv3 = vv3\r
-               else:\r
-                       glBegin(GL_LINE_STRIP)\r
-                       glColor3fv(c)\r
-                       for v in path.list:\r
-                               glVertex3f(v.x, v.y, v.z)\r
-                       glEnd()\r
-               if not path.type == 'move':\r
-                       prevPathWasRetract = False\r
-               if path.type == 'retract' and path.list[0].almostEqual(path.list[-1]):\r
-                       prevPathWasRetract = True\r
-       glEnable(GL_CULL_FACE)\r
+# coding=utf-8
+from __future__ import absolute_import
+
+import math
+
+from util import meshLoader
+from util import util3d
+from util import profile
+from util.resources import getPathForMesh
+
+try:
+       import OpenGL
+
+       OpenGL.ERROR_CHECKING = False
+       from OpenGL.GLU import *
+       from OpenGL.GL import *
+
+       hasOpenGLlibs = True
+except:
+       print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
+       hasOpenGLlibs = False
+
+def InitGL(window, view3D, zoom):
+       # set viewing projection
+       glMatrixMode(GL_MODELVIEW)
+       glLoadIdentity()
+       size = window.GetSize()
+       glViewport(0, 0, size.GetWidth(), size.GetHeight())
+
+       glLightfv(GL_LIGHT0, GL_POSITION, [0.2, 0.2, 1.0, 0.0])
+       glLightfv(GL_LIGHT1, GL_POSITION, [1.0, 1.0, 1.0, 0.0])
+
+       glEnable(GL_RESCALE_NORMAL)
+       glEnable(GL_LIGHTING)
+       glEnable(GL_LIGHT0)
+       glEnable(GL_DEPTH_TEST)
+       glEnable(GL_CULL_FACE)
+       glDisable(GL_BLEND)
+
+       glClearColor(1.0, 1.0, 1.0, 1.0)
+       glClearStencil(0)
+       glClearDepth(1.0)
+
+       glMatrixMode(GL_PROJECTION)
+       glLoadIdentity()
+       aspect = float(size.GetWidth()) / float(size.GetHeight())
+       if view3D:
+               gluPerspective(45.0, aspect, 1.0, 1000.0)
+       else:
+               glOrtho(-aspect * (zoom), aspect * (zoom), -1.0 * (zoom), 1.0 * (zoom), -1000.0, 1000.0)
+
+       glMatrixMode(GL_MODELVIEW)
+       glLoadIdentity()
+       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
+
+platformMesh = None
+
+def DrawMachine(machineSize):
+       if profile.getPreference('machine_type') == 'ultimaker':
+               glPushMatrix()
+               glEnable(GL_LIGHTING)
+               glTranslate(100, 200, -5)
+               glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.8, 0.8, 0.8])
+               glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5])
+               glEnable(GL_BLEND)
+               glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR)
+
+               global platformMesh
+               if platformMesh == None:
+                       platformMesh = meshLoader.loadMesh(getPathForMesh('ultimaker_platform.stl'))
+                       platformMesh.setRotateMirror(0, False, False, False, False, False)
+
+               DrawMesh(platformMesh)
+               glPopMatrix()
+
+       glDisable(GL_LIGHTING)
+       if False:
+               glColor3f(0.7, 0.7, 0.7)
+               glLineWidth(2)
+               glBegin(GL_LINES)
+               for i in xrange(0, int(machineSize.x), 10):
+                       glVertex3f(i, 0, 0)
+                       glVertex3f(i, machineSize.y, 0)
+               for i in xrange(0, int(machineSize.y), 10):
+                       glVertex3f(0, i, 0)
+                       glVertex3f(machineSize.x, i, 0)
+               glEnd()
+
+               glEnable(GL_LINE_SMOOTH)
+               glEnable(GL_BLEND)
+               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+               glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
+
+               glColor3f(0.0, 0.0, 0.0)
+               glLineWidth(4)
+               glBegin(GL_LINE_LOOP)
+               glVertex3f(0, 0, 0)
+               glVertex3f(machineSize.x, 0, 0)
+               glVertex3f(machineSize.x, machineSize.y, 0)
+               glVertex3f(0, machineSize.y, 0)
+               glEnd()
+
+               glLineWidth(2)
+               glBegin(GL_LINE_LOOP)
+               glVertex3f(0, 0, machineSize.z)
+               glVertex3f(machineSize.x, 0, machineSize.z)
+               glVertex3f(machineSize.x, machineSize.y, machineSize.z)
+               glVertex3f(0, machineSize.y, machineSize.z)
+               glEnd()
+               glBegin(GL_LINES)
+               glVertex3f(0, 0, 0)
+               glVertex3f(0, 0, machineSize.z)
+               glVertex3f(machineSize.x, 0, 0)
+               glVertex3f(machineSize.x, 0, machineSize.z)
+               glVertex3f(machineSize.x, machineSize.y, 0)
+               glVertex3f(machineSize.x, machineSize.y, machineSize.z)
+               glVertex3f(0, machineSize.y, 0)
+               glVertex3f(0, machineSize.y, machineSize.z)
+               glEnd()
+       else:
+               glDisable(GL_CULL_FACE)
+               glEnable(GL_BLEND)
+               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+               glColor4ub(5, 171, 231, 127)
+               glBegin(GL_QUADS)
+               for x in xrange(0, int(machineSize.x), 20):
+                       for y in xrange(0, int(machineSize.y), 20):
+                               glVertex3f(x, y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)
+                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)
+               for x in xrange(10, int(machineSize.x), 20):
+                       for y in xrange(10, int(machineSize.y), 20):
+                               glVertex3f(x, y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)
+                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)
+               glEnd()
+               glColor4ub(5 * 8 / 10, 171 * 8 / 10, 231 * 8 / 10, 128)
+               glBegin(GL_QUADS)
+               for x in xrange(10, int(machineSize.x), 20):
+                       for y in xrange(0, int(machineSize.y), 20):
+                               glVertex3f(x, y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)
+                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)
+               for x in xrange(0, int(machineSize.x), 20):
+                       for y in xrange(10, int(machineSize.y), 20):
+                               glVertex3f(x, y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), y, -0.01)
+                               glVertex3f(min(x + 10, machineSize.x), min(y + 10, machineSize.y), -0.01)
+                               glVertex3f(x, min(y + 10, machineSize.y), -0.01)
+               glEnd()
+               glEnable(GL_CULL_FACE)
+
+               glColor4ub(5, 171, 231, 64)
+               glBegin(GL_QUADS)
+               glVertex3f(0, 0, machineSize.z)
+               glVertex3f(0, machineSize.y, machineSize.z)
+               glVertex3f(machineSize.x, machineSize.y, machineSize.z)
+               glVertex3f(machineSize.x, 0, machineSize.z)
+               glEnd()
+
+               glColor4ub(5, 171, 231, 96)
+               glBegin(GL_QUADS)
+               glVertex3f(0, 0, 0)
+               glVertex3f(0, 0, machineSize.z)
+               glVertex3f(machineSize.x, 0, machineSize.z)
+               glVertex3f(machineSize.x, 0, 0)
+
+               glVertex3f(0, machineSize.y, machineSize.z)
+               glVertex3f(0, machineSize.y, 0)
+               glVertex3f(machineSize.x, machineSize.y, 0)
+               glVertex3f(machineSize.x, machineSize.y, machineSize.z)
+               glEnd()
+
+               glColor4ub(5, 171, 231, 128)
+               glBegin(GL_QUADS)
+               glVertex3f(0, 0, machineSize.z)
+               glVertex3f(0, 0, 0)
+               glVertex3f(0, machineSize.y, 0)
+               glVertex3f(0, machineSize.y, machineSize.z)
+
+               glVertex3f(machineSize.x, 0, 0)
+               glVertex3f(machineSize.x, 0, machineSize.z)
+               glVertex3f(machineSize.x, machineSize.y, machineSize.z)
+               glVertex3f(machineSize.x, machineSize.y, 0)
+               glEnd()
+
+               glDisable(GL_BLEND)
+
+       glPushMatrix()
+       glTranslate(5, 5, 2)
+       glLineWidth(2)
+       glColor3f(0.5, 0, 0)
+       glBegin(GL_LINES)
+       glVertex3f(0, 0, 0)
+       glVertex3f(20, 0, 0)
+       glEnd()
+       glColor3f(0, 0.5, 0)
+       glBegin(GL_LINES)
+       glVertex3f(0, 0, 0)
+       glVertex3f(0, 20, 0)
+       glEnd()
+       glColor3f(0, 0, 0.5)
+       glBegin(GL_LINES)
+       glVertex3f(0, 0, 0)
+       glVertex3f(0, 0, 20)
+       glEnd()
+
+       glDisable(GL_DEPTH_TEST)
+       #X
+       glColor3f(1, 0, 0)
+       glPushMatrix()
+       glTranslate(23, 0, 0)
+       noZ = ResetMatrixRotationAndScale()
+       glBegin(GL_LINES)
+       glVertex3f(-0.8, 1, 0)
+       glVertex3f(0.8, -1, 0)
+       glVertex3f(0.8, 1, 0)
+       glVertex3f(-0.8, -1, 0)
+       glEnd()
+       glPopMatrix()
+
+       #Y
+       glColor3f(0, 1, 0)
+       glPushMatrix()
+       glTranslate(0, 23, 0)
+       ResetMatrixRotationAndScale()
+       glBegin(GL_LINES)
+       glVertex3f(-0.8, 1, 0)
+       glVertex3f(0.0, 0, 0)
+       glVertex3f(0.8, 1, 0)
+       glVertex3f(-0.8, -1, 0)
+       glEnd()
+       glPopMatrix()
+
+       #Z
+       if not noZ:
+               glColor3f(0, 0, 1)
+               glPushMatrix()
+               glTranslate(0, 0, 23)
+               ResetMatrixRotationAndScale()
+               glBegin(GL_LINES)
+               glVertex3f(-0.8, 1, 0)
+               glVertex3f(0.8, 1, 0)
+               glVertex3f(0.8, 1, 0)
+               glVertex3f(-0.8, -1, 0)
+               glVertex3f(-0.8, -1, 0)
+               glVertex3f(0.8, -1, 0)
+               glEnd()
+               glPopMatrix()
+
+       glPopMatrix()
+       glEnable(GL_DEPTH_TEST)
+
+
+def ResetMatrixRotationAndScale():
+       matrix = glGetFloatv(GL_MODELVIEW_MATRIX)
+       noZ = False
+       if matrix[3][2] > 0:
+               return False
+       scale2D = matrix[0][0]
+       matrix[0][0] = 1.0
+       matrix[1][0] = 0.0
+       matrix[2][0] = 0.0
+       matrix[0][1] = 0.0
+       matrix[1][1] = 1.0
+       matrix[2][1] = 0.0
+       matrix[0][2] = 0.0
+       matrix[1][2] = 0.0
+       matrix[2][2] = 1.0
+
+       if matrix[3][2] != 0.0:
+               matrix[3][0] = matrix[3][0] / (-matrix[3][2] / 100)
+               matrix[3][1] = matrix[3][1] / (-matrix[3][2] / 100)
+               matrix[3][2] = -100
+       else:
+               matrix[0][0] = scale2D
+               matrix[1][1] = scale2D
+               matrix[2][2] = scale2D
+               matrix[3][2] = -100
+               noZ = True
+
+       glLoadMatrixf(matrix)
+       return noZ
+
+
+def DrawBox(vMin, vMax):
+       glBegin(GL_LINE_LOOP)
+       glVertex3f(vMin[0], vMin[1], vMin[2])
+       glVertex3f(vMax[0], vMin[1], vMin[2])
+       glVertex3f(vMax[0], vMax[1], vMin[2])
+       glVertex3f(vMin[0], vMax[1], vMin[2])
+       glEnd()
+
+       glBegin(GL_LINE_LOOP)
+       glVertex3f(vMin[0], vMin[1], vMax[2])
+       glVertex3f(vMax[0], vMin[1], vMax[2])
+       glVertex3f(vMax[0], vMax[1], vMax[2])
+       glVertex3f(vMin[0], vMax[1], vMax[2])
+       glEnd()
+       glBegin(GL_LINES)
+       glVertex3f(vMin[0], vMin[1], vMin[2])
+       glVertex3f(vMin[0], vMin[1], vMax[2])
+       glVertex3f(vMax[0], vMin[1], vMin[2])
+       glVertex3f(vMax[0], vMin[1], vMax[2])
+       glVertex3f(vMax[0], vMax[1], vMin[2])
+       glVertex3f(vMax[0], vMax[1], vMax[2])
+       glVertex3f(vMin[0], vMax[1], vMin[2])
+       glVertex3f(vMin[0], vMax[1], vMax[2])
+       glEnd()
+
+
+def DrawMeshOutline(mesh):
+       glEnable(GL_CULL_FACE)
+       glEnableClientState(GL_VERTEX_ARRAY);
+       glVertexPointer(3, GL_FLOAT, 0, mesh.vertexes)
+
+       glCullFace(GL_FRONT)
+       glLineWidth(3)
+       glPolygonMode(GL_BACK, GL_LINE)
+       glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount)
+       glPolygonMode(GL_BACK, GL_FILL)
+       glCullFace(GL_BACK)
+
+       glDisableClientState(GL_VERTEX_ARRAY)
+
+
+def DrawMesh(mesh):
+       glEnable(GL_CULL_FACE)
+       glEnableClientState(GL_VERTEX_ARRAY);
+       glEnableClientState(GL_NORMAL_ARRAY);
+       glVertexPointer(3, GL_FLOAT, 0, mesh.vertexes)
+       glNormalPointer(GL_FLOAT, 0, mesh.normal)
+
+       #Odd, drawing in batchs is a LOT faster then drawing it all at once.
+       batchSize = 999    #Warning, batchSize needs to be dividable by 3
+       extraStartPos = int(mesh.vertexCount / batchSize) * batchSize
+       extraCount = mesh.vertexCount - extraStartPos
+
+       glCullFace(GL_BACK)
+       for i in xrange(0, int(mesh.vertexCount / batchSize)):
+               glDrawArrays(GL_TRIANGLES, i * batchSize, batchSize)
+       glDrawArrays(GL_TRIANGLES, extraStartPos, extraCount)
+
+       glCullFace(GL_FRONT)
+       glNormalPointer(GL_FLOAT, 0, mesh.invNormal)
+       for i in xrange(0, int(mesh.vertexCount / batchSize)):
+               glDrawArrays(GL_TRIANGLES, i * batchSize, batchSize)
+       extraStartPos = int(mesh.vertexCount / batchSize) * batchSize
+       extraCount = mesh.vertexCount - extraStartPos
+       glDrawArrays(GL_TRIANGLES, extraStartPos, extraCount)
+       glCullFace(GL_BACK)
+
+       glDisableClientState(GL_VERTEX_ARRAY)
+       glDisableClientState(GL_NORMAL_ARRAY);
+
+
+def DrawMeshSteep(mesh, angle):
+       cosAngle = math.sin(angle / 180.0 * math.pi)
+       glDisable(GL_LIGHTING)
+       glDepthFunc(GL_EQUAL)
+       for i in xrange(0, int(mesh.vertexCount), 3):
+               if mesh.normal[i][2] < -0.999999:
+                       if mesh.vertexes[i + 0][2] > 0.01:
+                               glColor3f(0.5, 0, 0)
+                               glBegin(GL_TRIANGLES)
+                               glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
+                               glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
+                               glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
+                               glEnd()
+               elif mesh.normal[i][2] < -cosAngle:
+                       glColor3f(-mesh.normal[i][2], 0, 0)
+                       glBegin(GL_TRIANGLES)
+                       glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
+                       glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
+                       glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
+                       glEnd()
+               elif mesh.normal[i][2] > 0.999999:
+                       if mesh.vertexes[i + 0][2] > 0.01:
+                               glColor3f(0.5, 0, 0)
+                               glBegin(GL_TRIANGLES)
+                               glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
+                               glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
+                               glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
+                               glEnd()
+               elif mesh.normal[i][2] > cosAngle:
+                       glColor3f(mesh.normal[i][2], 0, 0)
+                       glBegin(GL_TRIANGLES)
+                       glVertex3f(mesh.vertexes[i + 0][0], mesh.vertexes[i + 0][1], mesh.vertexes[i + 0][2])
+                       glVertex3f(mesh.vertexes[i + 2][0], mesh.vertexes[i + 2][1], mesh.vertexes[i + 2][2])
+                       glVertex3f(mesh.vertexes[i + 1][0], mesh.vertexes[i + 1][1], mesh.vertexes[i + 1][2])
+                       glEnd()
+       glDepthFunc(GL_LESS)
+
+
+def DrawGCodeLayer(layer):
+       filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
+       filamentArea = math.pi * filamentRadius * filamentRadius
+       lineWidth = profile.getProfileSettingFloat('nozzle_size') / 2 / 10
+
+       fillCycle = 0
+       fillColorCycle = [[0.5, 0.5, 0.0], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5]]
+       moveColor = [0, 0, 1]
+       retractColor = [1, 0, 0.5]
+       supportColor = [0, 1, 1]
+       extrudeColor = [1, 0, 0]
+       innerWallColor = [0, 1, 0]
+       skirtColor = [0, 0.5, 0.5]
+       prevPathWasRetract = False
+
+       glDisable(GL_CULL_FACE)
+       for path in layer:
+               if path.type == 'move':
+                       if prevPathWasRetract:
+                               c = retractColor
+                       else:
+                               c = moveColor
+               zOffset = 0.01
+               if path.type == 'extrude':
+                       if path.pathType == 'FILL':
+                               c = fillColorCycle[fillCycle]
+                               fillCycle = (fillCycle + 1) % len(fillColorCycle)
+                       elif path.pathType == 'WALL-INNER':
+                               c = innerWallColor
+                               zOffset = 0.02
+                       elif path.pathType == 'SUPPORT':
+                               c = supportColor
+                       elif path.pathType == 'SKIRT':
+                               c = skirtColor
+                       else:
+                               c = extrudeColor
+               if path.type == 'retract':
+                       c = [0, 1, 1]
+               if path.type == 'extrude':
+                       drawLength = 0.0
+                       prevNormal = None
+                       for i in xrange(0, len(path.list) - 1):
+                               v0 = path.list[i]
+                               v1 = path.list[i + 1]
+
+                               # Calculate line width from ePerDistance (needs layer thickness and filament diameter)
+                               dist = (v0 - v1).vsize()
+                               if dist > 0 and path.layerThickness > 0:
+                                       extrusionMMperDist = (v1.e - v0.e) / dist
+                                       lineWidth = extrusionMMperDist * filamentArea / path.layerThickness / 2 * v1.extrudeAmountMultiply
+
+                               drawLength += (v0 - v1).vsize()
+                               normal = (v0 - v1).cross(util3d.Vector3(0, 0, 1))
+                               normal.normalize()
+
+                               vv2 = v0 + normal * lineWidth
+                               vv3 = v1 + normal * lineWidth
+                               vv0 = v0 - normal * lineWidth
+                               vv1 = v1 - normal * lineWidth
+
+                               glBegin(GL_QUADS)
+                               glColor3fv(c)
+                               glVertex3f(vv0.x, vv0.y, vv0.z - zOffset)
+                               glVertex3f(vv1.x, vv1.y, vv1.z - zOffset)
+                               glVertex3f(vv3.x, vv3.y, vv3.z - zOffset)
+                               glVertex3f(vv2.x, vv2.y, vv2.z - zOffset)
+                               glEnd()
+                               if prevNormal != None:
+                                       n = (normal + prevNormal)
+                                       n.normalize()
+                                       vv4 = v0 + n * lineWidth
+                                       vv5 = v0 - n * lineWidth
+                                       glBegin(GL_QUADS)
+                                       glColor3fv(c)
+                                       glVertex3f(vv2.x, vv2.y, vv2.z - zOffset)
+                                       glVertex3f(vv4.x, vv4.y, vv4.z - zOffset)
+                                       glVertex3f(prevVv3.x, prevVv3.y, prevVv3.z - zOffset)
+                                       glVertex3f(v0.x, v0.y, v0.z - zOffset)
+
+                                       glVertex3f(vv0.x, vv0.y, vv0.z - zOffset)
+                                       glVertex3f(vv5.x, vv5.y, vv5.z - zOffset)
+                                       glVertex3f(prevVv1.x, prevVv1.y, prevVv1.z - zOffset)
+                                       glVertex3f(v0.x, v0.y, v0.z - zOffset)
+                                       glEnd()
+
+                               prevNormal = normal
+                               prevVv1 = vv1
+                               prevVv3 = vv3
+               else:
+                       glBegin(GL_LINE_STRIP)
+                       glColor3fv(c)
+                       for v in path.list:
+                               glVertex3f(v.x, v.y, v.z)
+                       glEnd()
+               if not path.type == 'move':
+                       prevPathWasRetract = False
+               if path.type == 'retract' and path.list[0].almostEqual(path.list[-1]):
+                       prevPathWasRetract = True
+       glEnable(GL_CULL_FACE)
index da41b3e7f4e031f9cb114177db43c6b0135d848f..937778294f3160b24bd62168a1954215467c2ed3 100644 (file)
@@ -1,82 +1,82 @@
-from __future__ import absolute_import\r
-import __init__\r
-\r
-import wx, os, platform, types, string, glob, stat\r
-import ConfigParser\r
-\r
-from gui import configBase\r
-from util import validators\r
-from util import machineCom\r
-from util import profile\r
-\r
-class preferencesDialog(configBase.configWindowBase):\r
-       def __init__(self, parent):\r
-               super(preferencesDialog, self).__init__(title="Preferences", style=wx.DEFAULT_DIALOG_STYLE)\r
-               \r
-               wx.EVT_CLOSE(self, self.OnClose)\r
-               \r
-               self.parent = parent\r
-               self.oldExtruderAmount = int(profile.getPreference('extruder_amount'))\r
-               \r
-               left, right, main = self.CreateConfigPanel(self)\r
-               configBase.TitleRow(left, 'Machine settings')\r
-               c = configBase.SettingRow(left, 'Steps per E', 'steps_per_e', '0', 'Amount of steps per mm filament extrusion', type = 'preference')\r
-               validators.validFloat(c, 0.1)\r
-               c = configBase.SettingRow(left, 'Maximum width (mm)', 'machine_width', '205', 'Size of the machine in mm', type = 'preference')\r
-               validators.validFloat(c, 10.0)\r
-               c = configBase.SettingRow(left, 'Maximum depth (mm)', 'machine_depth', '205', 'Size of the machine in mm', type = 'preference')\r
-               validators.validFloat(c, 10.0)\r
-               c = configBase.SettingRow(left, 'Maximum height (mm)', 'machine_height', '200', 'Size of the machine in mm', type = 'preference')\r
-               validators.validFloat(c, 10.0)\r
-               c = configBase.SettingRow(left, 'Extruder count', 'extruder_amount', ['1', '2', '3', '4'], 'Amount of extruders in your machine.', type = 'preference')\r
-               c = configBase.SettingRow(left, 'Heated bed', 'has_heated_bed', False, 'If you have an heated bed, this enabled heated bed settings', type = 'preference')\r
-               \r
-               for i in xrange(1, self.oldExtruderAmount):\r
-                       configBase.TitleRow(left, 'Extruder %d' % (i+1))\r
-                       c = configBase.SettingRow(left, 'Offset X', 'extruder_offset_x%d' % (i), '0.0', 'The offset of your secondary extruder compared to the primary.', type = 'preference')\r
-                       validators.validFloat(c)\r
-                       c = configBase.SettingRow(left, 'Offset Y', 'extruder_offset_y%d' % (i), '0.0', 'The offset of your secondary extruder compared to the primary.', type = 'preference')\r
-                       validators.validFloat(c)\r
-\r
-               configBase.TitleRow(left, 'Colours')\r
-               c = configBase.SettingRow(left, 'Model colour', 'model_colour', wx.Colour(0,0,0), '', type = 'preference')\r
-               for i in xrange(1, self.oldExtruderAmount):\r
-                       c = configBase.SettingRow(left, 'Model colour (%d)' % (i+1), 'model_colour%d' % (i+1), wx.Colour(0,0,0), '', type = 'preference')\r
-\r
-               configBase.TitleRow(right, 'Filament settings')\r
-               c = configBase.SettingRow(right, 'Density (kg/m3)', 'filament_density', '1300', 'Weight of the filament per m3. Around 1300 for PLA. And around 1040 for ABS. This value is used to estimate the weight if the filament used for the print.', type = 'preference')\r
-               validators.validFloat(c, 500.0, 3000.0)\r
-               c = configBase.SettingRow(right, 'Cost (price/kg)', 'filament_cost_kg', '0', 'Cost of your filament per kg, to estimate the cost of the final print.', type = 'preference')\r
-               validators.validFloat(c, 0.0)\r
-               c = configBase.SettingRow(right, 'Cost (price/m)', 'filament_cost_meter', '0', 'Cost of your filament per meter, to estimate the cost of the final print.', type = 'preference')\r
-               validators.validFloat(c, 0.0)\r
-               \r
-               configBase.TitleRow(right, 'Communication settings')\r
-               c = configBase.SettingRow(right, 'Serial port', 'serial_port', ['AUTO'] + machineCom.serialList(), 'Serial port to use for communication with the printer', type = 'preference')\r
-               c = configBase.SettingRow(right, 'Baudrate', 'serial_baud', ['AUTO'] + map(str, machineCom.baudrateList()), 'Speed of the serial port communication\nNeeds to match your firmware settings\nCommon values are 250000, 115200, 57600', type = 'preference')\r
-\r
-               configBase.TitleRow(right, 'Slicer settings')\r
-               #c = configBase.SettingRow(right, 'Slicer selection', 'slicer', ['Cura (Skeinforge based)', 'Slic3r'], 'Which slicer to use to slice objects. Usually the Cura engine produces the best results. But Slic3r is developing fast and is faster with slicing.', type = 'preference')\r
-               c = configBase.SettingRow(right, 'Save profile on slice', 'save_profile', False, 'When slicing save the profile as [stl_file]_profile.ini next to the model.', type = 'preference')\r
-\r
-               configBase.TitleRow(right, 'SD Card settings')\r
-               if len(profile.getSDcardDrives()) > 1:\r
-                       c = configBase.SettingRow(right, 'SD card drive', 'sdpath', profile.getSDcardDrives(), 'Location of your SD card, when using the copy to SD feature.', type = 'preference')\r
-               else:\r
-                       c = configBase.SettingRow(right, 'SD card path', 'sdpath', '', 'Location of your SD card, when using the copy to SD feature.', type = 'preference')\r
-               c = configBase.SettingRow(right, 'Copy to SD with 8.3 names', 'sdshortnames', False, 'Save the gcode files in short filenames, so they are properly shown on the UltiController', type = 'preference')\r
-\r
-               self.okButton = wx.Button(right, -1, 'Ok')\r
-               right.GetSizer().Add(self.okButton, (right.GetSizer().GetRows(), 0), flag=wx.BOTTOM, border=5)\r
-               self.okButton.Bind(wx.EVT_BUTTON, self.OnClose)\r
-               \r
-               self.MakeModal(True)\r
-               main.Fit()\r
-               self.Fit()\r
-\r
-       def OnClose(self, e):\r
-               if self.oldExtruderAmount != int(profile.getPreference('extruder_amount')):\r
-                       wx.MessageBox('After changing the amount of extruders you need to restart Cura for full effect.', 'Extruder amount warning.', wx.OK | wx.ICON_INFORMATION)\r
-               self.MakeModal(False)\r
-               self.parent.updateProfileToControls()\r
-               self.Destroy()\r
+from __future__ import absolute_import
+import __init__
+
+import wx, os, platform, types, string, glob, stat
+import ConfigParser
+
+from gui import configBase
+from util import validators
+from util import machineCom
+from util import profile
+
+class preferencesDialog(configBase.configWindowBase):
+       def __init__(self, parent):
+               super(preferencesDialog, self).__init__(title="Preferences", style=wx.DEFAULT_DIALOG_STYLE)
+               
+               wx.EVT_CLOSE(self, self.OnClose)
+               
+               self.parent = parent
+               self.oldExtruderAmount = int(profile.getPreference('extruder_amount'))
+               
+               left, right, main = self.CreateConfigPanel(self)
+               configBase.TitleRow(left, 'Machine settings')
+               c = configBase.SettingRow(left, 'Steps per E', 'steps_per_e', '0', 'Amount of steps per mm filament extrusion', type = 'preference')
+               validators.validFloat(c, 0.1)
+               c = configBase.SettingRow(left, 'Maximum width (mm)', 'machine_width', '205', 'Size of the machine in mm', type = 'preference')
+               validators.validFloat(c, 10.0)
+               c = configBase.SettingRow(left, 'Maximum depth (mm)', 'machine_depth', '205', 'Size of the machine in mm', type = 'preference')
+               validators.validFloat(c, 10.0)
+               c = configBase.SettingRow(left, 'Maximum height (mm)', 'machine_height', '200', 'Size of the machine in mm', type = 'preference')
+               validators.validFloat(c, 10.0)
+               c = configBase.SettingRow(left, 'Extruder count', 'extruder_amount', ['1', '2', '3', '4'], 'Amount of extruders in your machine.', type = 'preference')
+               c = configBase.SettingRow(left, 'Heated bed', 'has_heated_bed', False, 'If you have an heated bed, this enabled heated bed settings', type = 'preference')
+               
+               for i in xrange(1, self.oldExtruderAmount):
+                       configBase.TitleRow(left, 'Extruder %d' % (i+1))
+                       c = configBase.SettingRow(left, 'Offset X', 'extruder_offset_x%d' % (i), '0.0', 'The offset of your secondary extruder compared to the primary.', type = 'preference')
+                       validators.validFloat(c)
+                       c = configBase.SettingRow(left, 'Offset Y', 'extruder_offset_y%d' % (i), '0.0', 'The offset of your secondary extruder compared to the primary.', type = 'preference')
+                       validators.validFloat(c)
+
+               configBase.TitleRow(left, 'Colours')
+               c = configBase.SettingRow(left, 'Model colour', 'model_colour', wx.Colour(0,0,0), '', type = 'preference')
+               for i in xrange(1, self.oldExtruderAmount):
+                       c = configBase.SettingRow(left, 'Model colour (%d)' % (i+1), 'model_colour%d' % (i+1), wx.Colour(0,0,0), '', type = 'preference')
+
+               configBase.TitleRow(right, 'Filament settings')
+               c = configBase.SettingRow(right, 'Density (kg/m3)', 'filament_density', '1300', 'Weight of the filament per m3. Around 1300 for PLA. And around 1040 for ABS. This value is used to estimate the weight if the filament used for the print.', type = 'preference')
+               validators.validFloat(c, 500.0, 3000.0)
+               c = configBase.SettingRow(right, 'Cost (price/kg)', 'filament_cost_kg', '0', 'Cost of your filament per kg, to estimate the cost of the final print.', type = 'preference')
+               validators.validFloat(c, 0.0)
+               c = configBase.SettingRow(right, 'Cost (price/m)', 'filament_cost_meter', '0', 'Cost of your filament per meter, to estimate the cost of the final print.', type = 'preference')
+               validators.validFloat(c, 0.0)
+               
+               configBase.TitleRow(right, 'Communication settings')
+               c = configBase.SettingRow(right, 'Serial port', 'serial_port', ['AUTO'] + machineCom.serialList(), 'Serial port to use for communication with the printer', type = 'preference')
+               c = configBase.SettingRow(right, 'Baudrate', 'serial_baud', ['AUTO'] + map(str, machineCom.baudrateList()), 'Speed of the serial port communication\nNeeds to match your firmware settings\nCommon values are 250000, 115200, 57600', type = 'preference')
+
+               configBase.TitleRow(right, 'Slicer settings')
+               #c = configBase.SettingRow(right, 'Slicer selection', 'slicer', ['Cura (Skeinforge based)', 'Slic3r'], 'Which slicer to use to slice objects. Usually the Cura engine produces the best results. But Slic3r is developing fast and is faster with slicing.', type = 'preference')
+               c = configBase.SettingRow(right, 'Save profile on slice', 'save_profile', False, 'When slicing save the profile as [stl_file]_profile.ini next to the model.', type = 'preference')
+
+               configBase.TitleRow(right, 'SD Card settings')
+               if len(profile.getSDcardDrives()) > 1:
+                       c = configBase.SettingRow(right, 'SD card drive', 'sdpath', profile.getSDcardDrives(), 'Location of your SD card, when using the copy to SD feature.', type = 'preference')
+               else:
+                       c = configBase.SettingRow(right, 'SD card path', 'sdpath', '', 'Location of your SD card, when using the copy to SD feature.', type = 'preference')
+               c = configBase.SettingRow(right, 'Copy to SD with 8.3 names', 'sdshortnames', False, 'Save the gcode files in short filenames, so they are properly shown on the UltiController', type = 'preference')
+
+               self.okButton = wx.Button(right, -1, 'Ok')
+               right.GetSizer().Add(self.okButton, (right.GetSizer().GetRows(), 0), flag=wx.BOTTOM, border=5)
+               self.okButton.Bind(wx.EVT_BUTTON, self.OnClose)
+               
+               self.MakeModal(True)
+               main.Fit()
+               self.Fit()
+
+       def OnClose(self, e):
+               if self.oldExtruderAmount != int(profile.getPreference('extruder_amount')):
+                       wx.MessageBox('After changing the amount of extruders you need to restart Cura for full effect.', 'Extruder amount warning.', wx.OK | wx.ICON_INFORMATION)
+               self.MakeModal(False)
+               self.parent.updateProfileToControls()
+               self.Destroy()
index c830711cc282db66d6ea953b1c9530503177f629..c689b7f9ad161f15868fc3a8f45462c660846050 100644 (file)
-from __future__ import division\r
-\r
-import sys, math, threading, re, time, os\r
-import numpy\r
-\r
-from wx import glcanvas\r
-import wx\r
-try:\r
-       import OpenGL\r
-       OpenGL.ERROR_CHECKING = False\r
-       from OpenGL.GLU import *\r
-       from OpenGL.GL import *\r
-       hasOpenGLlibs = True\r
-except:\r
-       print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
-       hasOpenGLlibs = False\r
-\r
-from gui import opengl\r
-from gui import toolbarUtil\r
-\r
-from util import profile\r
-from util import gcodeInterpreter\r
-from util import meshLoader\r
-from util import util3d\r
-from util import sliceRun\r
-\r
-class previewObject():\r
-       def __init__(self):\r
-               self.mesh = None\r
-               self.filename = None\r
-               self.displayList = None\r
-               self.dirty = False\r
-\r
-class previewPanel(wx.Panel):\r
-       def __init__(self, parent):\r
-               super(previewPanel, self).__init__(parent,-1)\r
-               \r
-               self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))\r
-               self.SetMinSize((440,320))\r
-               \r
-               self.objectList = []\r
-               self.errorList = []\r
-               self.gcode = None\r
-               self.objectsMinV = None\r
-               self.objectsMaxV = None\r
-               self.objectsBounderyCircleSize = None\r
-               self.loadThread = None\r
-               self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))\r
-               self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)\r
-\r
-               self.glCanvas = PreviewGLCanvas(self)\r
-               #Create the popup window\r
-               self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)\r
-               self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))\r
-               self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')\r
-               self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)\r
-               self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)\r
-               self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)\r
-               self.warningPopup.SetSizer(self.warningPopup.sizer)\r
-               self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)\r
-               self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)\r
-               self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)\r
-               self.warningPopup.Fit()\r
-               self.warningPopup.Layout()\r
-               self.warningPopup.timer = wx.Timer(self)\r
-               self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)\r
-               \r
-               self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)\r
-               self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)\r
-               parent.Bind(wx.EVT_MOVE, self.OnMove)\r
-               parent.Bind(wx.EVT_SIZE, self.OnMove)\r
-               \r
-               self.toolbar = toolbarUtil.Toolbar(self)\r
-\r
-               group = []\r
-               toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)\r
-               toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)\r
-               self.toolbar.AddSeparator()\r
-\r
-               self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)\r
-               self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)\r
-               self.toolbar.AddSeparator()\r
-\r
-               group = []\r
-               self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)\r
-               self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)\r
-               self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)\r
-               self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)\r
-               self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)\r
-               self.toolbar.AddSeparator()\r
-\r
-               self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)\r
-               self.toolbar.AddControl(self.layerSpin)\r
-               self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)\r
-               self.toolbar.AddSeparator()\r
-               self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)\r
-               self.toolbar.AddControl(self.toolbarInfo)\r
-\r
-               self.toolbar2 = toolbarUtil.Toolbar(self)\r
-\r
-               # Mirror\r
-               self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.returnToModelViewAndUpdateModel)\r
-               self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)\r
-               self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)\r
-               self.toolbar2.AddSeparator()\r
-\r
-               # Swap\r
-               self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.returnToModelViewAndUpdateModel)\r
-               self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)\r
-               self.toolbar2.AddSeparator()\r
-\r
-               # Scale\r
-               self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')\r
-               self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))\r
-               self.toolbar2.AddControl(self.scale)\r
-               self.scale.Bind(wx.EVT_TEXT, self.OnScale)\r
-               self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')\r
-\r
-               self.toolbar2.AddSeparator()\r
-\r
-               # Multiply\r
-               #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')\r
-               #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')\r
-               #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')\r
-               #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')\r
-               #self.toolbar2.AddSeparator()\r
-\r
-               # Rotate\r
-               self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')\r
-               self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)\r
-               self.rotate.SetRange(0, 360)\r
-               self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)\r
-               self.toolbar2.AddControl(self.rotate)\r
-\r
-               self.toolbar2.Realize()\r
-               self.OnViewChange()\r
-               \r
-               sizer = wx.BoxSizer(wx.VERTICAL)\r
-               sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)\r
-               sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)\r
-               sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)\r
-               self.SetSizer(sizer)\r
-       \r
-       def returnToModelViewAndUpdateModel(self):\r
-               if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':\r
-                       self.setViewMode('Normal')\r
-               self.updateModelTransform()\r
-       \r
-       def OnMove(self, e = None):\r
-               if e != None:\r
-                       e.Skip()\r
-               x, y = self.glCanvas.ClientToScreenXY(0, 0)\r
-               sx, sy = self.glCanvas.GetClientSizeTuple()\r
-               self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))\r
-       \r
-       def OnMulXAddClick(self, e):\r
-               profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))\r
-               self.glCanvas.Refresh()\r
-\r
-       def OnMulXSubClick(self, e):\r
-               profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))\r
-               self.glCanvas.Refresh()\r
-\r
-       def OnMulYAddClick(self, e):\r
-               profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))\r
-               self.glCanvas.Refresh()\r
-\r
-       def OnMulYSubClick(self, e):\r
-               profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))\r
-               self.glCanvas.Refresh()\r
-\r
-       def OnScaleReset(self, e):\r
-               self.scale.SetValue('1.0')\r
-               self.OnScale(None)\r
-\r
-       def OnScale(self, e):\r
-               scale = 1.0\r
-               if self.scale.GetValue() != '':\r
-                       scale = self.scale.GetValue()\r
-               profile.putProfileSetting('model_scale', scale)\r
-               if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':\r
-                       self.setViewMode('Normal')\r
-               self.glCanvas.Refresh()\r
-\r
-               if self.objectsMaxV != None:\r
-                       size = (self.objectsMaxV - self.objectsMinV) * float(scale)\r
-                       self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))\r
-\r
-       def OnScaleMax(self, e = None, onlyScaleDown = False):\r
-               if self.objectsMinV == None:\r
-                       return\r
-               vMin = self.objectsMinV\r
-               vMax = self.objectsMaxV\r
-               skirtSize = 3\r
-               if profile.getProfileSettingFloat('skirt_line_count') > 0:\r
-                       skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')\r
-               scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)\r
-               scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)\r
-               scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)\r
-               scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)\r
-               scaleZ = self.machineSize.z / (vMax[2] - vMin[2])\r
-               scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)\r
-               if scale > 1.0 and onlyScaleDown:\r
-                       return\r
-               self.scale.SetValue(str(scale))\r
-               profile.putProfileSetting('model_scale', self.scale.GetValue())\r
-               if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':\r
-                       self.setViewMode('Normal')\r
-               self.glCanvas.Refresh()\r
-\r
-       def OnRotateReset(self, e):\r
-               self.rotate.SetValue(0)\r
-               self.OnRotate(None)\r
-\r
-       def OnRotate(self, e):\r
-               profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())\r
-               self.returnToModelViewAndUpdateModel()\r
-\r
-       def On3DClick(self):\r
-               self.glCanvas.yaw = 30\r
-               self.glCanvas.pitch = 60\r
-               self.glCanvas.zoom = 300\r
-               self.glCanvas.view3D = True\r
-               self.glCanvas.Refresh()\r
-\r
-       def OnTopClick(self):\r
-               self.glCanvas.view3D = False\r
-               self.glCanvas.zoom = 100\r
-               self.glCanvas.offsetX = 0\r
-               self.glCanvas.offsetY = 0\r
-               self.glCanvas.Refresh()\r
-\r
-       def OnLayerNrChange(self, e):\r
-               self.glCanvas.Refresh()\r
-       \r
-       def setViewMode(self, mode):\r
-               if mode == "Normal":\r
-                       self.normalViewButton.SetValue(True)\r
-               if mode == "GCode":\r
-                       self.gcodeViewButton.SetValue(True)\r
-               self.glCanvas.viewMode = mode\r
-               wx.CallAfter(self.glCanvas.Refresh)\r
-       \r
-       def loadModelFiles(self, filelist, showWarning = False):\r
-               while len(filelist) > len(self.objectList):\r
-                       self.objectList.append(previewObject())\r
-               for idx in xrange(len(filelist), len(self.objectList)):\r
-                       self.objectList[idx].mesh = None\r
-                       self.objectList[idx].filename = None\r
-               for idx in xrange(0, len(filelist)):\r
-                       obj = self.objectList[idx]\r
-                       if obj.filename != filelist[idx]:\r
-                               obj.fileTime = None\r
-                               self.gcodeFileTime = None\r
-                               self.logFileTime = None\r
-                       obj.filename = filelist[idx]\r
-               \r
-               self.gcodeFilename = sliceRun.getExportFilename(filelist[0])\r
-               #Do the STL file loading in a background thread so we don't block the UI.\r
-               if self.loadThread != None and self.loadThread.isAlive():\r
-                       self.loadThread.join()\r
-               self.loadThread = threading.Thread(target=self.doFileLoadThread)\r
-               self.loadThread.daemon = True\r
-               self.loadThread.start()\r
-               \r
-               if showWarning:\r
-                       if profile.getProfileSettingFloat('model_scale') != 1.0 or profile.getProfileSettingFloat('model_rotate_base') != 0 or profile.getProfileSetting('flip_x') != 'False' or profile.getProfileSetting('flip_y') != 'False' or profile.getProfileSetting('flip_z') != 'False' or profile.getProfileSetting('swap_xz') != 'False' or profile.getProfileSetting('swap_yz') != 'False' or len(profile.getPluginConfig()) > 0:\r
-                               self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)\r
-       \r
-       def loadReModelFiles(self, filelist):\r
-               #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)\r
-               for idx in xrange(0, len(filelist)):\r
-                       if self.objectList[idx].filename != filelist[idx]:\r
-                               return False\r
-               self.loadModelFiles(filelist)\r
-               return True\r
-       \r
-       def doFileLoadThread(self):\r
-               for obj in self.objectList:\r
-                       if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:\r
-                               obj.ileTime = os.stat(obj.filename).st_mtime\r
-                               mesh = meshLoader.loadMesh(obj.filename)\r
-                               obj.dirty = False\r
-                               obj.mesh = mesh\r
-                               self.updateModelTransform()\r
-                               self.OnScaleMax(None, True)\r
-                               scale = profile.getProfileSettingFloat('model_scale')\r
-                               size = (self.objectsMaxV - self.objectsMinV) * scale\r
-                               self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))\r
-                               self.glCanvas.zoom = numpy.max(size) * 2.5\r
-                               self.errorList = []\r
-                               wx.CallAfter(self.updateToolbar)\r
-                               wx.CallAfter(self.glCanvas.Refresh)\r
-               \r
-               if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:\r
-                       self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime\r
-                       gcode = gcodeInterpreter.gcode()\r
-                       gcode.progressCallback = self.loadProgress\r
-                       gcode.load(self.gcodeFilename)\r
-                       self.gcodeDirty = False\r
-                       self.gcode = gcode\r
-                       self.gcodeDirty = True\r
-\r
-                       errorList = []\r
-                       for line in open(self.gcodeFilename, "rt"):\r
-                               res = re.search(';Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line)\r
-                               if res != None:\r
-                                       v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))\r
-                                       v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))\r
-                                       errorList.append([v1, v2])\r
-                       self.errorList = errorList\r
-\r
-                       wx.CallAfter(self.updateToolbar)\r
-                       wx.CallAfter(self.glCanvas.Refresh)\r
-               elif not os.path.isfile(self.gcodeFilename):\r
-                       self.gcode = None\r
-       \r
-       def loadProgress(self, progress):\r
-               pass\r
-\r
-       def OnResetAll(self, e = None):\r
-               profile.putProfileSetting('model_scale', '1.0')\r
-               profile.putProfileSetting('model_rotate_base', '0')\r
-               profile.putProfileSetting('flip_x', 'False')\r
-               profile.putProfileSetting('flip_y', 'False')\r
-               profile.putProfileSetting('flip_z', 'False')\r
-               profile.putProfileSetting('swap_xz', 'False')\r
-               profile.putProfileSetting('swap_yz', 'False')\r
-               profile.setPluginConfig([])\r
-               self.GetParent().updateProfileToControls()\r
-\r
-       def ShowWarningPopup(self, text, callback = None):\r
-               self.warningPopup.text.SetLabel(text)\r
-               self.warningPopup.callback = callback\r
-               if callback == None:\r
-                       self.warningPopup.yesButton.Show(False)\r
-                       self.warningPopup.noButton.SetLabel('ok')\r
-               else:\r
-                       self.warningPopup.yesButton.Show(True)\r
-                       self.warningPopup.noButton.SetLabel('no')\r
-               self.warningPopup.Fit()\r
-               self.warningPopup.Layout()\r
-               self.OnMove()\r
-               self.warningPopup.Show(True)\r
-               self.warningPopup.timer.Start(5000)\r
-       \r
-       def OnWarningPopup(self, e):\r
-               self.warningPopup.Show(False)\r
-               self.warningPopup.timer.Stop()\r
-               self.warningPopup.callback()\r
-\r
-       def OnHideWarning(self, e):\r
-               self.warningPopup.Show(False)\r
-               self.warningPopup.timer.Stop()\r
-\r
-       def updateToolbar(self):\r
-               self.gcodeViewButton.Show(self.gcode != None)\r
-               self.mixedViewButton.Show(self.gcode != None)\r
-               self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")\r
-               if self.gcode != None:\r
-                       self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)\r
-               self.toolbar.Realize()\r
-               self.Update()\r
-       \r
-       def OnViewChange(self):\r
-               if self.normalViewButton.GetValue():\r
-                       self.glCanvas.viewMode = "Normal"\r
-               elif self.transparentViewButton.GetValue():\r
-                       self.glCanvas.viewMode = "Transparent"\r
-               elif self.xrayViewButton.GetValue():\r
-                       self.glCanvas.viewMode = "X-Ray"\r
-               elif self.gcodeViewButton.GetValue():\r
-                       self.glCanvas.viewMode = "GCode"\r
-               elif self.mixedViewButton.GetValue():\r
-                       self.glCanvas.viewMode = "Mixed"\r
-               self.glCanvas.drawBorders = self.showBorderButton.GetValue()\r
-               self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()\r
-               self.updateToolbar()\r
-               self.glCanvas.Refresh()\r
-       \r
-       def updateModelTransform(self, f=0):\r
-               if len(self.objectList) < 1 or self.objectList[0].mesh == None:\r
-                       return\r
-               \r
-               rotate = profile.getProfileSettingFloat('model_rotate_base')\r
-               mirrorX = profile.getProfileSetting('flip_x') == 'True'\r
-               mirrorY = profile.getProfileSetting('flip_y') == 'True'\r
-               mirrorZ = profile.getProfileSetting('flip_z') == 'True'\r
-               swapXZ = profile.getProfileSetting('swap_xz') == 'True'\r
-               swapYZ = profile.getProfileSetting('swap_yz') == 'True'\r
-\r
-               for obj in self.objectList:\r
-                       if obj.mesh == None:\r
-                               continue\r
-                       obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)\r
-               \r
-               minV = self.objectList[0].mesh.getMinimum()\r
-               maxV = self.objectList[0].mesh.getMaximum()\r
-               objectsBounderyCircleSize = self.objectList[0].mesh.bounderyCircleSize\r
-               for obj in self.objectList:\r
-                       if obj.mesh == None:\r
-                               continue\r
-\r
-                       obj.mesh.getMinimumZ()\r
-                       minV = numpy.minimum(minV, obj.mesh.getMinimum())\r
-                       maxV = numpy.maximum(maxV, obj.mesh.getMaximum())\r
-                       objectsBounderyCircleSize = max(objectsBounderyCircleSize, obj.mesh.bounderyCircleSize)\r
-\r
-               self.objectsMaxV = maxV\r
-               self.objectsMinV = minV\r
-               self.objectsBounderyCircleSize = objectsBounderyCircleSize\r
-               for obj in self.objectList:\r
-                       if obj.mesh == None:\r
-                               continue\r
-\r
-                       obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])\r
-                       #for v in obj.mesh.vertexes:\r
-                       #       v[2] -= minV[2]\r
-                       #       v[0] -= minV[0] + (maxV[0] - minV[0]) / 2\r
-                       #       v[1] -= minV[1] + (maxV[1] - minV[1]) / 2\r
-                       obj.mesh.getMinimumZ()\r
-                       obj.dirty = True\r
-\r
-               scale = profile.getProfileSettingFloat('model_scale')\r
-               size = (self.objectsMaxV - self.objectsMinV) * scale\r
-               self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))\r
-\r
-               self.glCanvas.Refresh()\r
-       \r
-       def updateProfileToControls(self):\r
-               self.scale.SetValue(profile.getProfileSetting('model_scale'))\r
-               self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))\r
-               self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')\r
-               self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')\r
-               self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')\r
-               self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')\r
-               self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')\r
-               self.updateModelTransform()\r
-               self.glCanvas.updateProfileToControls()\r
-\r
-class PreviewGLCanvas(glcanvas.GLCanvas):\r
-       def __init__(self, parent):\r
-               attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
-               glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
-               self.parent = parent\r
-               self.context = glcanvas.GLContext(self)\r
-               wx.EVT_PAINT(self, self.OnPaint)\r
-               wx.EVT_SIZE(self, self.OnSize)\r
-               wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
-               wx.EVT_MOTION(self, self.OnMouseMotion)\r
-               wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
-               self.yaw = 30\r
-               self.pitch = 60\r
-               self.zoom = 300\r
-               self.offsetX = 0\r
-               self.offsetY = 0\r
-               self.view3D = True\r
-               self.gcodeDisplayList = None\r
-               self.gcodeDisplayListMade = None\r
-               self.gcodeDisplayListCount = 0\r
-               self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]]\r
-               self.oldX = 0\r
-               self.oldY = 0\r
-               self.dragType = ''\r
-               self.tempRotate = 0\r
-       \r
-       def updateProfileToControls(self):\r
-               self.objColor[0] = profile.getPreferenceColour('model_colour')\r
-               self.objColor[1] = profile.getPreferenceColour('model_colour2')\r
-               self.objColor[2] = profile.getPreferenceColour('model_colour3')\r
-               self.objColor[3] = profile.getPreferenceColour('model_colour4')\r
-\r
-       def OnMouseMotion(self,e):\r
-               cursorXY = 100000\r
-               radius = 0\r
-               if self.parent.objectsMaxV != None:\r
-                       radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')\r
-                       \r
-                       p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))\r
-                       p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))\r
-                       cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))\r
-                       cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))\r
-                       if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:\r
-                               self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))\r
-                       else:\r
-                               self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))\r
-\r
-               if e.Dragging() and e.LeftIsDown():\r
-                       if self.dragType == '':\r
-                               #Define the drag type depending on the cursor position.\r
-                               if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:\r
-                                       self.dragType = 'modelRotate'\r
-                                       self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])\r
-                               else:\r
-                                       self.dragType = 'viewRotate'\r
-                               \r
-                       if self.dragType == 'viewRotate':\r
-                               if self.view3D:\r
-                                       self.yaw += e.GetX() - self.oldX\r
-                                       self.pitch -= e.GetY() - self.oldY\r
-                                       if self.pitch > 170:\r
-                                               self.pitch = 170\r
-                                       if self.pitch < 10:\r
-                                               self.pitch = 10\r
-                               else:\r
-                                       self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
-                                       self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
-                       elif self.dragType == 'modelRotate':\r
-                               angle = math.atan2(cursorZ0[0], cursorZ0[1])\r
-                               diff = self.dragStart - angle\r
-                               self.tempRotate = diff * 180 / math.pi\r
-                               rot = profile.getProfileSettingFloat('model_rotate_base')\r
-                               self.tempRotate = round((self.tempRotate + rot) / 15) * 15 - rot\r
-                       #Workaround for buggy ATI cards.\r
-                       size = self.GetSizeTuple()\r
-                       self.SetSize((size[0]+1, size[1]))\r
-                       self.SetSize((size[0], size[1]))\r
-                       self.Refresh()\r
-               else:\r
-                       if self.tempRotate != 0:\r
-                               newRotation = profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate\r
-                               while newRotation >= 360:\r
-                                       newRotation -= 360\r
-                               while newRotation < 0:\r
-                                       newRotation += 360\r
-                               profile.putProfileSetting('model_rotate_base', newRotation)\r
-                               self.parent.rotate.SetValue(newRotation)\r
-                               self.parent.updateModelTransform()\r
-                               self.tempRotate = 0\r
-                               \r
-                       self.dragType = ''\r
-               if e.Dragging() and e.RightIsDown():\r
-                       self.zoom += e.GetY() - self.oldY\r
-                       if self.zoom < 1:\r
-                               self.zoom = 1\r
-                       if self.zoom > 500:\r
-                               self.zoom = 500\r
-                       self.Refresh()\r
-               self.oldX = e.GetX()\r
-               self.oldY = e.GetY()\r
-\r
-               #self.Refresh()\r
-               \r
-       \r
-       def OnMouseWheel(self,e):\r
-               self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
-               if self.zoom < 1.0:\r
-                       self.zoom = 1.0\r
-               if self.zoom > 500:\r
-                       self.zoom = 500\r
-               self.Refresh()\r
-       \r
-       def OnEraseBackground(self,event):\r
-               #Workaround for windows background redraw flicker.\r
-               pass\r
-       \r
-       def OnSize(self,e):\r
-               self.Refresh()\r
-\r
-       def OnPaint(self,e):\r
-               dc = wx.PaintDC(self)\r
-               if not hasOpenGLlibs:\r
-                       dc.Clear()\r
-                       dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
-                       return\r
-               self.SetCurrent(self.context)\r
-               opengl.InitGL(self, self.view3D, self.zoom)\r
-               if self.view3D:\r
-                       glTranslate(0,0,-self.zoom)\r
-                       glRotate(-self.pitch, 1,0,0)\r
-                       glRotate(self.yaw, 0,0,1)\r
-                       if self.viewMode == "GCode" or self.viewMode == "Mixed":\r
-                               if self.parent.gcode != None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:\r
-                                       glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)\r
-                       else:\r
-                               if self.parent.objectsMaxV != None:\r
-                                       glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)\r
-               else:\r
-                       glTranslate(self.offsetX, self.offsetY, 0)\r
-\r
-               self.viewport = glGetIntegerv(GL_VIEWPORT);\r
-               self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);\r
-               self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);\r
-\r
-               glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)\r
-\r
-               self.OnDraw()\r
-               self.SwapBuffers()\r
-\r
-       def OnDraw(self):\r
-               machineSize = self.parent.machineSize\r
-\r
-               if self.parent.gcode != None and self.parent.gcodeDirty:\r
-                       if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:\r
-                               if self.gcodeDisplayList != None:\r
-                                       glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)\r
-                               self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));\r
-                               self.gcodeDisplayListCount = len(self.parent.gcode.layerList)\r
-                       self.parent.gcodeDirty = False\r
-                       self.gcodeDisplayListMade = 0\r
-               \r
-               if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):\r
-                       glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)\r
-                       opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])\r
-                       glEndList()\r
-                       self.gcodeDisplayListMade += 1\r
-                       wx.CallAfter(self.Refresh)\r
-               \r
-               glPushMatrix()\r
-               glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
-               for obj in self.parent.objectList:\r
-                       if obj.mesh == None:\r
-                               continue\r
-                       if obj.displayList == None:\r
-                               obj.displayList = glGenLists(1)\r
-                               obj.steepDisplayList = glGenLists(1)\r
-                       if obj.dirty:\r
-                               obj.dirty = False\r
-                               glNewList(obj.displayList, GL_COMPILE)\r
-                               opengl.DrawMesh(obj.mesh)\r
-                               glEndList()\r
-                               glNewList(obj.steepDisplayList, GL_COMPILE)\r
-                               opengl.DrawMeshSteep(obj.mesh, 60)\r
-                               glEndList()\r
-                       \r
-                       if self.viewMode == "Mixed":\r
-                               glDisable(GL_BLEND)\r
-                               glColor3f(0.0,0.0,0.0)\r
-                               self.drawModel(obj)\r
-                               glColor3f(1.0,1.0,1.0)\r
-                               glClear(GL_DEPTH_BUFFER_BIT)\r
-               \r
-               glPopMatrix()\r
-               \r
-               if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):\r
-                       glEnable(GL_COLOR_MATERIAL)\r
-                       glEnable(GL_LIGHTING)\r
-                       drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)\r
-                       starttime = time.time()\r
-                       for i in xrange(drawUpToLayer - 1, -1, -1):\r
-                               c = 1.0\r
-                               if i < self.parent.layerSpin.GetValue():\r
-                                       c = 0.9 - (drawUpToLayer - i) * 0.1\r
-                                       if c < 0.4:\r
-                                               c = (0.4 + c) / 2\r
-                                       if c < 0.1:\r
-                                               c = 0.1\r
-                               glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])\r
-                               glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])\r
-                               glCallList(self.gcodeDisplayList + i)\r
-                               if time.time() - starttime > 0.1:\r
-                                       break\r
-\r
-                       glDisable(GL_LIGHTING)\r
-                       glDisable(GL_COLOR_MATERIAL)\r
-                       glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);\r
-                       glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);\r
-\r
-               glColor3f(1.0,1.0,1.0)\r
-               glPushMatrix()\r
-               glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
-               for obj in self.parent.objectList:\r
-                       if obj.mesh == None:\r
-                               continue\r
-                       \r
-                       if self.viewMode == "Transparent" or self.viewMode == "Mixed":\r
-                               glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))\r
-                               glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))\r
-                               #If we want transparent, then first render a solid black model to remove the printer size lines.\r
-                               if self.viewMode != "Mixed":\r
-                                       glDisable(GL_BLEND)\r
-                                       glColor3f(0.0,0.0,0.0)\r
-                                       self.drawModel(obj)\r
-                                       glColor3f(1.0,1.0,1.0)\r
-                               #After the black model is rendered, render the model again but now with lighting and no depth testing.\r
-                               glDisable(GL_DEPTH_TEST)\r
-                               glEnable(GL_LIGHTING)\r
-                               glEnable(GL_BLEND)\r
-                               glBlendFunc(GL_ONE, GL_ONE)\r
-                               glEnable(GL_LIGHTING)\r
-                               self.drawModel(obj)\r
-                               glEnable(GL_DEPTH_TEST)\r
-                       elif self.viewMode == "X-Ray":\r
-                               glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
-                               glDisable(GL_LIGHTING)\r
-                               glDisable(GL_DEPTH_TEST)\r
-                               glEnable(GL_STENCIL_TEST)\r
-                               glStencilFunc(GL_ALWAYS, 1, 1)\r
-                               glStencilOp(GL_INCR, GL_INCR, GL_INCR)\r
-                               self.drawModel(obj)\r
-                               glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);\r
-                               \r
-                               glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
-                               glStencilFunc(GL_EQUAL, 0, 1)\r
-                               glColor(1, 1, 1)\r
-                               self.drawModel(obj)\r
-                               glStencilFunc(GL_EQUAL, 1, 1)\r
-                               glColor(1, 0, 0)\r
-                               self.drawModel(obj)\r
-\r
-                               glPushMatrix()\r
-                               glLoadIdentity()\r
-                               for i in xrange(2, 15, 2):\r
-                                       glStencilFunc(GL_EQUAL, i, 0xFF);\r
-                                       glColor(float(i)/10, float(i)/10, float(i)/5)\r
-                                       glBegin(GL_QUADS)\r
-                                       glVertex3f(-1000,-1000,-1)\r
-                                       glVertex3f( 1000,-1000,-1)\r
-                                       glVertex3f( 1000, 1000,-1)\r
-                                       glVertex3f(-1000, 1000,-1)\r
-                                       glEnd()\r
-                               for i in xrange(1, 15, 2):\r
-                                       glStencilFunc(GL_EQUAL, i, 0xFF);\r
-                                       glColor(float(i)/10, 0, 0)\r
-                                       glBegin(GL_QUADS)\r
-                                       glVertex3f(-1000,-1000,-1)\r
-                                       glVertex3f( 1000,-1000,-1)\r
-                                       glVertex3f( 1000, 1000,-1)\r
-                                       glVertex3f(-1000, 1000,-1)\r
-                                       glEnd()\r
-                               glPopMatrix()\r
-\r
-                               glDisable(GL_STENCIL_TEST)\r
-                               glEnable(GL_DEPTH_TEST)\r
-                               \r
-                               #Fix the depth buffer\r
-                               glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)\r
-                               self.drawModel(obj)\r
-                               glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)\r
-                       elif self.viewMode == "Normal":\r
-                               glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])\r
-                               glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))\r
-                               glEnable(GL_LIGHTING)\r
-                               self.drawModel(obj)\r
-\r
-                       if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):\r
-                               glEnable(GL_DEPTH_TEST)\r
-                               glDisable(GL_LIGHTING)\r
-                               glColor3f(1,1,1)\r
-                               glPushMatrix()\r
-                               modelScale = profile.getProfileSettingFloat('model_scale')\r
-                               glScalef(modelScale, modelScale, modelScale)\r
-                               opengl.DrawMeshOutline(obj.mesh)\r
-                               glPopMatrix()\r
-                       \r
-                       if self.drawSteepOverhang:\r
-                               glDisable(GL_LIGHTING)\r
-                               glColor3f(1,1,1)\r
-                               glPushMatrix()\r
-                               modelScale = profile.getProfileSettingFloat('model_scale')\r
-                               glScalef(modelScale, modelScale, modelScale)\r
-                               glCallList(obj.steepDisplayList)\r
-                               glPopMatrix()\r
-               \r
-               glPopMatrix()   \r
-               if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":\r
-                       glDisable(GL_LIGHTING)\r
-                       glDisable(GL_DEPTH_TEST)\r
-                       glDisable(GL_BLEND)\r
-                       glColor3f(1,0,0)\r
-                       glBegin(GL_LINES)\r
-                       for err in self.parent.errorList:\r
-                               glVertex3f(err[0].x, err[0].y, err[0].z)\r
-                               glVertex3f(err[1].x, err[1].y, err[1].z)\r
-                       glEnd()\r
-                       glEnable(GL_DEPTH_TEST)\r
-\r
-               glPushMatrix()\r
-               glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)\r
-               \r
-               #Draw the rotate circle\r
-               if self.parent.objectsMaxV != None:\r
-                       glDisable(GL_LIGHTING)\r
-                       glDisable(GL_CULL_FACE)\r
-                       glEnable(GL_BLEND)\r
-                       glRotate(self.tempRotate + profile.getProfileSettingFloat('model_rotate_base'), 0, 0, 1)\r
-                       radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')\r
-                       glScalef(radius, radius, 1)\r
-                       glBegin(GL_TRIANGLE_STRIP)\r
-                       for i in xrange(0, 64+1):\r
-                               f = i if i < 64/2 else 64 - i\r
-                               glColor4ub(255,int(f*255/(64/2)),0,255)\r
-                               glVertex3f(1.1 * math.cos(i/32.0*math.pi), 1.1 * math.sin(i/32.0*math.pi),0.1)\r
-                               glColor4ub(  0,128,0,255)\r
-                               glVertex3f(1.3 * math.cos(i/32.0*math.pi), 1.3 * math.sin(i/32.0*math.pi),0.1)\r
-                       glEnd()\r
-                       glBegin(GL_TRIANGLES)\r
-                       glColor4ub(0,0,0,192)\r
-                       glVertex3f(1, 0.1,0.15)\r
-                       glVertex3f(1,-0.1,0.15)\r
-                       glVertex3f(1.4,0,0.15)\r
-                       glEnd()\r
-                       glEnable(GL_CULL_FACE)\r
-               \r
-               glPopMatrix()\r
-\r
-               opengl.DrawMachine(machineSize)\r
-               \r
-               glFlush()\r
-       \r
-       def drawModel(self, obj):\r
-               multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))\r
-               multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))\r
-               modelScale = profile.getProfileSettingFloat('model_scale')\r
-               modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale\r
-               glPushMatrix()\r
-               glRotate(self.tempRotate, 0, 0, 1)\r
-               glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)\r
-               for mx in xrange(0, multiX):\r
-                       for my in xrange(0, multiY):\r
-                               glPushMatrix()\r
-                               glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)\r
-                               glScalef(modelScale, modelScale, modelScale)\r
-                               glCallList(obj.displayList)\r
-                               glPopMatrix()\r
-               glPopMatrix()\r
-\r
+from __future__ import division
+
+import sys, math, threading, re, time, os
+import numpy
+
+from wx import glcanvas
+import wx
+try:
+       import OpenGL
+       OpenGL.ERROR_CHECKING = False
+       from OpenGL.GLU import *
+       from OpenGL.GL import *
+       hasOpenGLlibs = True
+except:
+       print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
+       hasOpenGLlibs = False
+
+from gui import opengl
+from gui import toolbarUtil
+
+from util import profile
+from util import gcodeInterpreter
+from util import meshLoader
+from util import util3d
+from util import sliceRun
+
+class previewObject():
+       def __init__(self):
+               self.mesh = None
+               self.filename = None
+               self.displayList = None
+               self.dirty = False
+
+class previewPanel(wx.Panel):
+       def __init__(self, parent):
+               super(previewPanel, self).__init__(parent,-1)
+               
+               self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
+               self.SetMinSize((440,320))
+               
+               self.objectList = []
+               self.errorList = []
+               self.gcode = None
+               self.objectsMinV = None
+               self.objectsMaxV = None
+               self.objectsBounderyCircleSize = None
+               self.loadThread = None
+               self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height'))
+               self.machineCenter = util3d.Vector3(self.machineSize.x / 2, self.machineSize.y / 2, 0)
+
+               self.glCanvas = PreviewGLCanvas(self)
+               #Create the popup window
+               self.warningPopup = wx.PopupWindow(self, flags=wx.BORDER_SIMPLE)
+               self.warningPopup.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_INFOBK))
+               self.warningPopup.text = wx.StaticText(self.warningPopup, -1, 'Reset scale, rotation and mirror?')
+               self.warningPopup.yesButton = wx.Button(self.warningPopup, -1, 'yes', style=wx.BU_EXACTFIT)
+               self.warningPopup.noButton = wx.Button(self.warningPopup, -1, 'no', style=wx.BU_EXACTFIT)
+               self.warningPopup.sizer = wx.BoxSizer(wx.HORIZONTAL)
+               self.warningPopup.SetSizer(self.warningPopup.sizer)
+               self.warningPopup.sizer.Add(self.warningPopup.text, 1, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=1)
+               self.warningPopup.sizer.Add(self.warningPopup.yesButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
+               self.warningPopup.sizer.Add(self.warningPopup.noButton, 0, flag=wx.EXPAND|wx.ALL, border=1)
+               self.warningPopup.Fit()
+               self.warningPopup.Layout()
+               self.warningPopup.timer = wx.Timer(self)
+               self.Bind(wx.EVT_TIMER, self.OnHideWarning, self.warningPopup.timer)
+               
+               self.Bind(wx.EVT_BUTTON, self.OnWarningPopup, self.warningPopup.yesButton)
+               self.Bind(wx.EVT_BUTTON, self.OnHideWarning, self.warningPopup.noButton)
+               parent.Bind(wx.EVT_MOVE, self.OnMove)
+               parent.Bind(wx.EVT_SIZE, self.OnMove)
+               
+               self.toolbar = toolbarUtil.Toolbar(self)
+
+               group = []
+               toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick)
+               toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick)
+               self.toolbar.AddSeparator()
+
+               self.showBorderButton = toolbarUtil.ToggleButton(self.toolbar, '', 'view-border-on.png', 'view-border-off.png', 'Show model borders', callback=self.OnViewChange)
+               self.showSteepOverhang = toolbarUtil.ToggleButton(self.toolbar, '', 'steepOverhang-on.png', 'steepOverhang-off.png', 'Show steep overhang', callback=self.OnViewChange)
+               self.toolbar.AddSeparator()
+
+               group = []
+               self.normalViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Normal model view', callback=self.OnViewChange)
+               self.transparentViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-transparent-on.png', 'view-transparent-off.png', 'Transparent model view', callback=self.OnViewChange)
+               self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange)
+               self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange)
+               self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange)
+               self.toolbar.AddSeparator()
+
+               self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
+               self.toolbar.AddControl(self.layerSpin)
+               self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
+               self.toolbar.AddSeparator()
+               self.toolbarInfo = wx.TextCtrl(self.toolbar, -1, '', style=wx.TE_READONLY)
+               self.toolbar.AddControl(self.toolbarInfo)
+
+               self.toolbar2 = toolbarUtil.Toolbar(self)
+
+               # Mirror
+               self.mirrorX = toolbarUtil.ToggleButton(self.toolbar2, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.returnToModelViewAndUpdateModel)
+               self.mirrorY = toolbarUtil.ToggleButton(self.toolbar2, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.returnToModelViewAndUpdateModel)
+               self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar2, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.returnToModelViewAndUpdateModel)
+               self.toolbar2.AddSeparator()
+
+               # Swap
+               self.swapXZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.returnToModelViewAndUpdateModel)
+               self.swapYZ = toolbarUtil.ToggleButton(self.toolbar2, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.returnToModelViewAndUpdateModel)
+               self.toolbar2.AddSeparator()
+
+               # Scale
+               self.scaleReset = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleReset, 'object-scale.png', 'Reset model scale')
+               self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
+               self.toolbar2.AddControl(self.scale)
+               self.scale.Bind(wx.EVT_TEXT, self.OnScale)
+               self.scaleMax = toolbarUtil.NormalButton(self.toolbar2, self.OnScaleMax, 'object-max-size.png', 'Scale object to fit machine size')
+
+               self.toolbar2.AddSeparator()
+
+               # Multiply
+               #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis')
+               #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis')
+               #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis')
+               #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis')
+               #self.toolbar2.AddSeparator()
+
+               # Rotate
+               self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation')
+               self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)
+               self.rotate.SetRange(0, 360)
+               self.rotate.Bind(wx.EVT_TEXT, self.OnRotate)
+               self.toolbar2.AddControl(self.rotate)
+
+               self.toolbar2.Realize()
+               self.OnViewChange()
+               
+               sizer = wx.BoxSizer(wx.VERTICAL)
+               sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
+               sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
+               sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
+               self.SetSizer(sizer)
+       
+       def returnToModelViewAndUpdateModel(self):
+               if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
+                       self.setViewMode('Normal')
+               self.updateModelTransform()
+       
+       def OnMove(self, e = None):
+               if e != None:
+                       e.Skip()
+               x, y = self.glCanvas.ClientToScreenXY(0, 0)
+               sx, sy = self.glCanvas.GetClientSizeTuple()
+               self.warningPopup.SetPosition((x, y+sy-self.warningPopup.GetSize().height))
+       
+       def OnMulXAddClick(self, e):
+               profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
+               self.glCanvas.Refresh()
+
+       def OnMulXSubClick(self, e):
+               profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
+               self.glCanvas.Refresh()
+
+       def OnMulYAddClick(self, e):
+               profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
+               self.glCanvas.Refresh()
+
+       def OnMulYSubClick(self, e):
+               profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
+               self.glCanvas.Refresh()
+
+       def OnScaleReset(self, e):
+               self.scale.SetValue('1.0')
+               self.OnScale(None)
+
+       def OnScale(self, e):
+               scale = 1.0
+               if self.scale.GetValue() != '':
+                       scale = self.scale.GetValue()
+               profile.putProfileSetting('model_scale', scale)
+               if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
+                       self.setViewMode('Normal')
+               self.glCanvas.Refresh()
+
+               if self.objectsMaxV != None:
+                       size = (self.objectsMaxV - self.objectsMinV) * float(scale)
+                       self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
+
+       def OnScaleMax(self, e = None, onlyScaleDown = False):
+               if self.objectsMinV == None:
+                       return
+               vMin = self.objectsMinV
+               vMax = self.objectsMaxV
+               skirtSize = 3
+               if profile.getProfileSettingFloat('skirt_line_count') > 0:
+                       skirtSize = 3 + profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
+               scaleX1 = (self.machineSize.x - self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
+               scaleY1 = (self.machineSize.y - self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
+               scaleX2 = (self.machineCenter.x - skirtSize) / ((vMax[0] - vMin[0]) / 2)
+               scaleY2 = (self.machineCenter.y - skirtSize) / ((vMax[1] - vMin[1]) / 2)
+               scaleZ = self.machineSize.z / (vMax[2] - vMin[2])
+               scale = min(scaleX1, scaleY1, scaleX2, scaleY2, scaleZ)
+               if scale > 1.0 and onlyScaleDown:
+                       return
+               self.scale.SetValue(str(scale))
+               profile.putProfileSetting('model_scale', self.scale.GetValue())
+               if self.glCanvas.viewMode == 'GCode' or self.glCanvas.viewMode == 'Mixed':
+                       self.setViewMode('Normal')
+               self.glCanvas.Refresh()
+
+       def OnRotateReset(self, e):
+               self.rotate.SetValue(0)
+               self.OnRotate(None)
+
+       def OnRotate(self, e):
+               profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
+               self.returnToModelViewAndUpdateModel()
+
+       def On3DClick(self):
+               self.glCanvas.yaw = 30
+               self.glCanvas.pitch = 60
+               self.glCanvas.zoom = 300
+               self.glCanvas.view3D = True
+               self.glCanvas.Refresh()
+
+       def OnTopClick(self):
+               self.glCanvas.view3D = False
+               self.glCanvas.zoom = 100
+               self.glCanvas.offsetX = 0
+               self.glCanvas.offsetY = 0
+               self.glCanvas.Refresh()
+
+       def OnLayerNrChange(self, e):
+               self.glCanvas.Refresh()
+       
+       def setViewMode(self, mode):
+               if mode == "Normal":
+                       self.normalViewButton.SetValue(True)
+               if mode == "GCode":
+                       self.gcodeViewButton.SetValue(True)
+               self.glCanvas.viewMode = mode
+               wx.CallAfter(self.glCanvas.Refresh)
+       
+       def loadModelFiles(self, filelist, showWarning = False):
+               while len(filelist) > len(self.objectList):
+                       self.objectList.append(previewObject())
+               for idx in xrange(len(filelist), len(self.objectList)):
+                       self.objectList[idx].mesh = None
+                       self.objectList[idx].filename = None
+               for idx in xrange(0, len(filelist)):
+                       obj = self.objectList[idx]
+                       if obj.filename != filelist[idx]:
+                               obj.fileTime = None
+                               self.gcodeFileTime = None
+                               self.logFileTime = None
+                       obj.filename = filelist[idx]
+               
+               self.gcodeFilename = sliceRun.getExportFilename(filelist[0])
+               #Do the STL file loading in a background thread so we don't block the UI.
+               if self.loadThread != None and self.loadThread.isAlive():
+                       self.loadThread.join()
+               self.loadThread = threading.Thread(target=self.doFileLoadThread)
+               self.loadThread.daemon = True
+               self.loadThread.start()
+               
+               if showWarning:
+                       if profile.getProfileSettingFloat('model_scale') != 1.0 or profile.getProfileSettingFloat('model_rotate_base') != 0 or profile.getProfileSetting('flip_x') != 'False' or profile.getProfileSetting('flip_y') != 'False' or profile.getProfileSetting('flip_z') != 'False' or profile.getProfileSetting('swap_xz') != 'False' or profile.getProfileSetting('swap_yz') != 'False' or len(profile.getPluginConfig()) > 0:
+                               self.ShowWarningPopup('Reset scale, rotation, mirror and plugins?', self.OnResetAll)
+       
+       def loadReModelFiles(self, filelist):
+               #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
+               for idx in xrange(0, len(filelist)):
+                       if self.objectList[idx].filename != filelist[idx]:
+                               return False
+               self.loadModelFiles(filelist)
+               return True
+       
+       def doFileLoadThread(self):
+               for obj in self.objectList:
+                       if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime:
+                               obj.ileTime = os.stat(obj.filename).st_mtime
+                               mesh = meshLoader.loadMesh(obj.filename)
+                               obj.dirty = False
+                               obj.mesh = mesh
+                               self.updateModelTransform()
+                               self.OnScaleMax(None, True)
+                               scale = profile.getProfileSettingFloat('model_scale')
+                               size = (self.objectsMaxV - self.objectsMinV) * scale
+                               self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
+                               self.glCanvas.zoom = numpy.max(size) * 2.5
+                               self.errorList = []
+                               wx.CallAfter(self.updateToolbar)
+                               wx.CallAfter(self.glCanvas.Refresh)
+               
+               if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
+                       self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
+                       gcode = gcodeInterpreter.gcode()
+                       gcode.progressCallback = self.loadProgress
+                       gcode.load(self.gcodeFilename)
+                       self.gcodeDirty = False
+                       self.gcode = gcode
+                       self.gcodeDirty = True
+
+                       errorList = []
+                       for line in open(self.gcodeFilename, "rt"):
+                               res = re.search(';Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line)
+                               if res != None:
+                                       v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4)))
+                                       v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7)))
+                                       errorList.append([v1, v2])
+                       self.errorList = errorList
+
+                       wx.CallAfter(self.updateToolbar)
+                       wx.CallAfter(self.glCanvas.Refresh)
+               elif not os.path.isfile(self.gcodeFilename):
+                       self.gcode = None
+       
+       def loadProgress(self, progress):
+               pass
+
+       def OnResetAll(self, e = None):
+               profile.putProfileSetting('model_scale', '1.0')
+               profile.putProfileSetting('model_rotate_base', '0')
+               profile.putProfileSetting('flip_x', 'False')
+               profile.putProfileSetting('flip_y', 'False')
+               profile.putProfileSetting('flip_z', 'False')
+               profile.putProfileSetting('swap_xz', 'False')
+               profile.putProfileSetting('swap_yz', 'False')
+               profile.setPluginConfig([])
+               self.GetParent().updateProfileToControls()
+
+       def ShowWarningPopup(self, text, callback = None):
+               self.warningPopup.text.SetLabel(text)
+               self.warningPopup.callback = callback
+               if callback == None:
+                       self.warningPopup.yesButton.Show(False)
+                       self.warningPopup.noButton.SetLabel('ok')
+               else:
+                       self.warningPopup.yesButton.Show(True)
+                       self.warningPopup.noButton.SetLabel('no')
+               self.warningPopup.Fit()
+               self.warningPopup.Layout()
+               self.OnMove()
+               self.warningPopup.Show(True)
+               self.warningPopup.timer.Start(5000)
+       
+       def OnWarningPopup(self, e):
+               self.warningPopup.Show(False)
+               self.warningPopup.timer.Stop()
+               self.warningPopup.callback()
+
+       def OnHideWarning(self, e):
+               self.warningPopup.Show(False)
+               self.warningPopup.timer.Stop()
+
+       def updateToolbar(self):
+               self.gcodeViewButton.Show(self.gcode != None)
+               self.mixedViewButton.Show(self.gcode != None)
+               self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed")
+               if self.gcode != None:
+                       self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1)
+               self.toolbar.Realize()
+               self.Update()
+       
+       def OnViewChange(self):
+               if self.normalViewButton.GetValue():
+                       self.glCanvas.viewMode = "Normal"
+               elif self.transparentViewButton.GetValue():
+                       self.glCanvas.viewMode = "Transparent"
+               elif self.xrayViewButton.GetValue():
+                       self.glCanvas.viewMode = "X-Ray"
+               elif self.gcodeViewButton.GetValue():
+                       self.glCanvas.viewMode = "GCode"
+               elif self.mixedViewButton.GetValue():
+                       self.glCanvas.viewMode = "Mixed"
+               self.glCanvas.drawBorders = self.showBorderButton.GetValue()
+               self.glCanvas.drawSteepOverhang = self.showSteepOverhang.GetValue()
+               self.updateToolbar()
+               self.glCanvas.Refresh()
+       
+       def updateModelTransform(self, f=0):
+               if len(self.objectList) < 1 or self.objectList[0].mesh == None:
+                       return
+               
+               rotate = profile.getProfileSettingFloat('model_rotate_base')
+               mirrorX = profile.getProfileSetting('flip_x') == 'True'
+               mirrorY = profile.getProfileSetting('flip_y') == 'True'
+               mirrorZ = profile.getProfileSetting('flip_z') == 'True'
+               swapXZ = profile.getProfileSetting('swap_xz') == 'True'
+               swapYZ = profile.getProfileSetting('swap_yz') == 'True'
+
+               for obj in self.objectList:
+                       if obj.mesh == None:
+                               continue
+                       obj.mesh.setRotateMirror(rotate, mirrorX, mirrorY, mirrorZ, swapXZ, swapYZ)
+               
+               minV = self.objectList[0].mesh.getMinimum()
+               maxV = self.objectList[0].mesh.getMaximum()
+               objectsBounderyCircleSize = self.objectList[0].mesh.bounderyCircleSize
+               for obj in self.objectList:
+                       if obj.mesh == None:
+                               continue
+
+                       obj.mesh.getMinimumZ()
+                       minV = numpy.minimum(minV, obj.mesh.getMinimum())
+                       maxV = numpy.maximum(maxV, obj.mesh.getMaximum())
+                       objectsBounderyCircleSize = max(objectsBounderyCircleSize, obj.mesh.bounderyCircleSize)
+
+               self.objectsMaxV = maxV
+               self.objectsMinV = minV
+               self.objectsBounderyCircleSize = objectsBounderyCircleSize
+               for obj in self.objectList:
+                       if obj.mesh == None:
+                               continue
+
+                       obj.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minV[2]])
+                       #for v in obj.mesh.vertexes:
+                       #       v[2] -= minV[2]
+                       #       v[0] -= minV[0] + (maxV[0] - minV[0]) / 2
+                       #       v[1] -= minV[1] + (maxV[1] - minV[1]) / 2
+                       obj.mesh.getMinimumZ()
+                       obj.dirty = True
+
+               scale = profile.getProfileSettingFloat('model_scale')
+               size = (self.objectsMaxV - self.objectsMinV) * scale
+               self.toolbarInfo.SetValue('%0.1f %0.1f %0.1f' % (size[0], size[1], size[2]))
+
+               self.glCanvas.Refresh()
+       
+       def updateProfileToControls(self):
+               self.scale.SetValue(profile.getProfileSetting('model_scale'))
+               self.rotate.SetValue(profile.getProfileSettingFloat('model_rotate_base'))
+               self.mirrorX.SetValue(profile.getProfileSetting('flip_x') == 'True')
+               self.mirrorY.SetValue(profile.getProfileSetting('flip_y') == 'True')
+               self.mirrorZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
+               self.swapXZ.SetValue(profile.getProfileSetting('swap_xz') == 'True')
+               self.swapYZ.SetValue(profile.getProfileSetting('swap_yz') == 'True')
+               self.updateModelTransform()
+               self.glCanvas.updateProfileToControls()
+
+class PreviewGLCanvas(glcanvas.GLCanvas):
+       def __init__(self, parent):
+               attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
+               glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
+               self.parent = parent
+               self.context = glcanvas.GLContext(self)
+               wx.EVT_PAINT(self, self.OnPaint)
+               wx.EVT_SIZE(self, self.OnSize)
+               wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
+               wx.EVT_MOTION(self, self.OnMouseMotion)
+               wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
+               self.yaw = 30
+               self.pitch = 60
+               self.zoom = 300
+               self.offsetX = 0
+               self.offsetY = 0
+               self.view3D = True
+               self.gcodeDisplayList = None
+               self.gcodeDisplayListMade = None
+               self.gcodeDisplayListCount = 0
+               self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]]
+               self.oldX = 0
+               self.oldY = 0
+               self.dragType = ''
+               self.tempRotate = 0
+       
+       def updateProfileToControls(self):
+               self.objColor[0] = profile.getPreferenceColour('model_colour')
+               self.objColor[1] = profile.getPreferenceColour('model_colour2')
+               self.objColor[2] = profile.getPreferenceColour('model_colour3')
+               self.objColor[3] = profile.getPreferenceColour('model_colour4')
+
+       def OnMouseMotion(self,e):
+               cursorXY = 100000
+               radius = 0
+               if self.parent.objectsMaxV != None:
+                       radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
+                       
+                       p0 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 0, self.modelMatrix, self.projMatrix, self.viewport))
+                       p1 = numpy.array(gluUnProject(e.GetX(), self.viewport[1] + self.viewport[3] - e.GetY(), 1, self.modelMatrix, self.projMatrix, self.viewport))
+                       cursorZ0 = p0 - (p1 - p0) * (p0[2] / (p1[2] - p0[2]))
+                       cursorXY = math.sqrt((cursorZ0[0] * cursorZ0[0]) + (cursorZ0[1] * cursorZ0[1]))
+                       if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
+                               self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
+                       else:
+                               self.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+
+               if e.Dragging() and e.LeftIsDown():
+                       if self.dragType == '':
+                               #Define the drag type depending on the cursor position.
+                               if cursorXY >= radius * 1.1 and cursorXY <= radius * 1.3:
+                                       self.dragType = 'modelRotate'
+                                       self.dragStart = math.atan2(cursorZ0[0], cursorZ0[1])
+                               else:
+                                       self.dragType = 'viewRotate'
+                               
+                       if self.dragType == 'viewRotate':
+                               if self.view3D:
+                                       self.yaw += e.GetX() - self.oldX
+                                       self.pitch -= e.GetY() - self.oldY
+                                       if self.pitch > 170:
+                                               self.pitch = 170
+                                       if self.pitch < 10:
+                                               self.pitch = 10
+                               else:
+                                       self.offsetX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
+                                       self.offsetY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
+                       elif self.dragType == 'modelRotate':
+                               angle = math.atan2(cursorZ0[0], cursorZ0[1])
+                               diff = self.dragStart - angle
+                               self.tempRotate = diff * 180 / math.pi
+                               rot = profile.getProfileSettingFloat('model_rotate_base')
+                               self.tempRotate = round((self.tempRotate + rot) / 15) * 15 - rot
+                       #Workaround for buggy ATI cards.
+                       size = self.GetSizeTuple()
+                       self.SetSize((size[0]+1, size[1]))
+                       self.SetSize((size[0], size[1]))
+                       self.Refresh()
+               else:
+                       if self.tempRotate != 0:
+                               newRotation = profile.getProfileSettingFloat('model_rotate_base') + self.tempRotate
+                               while newRotation >= 360:
+                                       newRotation -= 360
+                               while newRotation < 0:
+                                       newRotation += 360
+                               profile.putProfileSetting('model_rotate_base', newRotation)
+                               self.parent.rotate.SetValue(newRotation)
+                               self.parent.updateModelTransform()
+                               self.tempRotate = 0
+                               
+                       self.dragType = ''
+               if e.Dragging() and e.RightIsDown():
+                       self.zoom += e.GetY() - self.oldY
+                       if self.zoom < 1:
+                               self.zoom = 1
+                       if self.zoom > 500:
+                               self.zoom = 500
+                       self.Refresh()
+               self.oldX = e.GetX()
+               self.oldY = e.GetY()
+
+               #self.Refresh()
+               
+       
+       def OnMouseWheel(self,e):
+               self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
+               if self.zoom < 1.0:
+                       self.zoom = 1.0
+               if self.zoom > 500:
+                       self.zoom = 500
+               self.Refresh()
+       
+       def OnEraseBackground(self,event):
+               #Workaround for windows background redraw flicker.
+               pass
+       
+       def OnSize(self,e):
+               self.Refresh()
+
+       def OnPaint(self,e):
+               dc = wx.PaintDC(self)
+               if not hasOpenGLlibs:
+                       dc.Clear()
+                       dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
+                       return
+               self.SetCurrent(self.context)
+               opengl.InitGL(self, self.view3D, self.zoom)
+               if self.view3D:
+                       glTranslate(0,0,-self.zoom)
+                       glRotate(-self.pitch, 1,0,0)
+                       glRotate(self.yaw, 0,0,1)
+                       if self.viewMode == "GCode" or self.viewMode == "Mixed":
+                               if self.parent.gcode != None and len(self.parent.gcode.layerList) > self.parent.layerSpin.GetValue() and len(self.parent.gcode.layerList[self.parent.layerSpin.GetValue()]) > 0:
+                                       glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z)
+                       else:
+                               if self.parent.objectsMaxV != None:
+                                       glTranslate(0,0,-(self.parent.objectsMaxV[2]-self.parent.objectsMinV[2]) * profile.getProfileSettingFloat('model_scale') / 2)
+               else:
+                       glTranslate(self.offsetX, self.offsetY, 0)
+
+               self.viewport = glGetIntegerv(GL_VIEWPORT);
+               self.modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX);
+               self.projMatrix = glGetDoublev(GL_PROJECTION_MATRIX);
+
+               glTranslate(-self.parent.machineCenter.x, -self.parent.machineCenter.y, 0)
+
+               self.OnDraw()
+               self.SwapBuffers()
+
+       def OnDraw(self):
+               machineSize = self.parent.machineSize
+
+               if self.parent.gcode != None and self.parent.gcodeDirty:
+                       if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None:
+                               if self.gcodeDisplayList != None:
+                                       glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount)
+                               self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList));
+                               self.gcodeDisplayListCount = len(self.parent.gcode.layerList)
+                       self.parent.gcodeDirty = False
+                       self.gcodeDisplayListMade = 0
+               
+               if self.parent.gcode != None and self.gcodeDisplayListMade < len(self.parent.gcode.layerList):
+                       glNewList(self.gcodeDisplayList + self.gcodeDisplayListMade, GL_COMPILE)
+                       opengl.DrawGCodeLayer(self.parent.gcode.layerList[self.gcodeDisplayListMade])
+                       glEndList()
+                       self.gcodeDisplayListMade += 1
+                       wx.CallAfter(self.Refresh)
+               
+               glPushMatrix()
+               glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
+               for obj in self.parent.objectList:
+                       if obj.mesh == None:
+                               continue
+                       if obj.displayList == None:
+                               obj.displayList = glGenLists(1)
+                               obj.steepDisplayList = glGenLists(1)
+                       if obj.dirty:
+                               obj.dirty = False
+                               glNewList(obj.displayList, GL_COMPILE)
+                               opengl.DrawMesh(obj.mesh)
+                               glEndList()
+                               glNewList(obj.steepDisplayList, GL_COMPILE)
+                               opengl.DrawMeshSteep(obj.mesh, 60)
+                               glEndList()
+                       
+                       if self.viewMode == "Mixed":
+                               glDisable(GL_BLEND)
+                               glColor3f(0.0,0.0,0.0)
+                               self.drawModel(obj)
+                               glColor3f(1.0,1.0,1.0)
+                               glClear(GL_DEPTH_BUFFER_BIT)
+               
+               glPopMatrix()
+               
+               if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"):
+                       glEnable(GL_COLOR_MATERIAL)
+                       glEnable(GL_LIGHTING)
+                       drawUpToLayer = min(self.gcodeDisplayListMade, self.parent.layerSpin.GetValue() + 1)
+                       starttime = time.time()
+                       for i in xrange(drawUpToLayer - 1, -1, -1):
+                               c = 1.0
+                               if i < self.parent.layerSpin.GetValue():
+                                       c = 0.9 - (drawUpToLayer - i) * 0.1
+                                       if c < 0.4:
+                                               c = (0.4 + c) / 2
+                                       if c < 0.1:
+                                               c = 0.1
+                               glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0])
+                               glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c])
+                               glCallList(self.gcodeDisplayList + i)
+                               if time.time() - starttime > 0.1:
+                                       break
+
+                       glDisable(GL_LIGHTING)
+                       glDisable(GL_COLOR_MATERIAL)
+                       glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]);
+                       glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]);
+
+               glColor3f(1.0,1.0,1.0)
+               glPushMatrix()
+               glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
+               for obj in self.parent.objectList:
+                       if obj.mesh == None:
+                               continue
+                       
+                       if self.viewMode == "Transparent" or self.viewMode == "Mixed":
+                               glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)]))
+                               glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)]))
+                               #If we want transparent, then first render a solid black model to remove the printer size lines.
+                               if self.viewMode != "Mixed":
+                                       glDisable(GL_BLEND)
+                                       glColor3f(0.0,0.0,0.0)
+                                       self.drawModel(obj)
+                                       glColor3f(1.0,1.0,1.0)
+                               #After the black model is rendered, render the model again but now with lighting and no depth testing.
+                               glDisable(GL_DEPTH_TEST)
+                               glEnable(GL_LIGHTING)
+                               glEnable(GL_BLEND)
+                               glBlendFunc(GL_ONE, GL_ONE)
+                               glEnable(GL_LIGHTING)
+                               self.drawModel(obj)
+                               glEnable(GL_DEPTH_TEST)
+                       elif self.viewMode == "X-Ray":
+                               glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
+                               glDisable(GL_LIGHTING)
+                               glDisable(GL_DEPTH_TEST)
+                               glEnable(GL_STENCIL_TEST)
+                               glStencilFunc(GL_ALWAYS, 1, 1)
+                               glStencilOp(GL_INCR, GL_INCR, GL_INCR)
+                               self.drawModel(obj)
+                               glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
+                               
+                               glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
+                               glStencilFunc(GL_EQUAL, 0, 1)
+                               glColor(1, 1, 1)
+                               self.drawModel(obj)
+                               glStencilFunc(GL_EQUAL, 1, 1)
+                               glColor(1, 0, 0)
+                               self.drawModel(obj)
+
+                               glPushMatrix()
+                               glLoadIdentity()
+                               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,-1)
+                                       glVertex3f( 1000,-1000,-1)
+                                       glVertex3f( 1000, 1000,-1)
+                                       glVertex3f(-1000, 1000,-1)
+                                       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,-1)
+                                       glVertex3f( 1000,-1000,-1)
+                                       glVertex3f( 1000, 1000,-1)
+                                       glVertex3f(-1000, 1000,-1)
+                                       glEnd()
+                               glPopMatrix()
+
+                               glDisable(GL_STENCIL_TEST)
+                               glEnable(GL_DEPTH_TEST)
+                               
+                               #Fix the depth buffer
+                               glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)
+                               self.drawModel(obj)
+                               glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
+                       elif self.viewMode == "Normal":
+                               glLightfv(GL_LIGHT0, GL_DIFFUSE, self.objColor[self.parent.objectList.index(obj)])
+                               glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x * 0.4, self.objColor[self.parent.objectList.index(obj)]))
+                               glEnable(GL_LIGHTING)
+                               self.drawModel(obj)
+
+                       if self.drawBorders and (self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray"):
+                               glEnable(GL_DEPTH_TEST)
+                               glDisable(GL_LIGHTING)
+                               glColor3f(1,1,1)
+                               glPushMatrix()
+                               modelScale = profile.getProfileSettingFloat('model_scale')
+                               glScalef(modelScale, modelScale, modelScale)
+                               opengl.DrawMeshOutline(obj.mesh)
+                               glPopMatrix()
+                       
+                       if self.drawSteepOverhang:
+                               glDisable(GL_LIGHTING)
+                               glColor3f(1,1,1)
+                               glPushMatrix()
+                               modelScale = profile.getProfileSettingFloat('model_scale')
+                               glScalef(modelScale, modelScale, modelScale)
+                               glCallList(obj.steepDisplayList)
+                               glPopMatrix()
+               
+               glPopMatrix()   
+               if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray":
+                       glDisable(GL_LIGHTING)
+                       glDisable(GL_DEPTH_TEST)
+                       glDisable(GL_BLEND)
+                       glColor3f(1,0,0)
+                       glBegin(GL_LINES)
+                       for err in self.parent.errorList:
+                               glVertex3f(err[0].x, err[0].y, err[0].z)
+                               glVertex3f(err[1].x, err[1].y, err[1].z)
+                       glEnd()
+                       glEnable(GL_DEPTH_TEST)
+
+               glPushMatrix()
+               glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
+               
+               #Draw the rotate circle
+               if self.parent.objectsMaxV != None:
+                       glDisable(GL_LIGHTING)
+                       glDisable(GL_CULL_FACE)
+                       glEnable(GL_BLEND)
+                       glRotate(self.tempRotate + profile.getProfileSettingFloat('model_rotate_base'), 0, 0, 1)
+                       radius = self.parent.objectsBounderyCircleSize * profile.getProfileSettingFloat('model_scale')
+                       glScalef(radius, radius, 1)
+                       glBegin(GL_TRIANGLE_STRIP)
+                       for i in xrange(0, 64+1):
+                               f = i if i < 64/2 else 64 - i
+                               glColor4ub(255,int(f*255/(64/2)),0,255)
+                               glVertex3f(1.1 * math.cos(i/32.0*math.pi), 1.1 * math.sin(i/32.0*math.pi),0.1)
+                               glColor4ub(  0,128,0,255)
+                               glVertex3f(1.3 * math.cos(i/32.0*math.pi), 1.3 * math.sin(i/32.0*math.pi),0.1)
+                       glEnd()
+                       glBegin(GL_TRIANGLES)
+                       glColor4ub(0,0,0,192)
+                       glVertex3f(1, 0.1,0.15)
+                       glVertex3f(1,-0.1,0.15)
+                       glVertex3f(1.4,0,0.15)
+                       glEnd()
+                       glEnable(GL_CULL_FACE)
+               
+               glPopMatrix()
+
+               opengl.DrawMachine(machineSize)
+               
+               glFlush()
+       
+       def drawModel(self, obj):
+               multiX = 1 #int(profile.getProfileSetting('model_multiply_x'))
+               multiY = 1 #int(profile.getProfileSetting('model_multiply_y'))
+               modelScale = profile.getProfileSettingFloat('model_scale')
+               modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale
+               glPushMatrix()
+               glRotate(self.tempRotate, 0, 0, 1)
+               glTranslate(-(modelSize[0]+10)*(multiX-1)/2,-(modelSize[1]+10)*(multiY-1)/2, 0)
+               for mx in xrange(0, multiX):
+                       for my in xrange(0, multiY):
+                               glPushMatrix()
+                               glTranslate((modelSize[0]+10)*mx,(modelSize[1]+10)*my, 0)
+                               glScalef(modelScale, modelScale, modelScale)
+                               glCallList(obj.displayList)
+                               glPopMatrix()
+               glPopMatrix()
+
index a6b4244ab7c76fc74f1333a4397c3bd26345c0f0..dc3a53b996bf6895d4d878be51517814bf9ed1a4 100644 (file)
-from __future__ import absolute_import\r
-import __init__\r
-\r
-import wx, os, platform, types, webbrowser, math, subprocess, threading, time, re, shutil\r
-import ConfigParser\r
-import numpy\r
-\r
-from wx import glcanvas\r
-try:\r
-       import OpenGL\r
-       OpenGL.ERROR_CHECKING = False\r
-       from OpenGL.GLU import *\r
-       from OpenGL.GL import *\r
-       hasOpenGLlibs = True\r
-except:\r
-       print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"\r
-       hasOpenGLlibs = False\r
-\r
-from gui import opengl\r
-from gui import toolbarUtil\r
-from gui import icon\r
-from gui import configBase\r
-from gui import printWindow\r
-from gui import dropTarget\r
-from gui import taskbar\r
-from util import validators\r
-from util import profile\r
-from util import util3d\r
-from util import meshLoader\r
-from util import stl\r
-from util import mesh\r
-from util import sliceRun\r
-from util import gcodeInterpreter\r
-from util import exporer\r
-\r
-class Action(object):\r
-       pass\r
-\r
-class ProjectObject(object):\r
-       def __init__(self, parent, filename):\r
-               super(ProjectObject, self).__init__()\r
-\r
-               self.mesh = meshLoader.loadMesh(filename)\r
-\r
-               self.parent = parent\r
-               self.filename = filename\r
-               self.scale = 1.0\r
-               self.rotate = 0.0\r
-               self.flipX = False\r
-               self.flipY = False\r
-               self.flipZ = False\r
-               self.swapXZ = False\r
-               self.swapYZ = False\r
-               self.extruder = 0\r
-               self.profile = None\r
-               \r
-               self.modelDisplayList = None\r
-               self.modelDirty = False\r
-\r
-               self.mesh.getMinimumZ()\r
-               \r
-               self.centerX = -self.getMinimum()[0] + 5\r
-               self.centerY = -self.getMinimum()[1] + 5\r
-               \r
-               self.updateModelTransform()\r
-\r
-               self.centerX = -self.getMinimum()[0] + 5\r
-               self.centerY = -self.getMinimum()[1] + 5\r
-\r
-       def isSameExceptForPosition(self, other):\r
-               if self.filename != other.filename:\r
-                       return False\r
-               if self.scale != other.scale:\r
-                       return False\r
-               if self.rotate != other.rotate:\r
-                       return False\r
-               if self.flipX != other.flipX:\r
-                       return False\r
-               if self.flipY != other.flipY:\r
-                       return False\r
-               if self.flipZ != other.flipZ:\r
-                       return False\r
-               if self.swapXZ != other.swapXZ:\r
-                       return False\r
-               if self.swapYZ != other.swapYZ:\r
-                       return False\r
-               if self.extruder != other.extruder:\r
-                       return False\r
-               if self.profile != other.profile:\r
-                       return False\r
-               return True\r
-\r
-       def updateModelTransform(self):\r
-               self.mesh.setRotateMirror(self.rotate, self.flipX, self.flipY, self.flipZ, self.swapXZ, self.swapYZ)\r
-               minZ = self.mesh.getMinimumZ()\r
-               minV = self.getMinimum()\r
-               maxV = self.getMaximum()\r
-               self.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minZ])\r
-               minZ = self.mesh.getMinimumZ()\r
-               self.modelDirty = True\r
-       \r
-       def getMinimum(self):\r
-               return self.mesh.getMinimum()\r
-       def getMaximum(self):\r
-               return self.mesh.getMaximum()\r
-       def getSize(self):\r
-               return self.mesh.getSize()\r
-       \r
-       def clone(self):\r
-               p = ProjectObject(self.parent, self.filename)\r
-\r
-               p.centerX = self.centerX + 5\r
-               p.centerY = self.centerY + 5\r
-               \r
-               p.filename = self.filename\r
-               p.scale = self.scale\r
-               p.rotate = self.rotate\r
-               p.flipX = self.flipX\r
-               p.flipY = self.flipY\r
-               p.flipZ = self.flipZ\r
-               p.swapXZ = self.swapXZ\r
-               p.swapYZ = self.swapYZ\r
-               p.extruder = self.extruder\r
-               p.profile = self.profile\r
-               \r
-               p.updateModelTransform()\r
-               \r
-               return p\r
-       \r
-       def clampXY(self):\r
-               if self.centerX < -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]:\r
-                       self.centerX = -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]\r
-               if self.centerY < -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]:\r
-                       self.centerY = -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]\r
-               if self.centerX > self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale:\r
-                       self.centerX = self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale\r
-               if self.centerY > self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale:\r
-                       self.centerY = self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale\r
-\r
-class projectPlanner(wx.Frame):\r
-       "Main user interface window"\r
-       def __init__(self):\r
-               super(projectPlanner, self).__init__(None, title='Cura - Project Planner')\r
-               \r
-               wx.EVT_CLOSE(self, self.OnClose)\r
-               self.panel = wx.Panel(self, -1)\r
-               self.SetSizer(wx.BoxSizer(wx.VERTICAL))\r
-               self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)\r
-               #self.SetIcon(icon.getMainIcon())\r
-               \r
-               self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))\r
-               \r
-               self.list = []\r
-               self.selection = None\r
-               self.printMode = 0\r
-               self.alwaysAutoPlace = True\r
-\r
-               self.machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])\r
-               self.headSizeMin = numpy.array([profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0])\r
-               self.headSizeMax = numpy.array([profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0])\r
-\r
-               self.extruderOffset = [\r
-                       numpy.array([0,0,0]),\r
-                       numpy.array([profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1'), 0]),\r
-                       numpy.array([profile.getPreferenceFloat('extruder_offset_x2'), profile.getPreferenceFloat('extruder_offset_y2'), 0]),\r
-                       numpy.array([profile.getPreferenceFloat('extruder_offset_x3'), profile.getPreferenceFloat('extruder_offset_y3'), 0])]\r
-\r
-               self.toolbar = toolbarUtil.Toolbar(self.panel)\r
-\r
-               toolbarUtil.NormalButton(self.toolbar, self.OnLoadProject, 'open.png', 'Open project')\r
-               toolbarUtil.NormalButton(self.toolbar, self.OnSaveProject, 'save.png', 'Save project')\r
-               self.toolbar.AddSeparator()\r
-               group = []\r
-               toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick).SetValue(self.alwaysAutoPlace)\r
-               toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick).SetValue(not self.alwaysAutoPlace)\r
-               self.toolbar.AddSeparator()\r
-               toolbarUtil.NormalButton(self.toolbar, self.OnPreferences, 'preferences.png', 'Project planner preferences')\r
-               self.toolbar.AddSeparator()\r
-               toolbarUtil.NormalButton(self.toolbar, self.OnCutMesh, 'cut-mesh.png', 'Cut a plate STL into multiple STL files, and add those files to the project.\nNote: Splitting up plates sometimes takes a few minutes.')\r
-               toolbarUtil.NormalButton(self.toolbar, self.OnSaveCombinedSTL, 'save-combination.png', 'Save all the combined STL files into a single STL file as a plate.')\r
-               self.toolbar.AddSeparator()\r
-               group = []\r
-               self.printOneAtATime = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Print one object at a time', callback=self.OnPrintTypeChange)\r
-               self.printAllAtOnce = toolbarUtil.RadioButton(self.toolbar, group, 'all-at-once-on.png', 'all-at-once-off.png', 'Print all the objects at once', callback=self.OnPrintTypeChange)\r
-               self.toolbar.AddSeparator()\r
-               toolbarUtil.NormalButton(self.toolbar, self.OnQuit, 'exit.png', 'Close project planner')\r
-               \r
-               self.toolbar.Realize()\r
-\r
-               self.toolbar2 = toolbarUtil.Toolbar(self.panel)\r
-\r
-               toolbarUtil.NormalButton(self.toolbar2, self.OnAddModel, 'object-add.png', 'Add model')\r
-               toolbarUtil.NormalButton(self.toolbar2, self.OnRemModel, 'object-remove.png', 'Remove model')\r
-               self.toolbar2.AddSeparator()\r
-               toolbarUtil.NormalButton(self.toolbar2, self.OnMoveUp, 'move-up.png', 'Move model up in print list')\r
-               toolbarUtil.NormalButton(self.toolbar2, self.OnMoveDown, 'move-down.png', 'Move model down in print list')\r
-               toolbarUtil.NormalButton(self.toolbar2, self.OnCopy, 'copy.png', 'Make a copy of the current selected object')\r
-               toolbarUtil.NormalButton(self.toolbar2, self.OnSetCustomProfile, 'set-profile.png', 'Set a custom profile to be used to prepare a specific object.')\r
-               self.toolbar2.AddSeparator()\r
-               if not self.alwaysAutoPlace:\r
-                       toolbarUtil.NormalButton(self.toolbar2, self.OnAutoPlace, 'autoplace.png', 'Automaticly organize the objects on the platform.')\r
-               toolbarUtil.NormalButton(self.toolbar2, self.OnSlice, 'slice.png', 'Prepare to project into a gcode file.')\r
-               self.toolbar2.Realize()\r
-\r
-               self.toolbar3 = toolbarUtil.Toolbar(self.panel)\r
-               self.mirrorX = toolbarUtil.ToggleButton(self.toolbar3, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.OnMirrorChange)\r
-               self.mirrorY = toolbarUtil.ToggleButton(self.toolbar3, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.OnMirrorChange)\r
-               self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar3, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.OnMirrorChange)\r
-               self.toolbar3.AddSeparator()\r
-\r
-               # Swap\r
-               self.swapXZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.OnMirrorChange)\r
-               self.swapYZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.OnMirrorChange)\r
-               self.toolbar3.Realize()\r
-               \r
-               sizer = wx.GridBagSizer(2,2)\r
-               self.panel.SetSizer(sizer)\r
-               self.preview = PreviewGLCanvas(self.panel, self)\r
-               self.listbox = wx.ListBox(self.panel, -1, choices=[])\r
-               self.addButton = wx.Button(self.panel, -1, "Add")\r
-               self.remButton = wx.Button(self.panel, -1, "Remove")\r
-               self.sliceButton = wx.Button(self.panel, -1, "Prepare")\r
-               if not self.alwaysAutoPlace:\r
-                       self.autoPlaceButton = wx.Button(self.panel, -1, "Auto Place")\r
-               \r
-               sizer.Add(self.toolbar, (0,0), span=(1,1), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)\r
-               sizer.Add(self.toolbar2, (0,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)\r
-               sizer.Add(self.preview, (1,0), span=(5,1), flag=wx.EXPAND)\r
-               sizer.Add(self.listbox, (1,1), span=(1,2), flag=wx.EXPAND)\r
-               sizer.Add(self.toolbar3, (2,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)\r
-               sizer.Add(self.addButton, (3,1), span=(1,1))\r
-               sizer.Add(self.remButton, (3,2), span=(1,1))\r
-               sizer.Add(self.sliceButton, (4,1), span=(1,1))\r
-               if not self.alwaysAutoPlace:\r
-                       sizer.Add(self.autoPlaceButton, (4,2), span=(1,1))\r
-               sizer.AddGrowableCol(0)\r
-               sizer.AddGrowableRow(1)\r
-               \r
-               self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)\r
-               self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)\r
-               self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)\r
-               if not self.alwaysAutoPlace:\r
-                       self.autoPlaceButton.Bind(wx.EVT_BUTTON, self.OnAutoPlace)\r
-               self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)\r
-\r
-               panel = wx.Panel(self.panel, -1)\r
-               sizer.Add(panel, (5,1), span=(1,2))\r
-               \r
-               sizer = wx.GridBagSizer(2,2)\r
-               panel.SetSizer(sizer)\r
-               \r
-               self.scaleCtrl = wx.TextCtrl(panel, -1, '')\r
-               self.rotateCtrl = wx.SpinCtrl(panel, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)\r
-               self.rotateCtrl.SetRange(0, 360)\r
-\r
-               sizer.Add(wx.StaticText(panel, -1, 'Scale'), (0,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
-               sizer.Add(self.scaleCtrl, (0,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
-               sizer.Add(wx.StaticText(panel, -1, 'Rotate'), (1,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
-               sizer.Add(self.rotateCtrl, (1,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
-\r
-               if int(profile.getPreference('extruder_amount')) > 1:\r
-                       self.extruderCtrl = wx.ComboBox(panel, -1, '1', choices=map(str, range(1, int(profile.getPreference('extruder_amount'))+1)), style=wx.CB_DROPDOWN|wx.CB_READONLY)\r
-                       sizer.Add(wx.StaticText(panel, -1, 'Extruder'), (2,0), flag=wx.ALIGN_CENTER_VERTICAL)\r
-                       sizer.Add(self.extruderCtrl, (2,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)\r
-                       self.extruderCtrl.Bind(wx.EVT_COMBOBOX, self.OnExtruderChange)\r
-\r
-               self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange)\r
-               self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange)\r
-\r
-               self.SetSize((800,600))\r
-\r
-       def OnClose(self, e):\r
-               self.Destroy()\r
-\r
-       def OnQuit(self, e):\r
-               self.Close()\r
-       \r
-       def OnPreferences(self, e):\r
-               prefDialog = preferencesDialog(self)\r
-               prefDialog.Centre()\r
-               prefDialog.Show(True)\r
-       \r
-       def OnCutMesh(self, e):\r
-               dlg=wx.FileDialog(self, "Open file to cut", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)\r
-               dlg.SetWildcard(meshLoader.wildcardFilter())\r
-               if dlg.ShowModal() == wx.ID_OK:\r
-                       filename = dlg.GetPath()\r
-                       model = meshLoader.loadMesh(filename)\r
-                       pd = wx.ProgressDialog('Splitting model.', 'Splitting model into multiple parts.', model.vertexCount, self, wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_SMOOTH)\r
-                       parts = model.splitToParts(pd.Update)\r
-                       for part in parts:\r
-                               partFilename = filename[:filename.rfind('.')] + "_part%d.stl" % (parts.index(part))\r
-                               stl.saveAsSTL(part, partFilename)\r
-                               item = ProjectObject(self, partFilename)\r
-                               self.list.append(item)\r
-                               self.selection = item\r
-                               self._updateListbox()\r
-                               self.OnListSelect(None)\r
-                       pd.Destroy()\r
-               self.preview.Refresh()\r
-               dlg.Destroy()\r
-       \r
-       def OnDropFiles(self, filenames):\r
-               for filename in filenames:\r
-                       item = ProjectObject(self, filename)\r
-                       profile.putPreference('lastFile', item.filename)\r
-                       self.list.append(item)\r
-                       self.selection = item\r
-                       self._updateListbox()\r
-               self.OnListSelect(None)\r
-               self.preview.Refresh()\r
-\r
-       def OnPrintTypeChange(self):\r
-               self.printMode = 0\r
-               if self.printAllAtOnce.GetValue():\r
-                       self.printMode = 1\r
-               if self.alwaysAutoPlace:\r
-                       self.OnAutoPlace(None)\r
-               self.preview.Refresh()\r
-       \r
-       def OnSaveCombinedSTL(self, e):\r
-               dlg=wx.FileDialog(self, "Save as STL", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
-               dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")\r
-               if dlg.ShowModal() == wx.ID_OK:\r
-                       self._saveCombinedSTL(dlg.GetPath())\r
-               dlg.Destroy()\r
-       \r
-       def _saveCombinedSTL(self, filename):\r
-               totalCount = 0\r
-               for item in self.list:\r
-                       totalCount += item.mesh.vertexCount\r
-               output = mesh.mesh()\r
-               output._prepareVertexCount(totalCount)\r
-               for item in self.list:\r
-                       offset = numpy.array([item.centerX, item.centerY, 0])\r
-                       for v in item.mesh.vertexes:\r
-                               v0 = v * item.scale + offset\r
-                               output.addVertex(v0[0], v0[1], v0[2])\r
-               stl.saveAsSTL(output, filename)\r
-       \r
-       def OnSaveProject(self, e):\r
-               dlg=wx.FileDialog(self, "Save project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
-               dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")\r
-               if dlg.ShowModal() == wx.ID_OK:\r
-                       cp = ConfigParser.ConfigParser()\r
-                       i = 0\r
-                       for item in self.list:\r
-                               section = 'model_%d' % (i)\r
-                               cp.add_section(section)\r
-                               cp.set(section, 'filename', item.filename.encode("utf-8"))\r
-                               cp.set(section, 'centerX', str(item.centerX))\r
-                               cp.set(section, 'centerY', str(item.centerY))\r
-                               cp.set(section, 'scale', str(item.scale))\r
-                               cp.set(section, 'rotate', str(item.rotate))\r
-                               cp.set(section, 'flipX', str(item.flipX))\r
-                               cp.set(section, 'flipY', str(item.flipY))\r
-                               cp.set(section, 'flipZ', str(item.flipZ))\r
-                               cp.set(section, 'swapXZ', str(item.swapXZ))\r
-                               cp.set(section, 'swapYZ', str(item.swapYZ))\r
-                               cp.set(section, 'extruder', str(item.extruder+1))\r
-                               if item.profile != None:\r
-                                       cp.set(section, 'profile', item.profile)\r
-                               i += 1\r
-                       cp.write(open(dlg.GetPath(), "w"))\r
-               dlg.Destroy()\r
-\r
-       def OnLoadProject(self, e):\r
-               dlg=wx.FileDialog(self, "Open project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)\r
-               dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")\r
-               if dlg.ShowModal() == wx.ID_OK:\r
-                       cp = ConfigParser.ConfigParser()\r
-                       cp.read(dlg.GetPath())\r
-                       self.list = []\r
-                       i = 0\r
-                       while cp.has_section('model_%d' % (i)):\r
-                               section = 'model_%d' % (i)\r
-                               \r
-                               item = ProjectObject(self, unicode(cp.get(section, 'filename'), "utf-8"))\r
-                               item.centerX = float(cp.get(section, 'centerX'))\r
-                               item.centerY = float(cp.get(section, 'centerY'))\r
-                               item.scale = float(cp.get(section, 'scale'))\r
-                               item.rotate = float(cp.get(section, 'rotate'))\r
-                               item.flipX = cp.get(section, 'flipX') == 'True'\r
-                               item.flipY = cp.get(section, 'flipY') == 'True'\r
-                               item.flipZ = cp.get(section, 'flipZ') == 'True'\r
-                               item.swapXZ = cp.get(section, 'swapXZ') == 'True'\r
-                               item.swapYZ = cp.get(section, 'swapYZ') == 'True'\r
-                               if cp.has_option(section, 'extruder'):\r
-                                       item.extuder = int(cp.get(section, 'extruder')) - 1\r
-                               if cp.has_option(section, 'profile'):\r
-                                       item.profile = cp.get(section, 'profile')\r
-                               item.updateModelTransform()\r
-                               i += 1\r
-                               \r
-                               self.list.append(item)\r
-\r
-                       self.selected = self.list[0]\r
-                       self._updateListbox()                   \r
-                       self.OnListSelect(None)\r
-                       self.preview.Refresh()\r
-\r
-               dlg.Destroy()\r
-\r
-       def On3DClick(self):\r
-               self.preview.yaw = 30\r
-               self.preview.pitch = 60\r
-               self.preview.zoom = 300\r
-               self.preview.view3D = True\r
-               self.preview.Refresh()\r
-\r
-       def OnTopClick(self):\r
-               self.preview.view3D = False\r
-               self.preview.zoom = self.machineSize[0] / 2 + 10\r
-               self.preview.offsetX = 0\r
-               self.preview.offsetY = 0\r
-               self.preview.Refresh()\r
-\r
-       def OnListSelect(self, e):\r
-               if self.listbox.GetSelection() == -1:\r
-                       return\r
-               self.selection = self.list[self.listbox.GetSelection()]\r
-               self.scaleCtrl.SetValue(str(self.selection.scale))\r
-               self.rotateCtrl.SetValue(int(self.selection.rotate))\r
-               if int(profile.getPreference('extruder_amount')) > 1:\r
-\r
-                       self.extruderCtrl.SetValue(str(self.selection.extruder+1))\r
-\r
-               self.mirrorX.SetValue(self.selection.flipX)\r
-               self.mirrorY.SetValue(self.selection.flipY)\r
-               self.mirrorZ.SetValue(self.selection.flipZ)\r
-               self.swapXZ.SetValue(self.selection.swapXZ)\r
-               self.swapYZ.SetValue(self.selection.swapYZ)\r
-               \r
-               self.preview.Refresh()\r
-       \r
-       def OnAddModel(self, e):\r
-               dlg=wx.FileDialog(self, "Open file to print", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)\r
-               dlg.SetWildcard(meshLoader.wildcardFilter())\r
-               if dlg.ShowModal() == wx.ID_OK:\r
-                       for filename in dlg.GetPaths():\r
-                               item = ProjectObject(self, filename)\r
-                               profile.putPreference('lastFile', item.filename)\r
-                               self.list.append(item)\r
-                               self.selection = item\r
-                               self._updateListbox()\r
-                               self.OnListSelect(None)\r
-               self.preview.Refresh()\r
-               dlg.Destroy()\r
-       \r
-       def OnRemModel(self, e):\r
-               if self.selection == None:\r
-                       return\r
-               self.list.remove(self.selection)\r
-               self._updateListbox()\r
-               self.preview.Refresh()\r
-       \r
-       def OnMoveUp(self, e):\r
-               if self.selection == None:\r
-                       return\r
-               i = self.listbox.GetSelection()\r
-               if i == 0:\r
-                       return\r
-               self.list.remove(self.selection)\r
-               self.list.insert(i-1, self.selection)\r
-               self._updateListbox()\r
-               self.preview.Refresh()\r
-\r
-       def OnMoveDown(self, e):\r
-               if self.selection == None:\r
-                       return\r
-               i = self.listbox.GetSelection()\r
-               if i == len(self.list) - 1:\r
-                       return\r
-               self.list.remove(self.selection)\r
-               self.list.insert(i+1, self.selection)\r
-               self._updateListbox()\r
-               self.preview.Refresh()\r
-       \r
-       def OnCopy(self, e):\r
-               if self.selection == None:\r
-                       return\r
-               \r
-               item = self.selection.clone()\r
-               self.list.insert(self.list.index(self.selection), item)\r
-               self.selection = item\r
-               \r
-               self._updateListbox()\r
-               self.preview.Refresh()\r
-       \r
-       def OnSetCustomProfile(self, e):\r
-               if self.selection == None:\r
-                       return\r
-\r
-               dlg=wx.FileDialog(self, "Select profile", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)\r
-               dlg.SetWildcard("Profile files (*.ini)|*.ini;*.INI")\r
-               if dlg.ShowModal() == wx.ID_OK:\r
-                       self.selection.profile = dlg.GetPath()\r
-               else:\r
-                       self.selection.profile = None\r
-               self._updateListbox()\r
-               dlg.Destroy()\r
-       \r
-       def _updateListbox(self):\r
-               self.listbox.Clear()\r
-               for item in self.list:\r
-                       if item.profile != None:\r
-                               self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1] + " *")\r
-                       else:\r
-                               self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1])\r
-               if self.selection in self.list:\r
-                       self.listbox.SetSelection(self.list.index(self.selection))\r
-               elif len(self.list) > 0:\r
-                       self.selection = self.list[0]\r
-                       self.listbox.SetSelection(0)\r
-               else:\r
-                       self.selection = None\r
-                       self.listbox.SetSelection(-1)\r
-               if self.alwaysAutoPlace:\r
-                       self.OnAutoPlace(None)\r
-\r
-       def OnAutoPlace(self, e):\r
-               bestAllowedSize = int(self.machineSize[1])\r
-               bestArea = self._doAutoPlace(bestAllowedSize)\r
-               for i in xrange(10, int(self.machineSize[1]), 10):\r
-                       area = self._doAutoPlace(i)\r
-                       if area < bestArea:\r
-                               bestAllowedSize = i\r
-                               bestArea = area\r
-               self._doAutoPlace(bestAllowedSize)\r
-               for item in self.list:\r
-                       item.clampXY()\r
-               self.preview.Refresh()\r
-       \r
-       def _doAutoPlace(self, allowedSizeY):\r
-               extraSizeMin, extraSizeMax = self.getExtraHeadSize()\r
-\r
-               if extraSizeMin[0] > extraSizeMax[0]:\r
-                       posX = self.machineSize[0]\r
-                       dirX = -1\r
-               else:\r
-                       posX = 0\r
-                       dirX = 1\r
-               posY = 0\r
-               dirY = 1\r
-               \r
-               minX = self.machineSize[0]\r
-               minY = self.machineSize[1]\r
-               maxX = 0\r
-               maxY = 0\r
-               for item in self.list:\r
-                       item.centerX = posX + item.getMaximum()[0] * item.scale * dirX\r
-                       item.centerY = posY + item.getMaximum()[1] * item.scale * dirY\r
-                       if item.centerY + item.getSize()[1] >= allowedSizeY:\r
-                               if dirX < 0:\r
-                                       posX = minX - extraSizeMax[0] - 1\r
-                               else:\r
-                                       posX = maxX + extraSizeMin[0] + 1\r
-                               posY = 0\r
-                               item.centerX = posX + item.getMaximum()[0] * item.scale * dirX\r
-                               item.centerY = posY + item.getMaximum()[1] * item.scale * dirY\r
-                       posY += item.getSize()[1]  * item.scale * dirY + extraSizeMin[1] + 1\r
-                       minX = min(minX, item.centerX - item.getSize()[0] * item.scale / 2)\r
-                       minY = min(minY, item.centerY - item.getSize()[1] * item.scale / 2)\r
-                       maxX = max(maxX, item.centerX + item.getSize()[0] * item.scale / 2)\r
-                       maxY = max(maxY, item.centerY + item.getSize()[1] * item.scale / 2)\r
-               \r
-               for item in self.list:\r
-                       if dirX < 0:\r
-                               item.centerX -= minX / 2\r
-                       else:\r
-                               item.centerX += (self.machineSize[0] - maxX) / 2\r
-                       item.centerY += (self.machineSize[1] - maxY) / 2\r
-               \r
-               if minX < 0 or maxX > self.machineSize[0]:\r
-                       return ((maxX - minX) + (maxY - minY)) * 100\r
-               \r
-               return (maxX - minX) + (maxY - minY)\r
-\r
-       def OnSlice(self, e):\r
-               dlg=wx.FileDialog(self, "Save project gcode file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)\r
-               dlg.SetWildcard("GCode file (*.gcode)|*.gcode")\r
-               if dlg.ShowModal() != wx.ID_OK:\r
-                       dlg.Destroy()\r
-                       return\r
-               resultFilename = dlg.GetPath()\r
-               dlg.Destroy()\r
-\r
-               put = profile.setTempOverride\r
-               oldProfile = profile.getGlobalProfileString()\r
-               \r
-               put('add_start_end_gcode', 'False')\r
-               put('gcode_extension', 'project_tmp')\r
-               if self.printMode == 0:\r
-                       clearZ = 0\r
-                       actionList = []\r
-                       for item in self.list:\r
-                               if item.profile != None and os.path.isfile(item.profile):\r
-                                       profile.loadGlobalProfile(item.profile)\r
-                               put('object_center_x', item.centerX - self.extruderOffset[item.extruder][0])\r
-                               put('object_center_y', item.centerY - self.extruderOffset[item.extruder][1])\r
-                               put('model_scale', item.scale)\r
-                               put('flip_x', item.flipX)\r
-                               put('flip_y', item.flipY)\r
-                               put('flip_z', item.flipZ)\r
-                               put('model_rotate_base', item.rotate)\r
-                               put('swap_xz', item.swapXZ)\r
-                               put('swap_yz', item.swapYZ)\r
-                               \r
-                               action = Action()\r
-                               action.sliceCmd = sliceRun.getSliceCommand(item.filename)\r
-                               action.centerX = item.centerX\r
-                               action.centerY = item.centerY\r
-                               action.temperature = profile.getProfileSettingFloat('print_temperature')\r
-                               action.extruder = item.extruder\r
-                               action.filename = item.filename\r
-                               clearZ = max(clearZ, item.getSize()[2] * item.scale + 5.0)\r
-                               action.clearZ = clearZ\r
-                               action.leaveResultForNextSlice = False\r
-                               action.usePreviousSlice = False\r
-                               actionList.append(action)\r
-\r
-                               if self.list.index(item) > 0 and item.isSameExceptForPosition(self.list[self.list.index(item)-1]):\r
-                                       actionList[-2].leaveResultForNextSlice = True\r
-                                       actionList[-1].usePreviousSlice = True\r
-\r
-                               if item.profile != None:\r
-                                       profile.loadGlobalProfileFromString(oldProfile)\r
-                       \r
-               else:\r
-                       self._saveCombinedSTL(resultFilename + "_temp_.stl")\r
-                       put('model_scale', 1.0)\r
-                       put('flip_x', False)\r
-                       put('flip_y', False)\r
-                       put('flip_z', False)\r
-                       put('model_rotate_base', 0)\r
-                       put('swap_xz', False)\r
-                       put('swap_yz', False)\r
-                       actionList = []\r
-                       \r
-                       action = Action()\r
-                       action.sliceCmd = sliceRun.getSliceCommand(resultFilename + "_temp_.stl")\r
-                       action.centerX = profile.getPreferenceFloat('machine_width') / 2\r
-                       action.centerY = profile.getPreferenceFloat('machine_depth') / 2\r
-                       action.temperature = profile.getProfileSettingFloat('print_temperature')\r
-                       action.extruder = 0\r
-                       action.filename = resultFilename + "_temp_.stl"\r
-                       action.clearZ = 0\r
-                       action.leaveResultForNextSlice = False\r
-                       action.usePreviousSlice = False\r
-\r
-                       actionList.append(action)\r
-               \r
-               #Restore the old profile.\r
-               profile.resetTempOverride()\r
-               \r
-               pspw = ProjectSliceProgressWindow(actionList, resultFilename)\r
-               pspw.extruderOffset = self.extruderOffset\r
-               pspw.Centre()\r
-               pspw.Show(True)\r
-       \r
-       def OnScaleChange(self, e):\r
-               if self.selection == None:\r
-                       return\r
-               try:\r
-                       self.selection.scale = float(self.scaleCtrl.GetValue())\r
-               except ValueError:\r
-                       self.selection.scale = 1.0\r
-               self.preview.Refresh()\r
-       \r
-       def OnRotateChange(self, e):\r
-               if self.selection == None:\r
-                       return\r
-               self.selection.rotate = float(self.rotateCtrl.GetValue())\r
-               self.selection.updateModelTransform()\r
-               if self.alwaysAutoPlace:\r
-                       self.OnAutoPlace(None)\r
-               self.preview.Refresh()\r
-\r
-       def OnExtruderChange(self, e):\r
-               if self.selection == None:\r
-                       return\r
-               self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1\r
-               self.preview.Refresh()\r
-               \r
-       def OnMirrorChange(self):\r
-               if self.selection == None:\r
-                       return\r
-               self.selection.flipX = self.mirrorX.GetValue()\r
-               self.selection.flipY = self.mirrorY.GetValue()\r
-               self.selection.flipZ = self.mirrorZ.GetValue()\r
-               self.selection.swapXZ = self.swapXZ.GetValue()\r
-               self.selection.swapYZ = self.swapYZ.GetValue()\r
-               self.selection.updateModelTransform()\r
-               if self.alwaysAutoPlace:\r
-                       self.OnAutoPlace(None)\r
-               self.preview.Refresh()\r
-\r
-       def getExtraHeadSize(self):\r
-               extraSizeMin = self.headSizeMin\r
-               extraSizeMax = self.headSizeMax\r
-               if profile.getProfileSettingFloat('skirt_line_count') > 0:\r
-                       skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')\r
-                       extraSizeMin = extraSizeMin + numpy.array([skirtSize, skirtSize, 0])\r
-                       extraSizeMax = extraSizeMax + numpy.array([skirtSize, skirtSize, 0])\r
-               if profile.getProfileSetting('enable_raft') != 'False':\r
-                       raftSize = profile.getProfileSettingFloat('raft_margin') * 2\r
-                       extraSizeMin = extraSizeMin + numpy.array([raftSize, raftSize, 0])\r
-                       extraSizeMax = extraSizeMax + numpy.array([raftSize, raftSize, 0])\r
-               if profile.getProfileSetting('support') != 'None':\r
-                       extraSizeMin = extraSizeMin + numpy.array([3.0, 0, 0])\r
-                       extraSizeMax = extraSizeMax + numpy.array([3.0, 0, 0])\r
-\r
-               if self.printMode == 1:\r
-                       extraSizeMin = numpy.array([6.0, 6.0, 0])\r
-                       extraSizeMax = numpy.array([6.0, 6.0, 0])\r
-               \r
-               return extraSizeMin, extraSizeMax\r
-\r
-class PreviewGLCanvas(glcanvas.GLCanvas):\r
-       def __init__(self, parent, projectPlannerWindow):\r
-               attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)\r
-               glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)\r
-               self.parent = projectPlannerWindow\r
-               self.context = glcanvas.GLContext(self)\r
-               wx.EVT_PAINT(self, self.OnPaint)\r
-               wx.EVT_SIZE(self, self.OnSize)\r
-               wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)\r
-               wx.EVT_LEFT_DOWN(self, self.OnMouseLeftDown)\r
-               wx.EVT_MOTION(self, self.OnMouseMotion)\r
-               wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)\r
-               self.yaw = 30\r
-               self.pitch = 60\r
-               self.offsetX = 0\r
-               self.offsetY = 0\r
-               self.view3D = self.parent.alwaysAutoPlace\r
-               if self.view3D:\r
-                       self.zoom = 300\r
-               else:\r
-                       self.zoom = self.parent.machineSize[0] / 2 + 10\r
-               self.allowDrag = False\r
-\r
-               self.objColor = profile.getPreferenceColour('model_colour')\r
-\r
-       def OnMouseLeftDown(self,e):\r
-               self.allowDrag = True\r
-               if not self.parent.alwaysAutoPlace and not self.view3D:\r
-                       #TODO: Translate mouse X/Y to 3D X/Y/Z\r
-                       for item in self.parent.list:\r
-                               iMin = (item.getMinimum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - self.parent.extruderOffset[item.extruder]\r
-                               iMax = (item.getMaximum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - self.parent.extruderOffset[item.extruder]\r
-               \r
-       def OnMouseMotion(self,e):\r
-               if self.allowDrag and e.Dragging() and e.LeftIsDown():\r
-                       if self.view3D:\r
-                               self.yaw += e.GetX() - self.oldX\r
-                               self.pitch -= e.GetY() - self.oldY\r
-                               if self.pitch > 170:\r
-                                       self.pitch = 170\r
-                               if self.pitch < 10:\r
-                                       self.pitch = 10\r
-                       elif not self.parent.alwaysAutoPlace:\r
-                               item = self.parent.selection\r
-                               if item != None:\r
-                                       item.centerX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2\r
-                                       item.centerY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2\r
-                                       item.clampXY()\r
-                       self.Refresh()\r
-               else:\r
-                       self.allowDrag = False\r
-               if e.Dragging() and e.RightIsDown():\r
-                       if self.view3D:\r
-                               self.zoom += e.GetY() - self.oldY\r
-                               if self.zoom < 1:\r
-                                       self.zoom = 1\r
-                       self.Refresh()\r
-               self.oldX = e.GetX()\r
-               self.oldY = e.GetY()\r
-       \r
-       def OnMouseWheel(self,e):\r
-               if self.view3D:\r
-                       self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0\r
-                       if self.zoom < 1.0:\r
-                               self.zoom = 1.0\r
-                       self.Refresh()\r
-       \r
-       def OnEraseBackground(self,event):\r
-               #Workaround for windows background redraw flicker.\r
-               pass\r
-       \r
-       def OnSize(self,event):\r
-               self.Refresh()\r
-\r
-       def OnPaint(self,event):\r
-               dc = wx.PaintDC(self)\r
-               if not hasOpenGLlibs:\r
-                       dc.Clear()\r
-                       dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)\r
-                       return\r
-               self.SetCurrent(self.context)\r
-               opengl.InitGL(self, self.view3D, self.zoom)\r
-               if self.view3D:\r
-                       glTranslate(0,0,-self.zoom)\r
-                       glRotate(-self.pitch, 1,0,0)\r
-                       glRotate(self.yaw, 0,0,1)\r
-               else:\r
-                       glTranslate(self.offsetX, self.offsetY, 0.0)\r
-               glTranslate(-self.parent.machineSize[0]/2, -self.parent.machineSize[1]/2, 0)\r
-\r
-               self.OnDraw()\r
-               self.SwapBuffers()\r
-\r
-       def OnDraw(self):\r
-               machineSize = self.parent.machineSize\r
-               extraSizeMin, extraSizeMax = self.parent.getExtraHeadSize()\r
-\r
-               for item in self.parent.list:\r
-                       item.validPlacement = True\r
-                       item.gotHit = False\r
-               \r
-               for idx1 in xrange(0, len(self.parent.list)):\r
-                       item = self.parent.list[idx1]\r
-                       iMin1 = (item.getMinimum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - extraSizeMin - self.parent.extruderOffset[item.extruder]\r
-                       iMax1 = (item.getMaximum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) + extraSizeMax - self.parent.extruderOffset[item.extruder]\r
-                       for idx2 in xrange(0, idx1):\r
-                               item2 = self.parent.list[idx2]\r
-                               iMin2 = (item2.getMinimum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])\r
-                               iMax2 = (item2.getMaximum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])\r
-                               if item != item2 and iMax1[0] >= iMin2[0] and iMin1[0] <= iMax2[0] and iMax1[1] >= iMin2[1] and iMin1[1] <= iMax2[1]:\r
-                                       item.validPlacement = False\r
-                                       item2.gotHit = True\r
-               \r
-               seenSelected = False\r
-               for item in self.parent.list:\r
-                       if item == self.parent.selection:\r
-                               seenSelected = True\r
-                       if item.modelDisplayList == None:\r
-                               item.modelDisplayList = glGenLists(1);\r
-                       if item.modelDirty:\r
-                               item.modelDirty = False\r
-                               modelSize = item.getMaximum() - item.getMinimum()\r
-                               glNewList(item.modelDisplayList, GL_COMPILE)\r
-                               opengl.DrawMesh(item.mesh)\r
-                               glEndList()\r
-                       \r
-                       if item.validPlacement:\r
-                               if self.parent.selection == item:\r
-                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  map(lambda x: x + 0.2, self.objColor))\r
-                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  map(lambda x: x / 2, self.objColor))\r
-                               else:\r
-                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  self.objColor)\r
-                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  map(lambda x: x / 2, self.objColor))\r
-                       else:\r
-                               if self.parent.selection == item:\r
-                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])\r
-                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])\r
-                               else:\r
-                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])\r
-                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])\r
-                       glPushMatrix()\r
-                       \r
-                       glEnable(GL_LIGHTING)\r
-                       glTranslate(item.centerX, item.centerY, 0)\r
-                       glPushMatrix()\r
-                       glScalef(item.scale, item.scale, item.scale)\r
-                       glCallList(item.modelDisplayList)\r
-                       glPopMatrix()\r
-                       \r
-                       vMin = item.getMinimum() * item.scale\r
-                       vMax = item.getMaximum() * item.scale\r
-                       vMinHead = vMin - extraSizeMin - self.parent.extruderOffset[item.extruder]\r
-                       vMaxHead = vMax + extraSizeMax - self.parent.extruderOffset[item.extruder]\r
-\r
-                       glDisable(GL_LIGHTING)\r
-\r
-                       if not self.parent.alwaysAutoPlace:\r
-                               if self.parent.selection == item:\r
-                                       if item.gotHit:\r
-                                               glColor3f(1.0,0.0,0.3)\r
-                                       else:\r
-                                               glColor3f(1.0,0.0,1.0)\r
-                                       opengl.DrawBox(vMin, vMax)\r
-                                       if item.gotHit:\r
-                                               glColor3f(1.0,0.3,0.0)\r
-                                       else:\r
-                                               glColor3f(1.0,1.0,0.0)\r
-                                       opengl.DrawBox(vMinHead, vMaxHead)\r
-                               elif seenSelected:\r
-                                       if item.gotHit:\r
-                                               glColor3f(0.5,0.0,0.1)\r
-                                       else:\r
-                                               glColor3f(0.5,0.0,0.5)\r
-                                       opengl.DrawBox(vMinHead, vMaxHead)\r
-                               else:\r
-                                       if item.gotHit:\r
-                                               glColor3f(0.7,0.1,0.0)\r
-                                       else:\r
-                                               glColor3f(0.7,0.7,0.0)\r
-                                       opengl.DrawBox(vMin, vMax)\r
-                       \r
-                       glPopMatrix()\r
-               \r
-               opengl.DrawMachine(util3d.Vector3(machineSize[0], machineSize[1], machineSize[2]))\r
-               glFlush()\r
-\r
-class ProjectSliceProgressWindow(wx.Frame):\r
-       def __init__(self, actionList, resultFilename):\r
-               super(ProjectSliceProgressWindow, self).__init__(None, title='Cura')\r
-               self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))\r
-               \r
-               self.actionList = actionList\r
-               self.resultFilename = resultFilename\r
-               self.abort = False\r
-               self.prevStep = 'start'\r
-               self.totalDoneFactor = 0.0\r
-               self.startTime = time.time()\r
-               self.sliceStartTime = time.time()\r
-               \r
-               self.sizer = wx.GridBagSizer(2, 2) \r
-               self.statusText = wx.StaticText(self, -1, "Building: %s" % (resultFilename))\r
-               self.progressGauge = wx.Gauge(self, -1)\r
-               self.progressGauge.SetRange(10000)\r
-               self.progressGauge2 = wx.Gauge(self, -1)\r
-               self.progressGauge2.SetRange(len(self.actionList))\r
-               self.abortButton = wx.Button(self, -1, "Abort")\r
-               self.sizer.Add(self.statusText, (0,0), span=(1,5))\r
-               self.sizer.Add(self.progressGauge, (1, 0), span=(1,5), flag=wx.EXPAND)\r
-               self.sizer.Add(self.progressGauge2, (2, 0), span=(1,5), flag=wx.EXPAND)\r
-\r
-               self.sizer.Add(self.abortButton, (3,0), span=(1,5), flag=wx.ALIGN_CENTER)\r
-               self.sizer.AddGrowableCol(0)\r
-               self.sizer.AddGrowableRow(0)\r
-\r
-               self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)\r
-               self.SetSizer(self.sizer)\r
-               self.Layout()\r
-               self.Fit()\r
-               \r
-               threading.Thread(target=self.OnRun).start()\r
-\r
-       def OnAbort(self, e):\r
-               if self.abort:\r
-                       self.Close()\r
-               else:\r
-                       self.abort = True\r
-                       self.abortButton.SetLabel('Close')\r
-\r
-       def SetProgress(self, stepName, layer, maxLayer):\r
-               if self.prevStep != stepName:\r
-                       self.totalDoneFactor += sliceRun.sliceStepTimeFactor[self.prevStep]\r
-                       newTime = time.time()\r
-                       #print "#####" + str(newTime-self.startTime) + " " + self.prevStep + " -> " + stepName\r
-                       self.startTime = newTime\r
-                       self.prevStep = stepName\r
-               \r
-               progresValue = ((self.totalDoneFactor + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000\r
-               self.progressGauge.SetValue(int(progresValue))\r
-               self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")\r
-               taskbar.setProgress(self, 10000 * self.progressGauge2.GetValue() + int(progresValue), 10000 * len(self.actionList))\r
-       \r
-       def OnRun(self):\r
-               resultFile = open(self.resultFilename, "w")\r
-               put = profile.setTempOverride\r
-               self.progressLog = []\r
-               for action in self.actionList:\r
-                       wx.CallAfter(self.SetTitle, "Building: [%d/%d]"  % (self.actionList.index(action) + 1, len(self.actionList)))\r
-                       if not action.usePreviousSlice:\r
-                               p = sliceRun.startSliceCommandProcess(action.sliceCmd)\r
-                               line = p.stdout.readline()\r
-               \r
-                               maxValue = 1\r
-                               while(len(line) > 0):\r
-                                       line = line.rstrip()\r
-                                       if line[0:9] == "Progress[" and line[-1:] == "]":\r
-                                               progress = line[9:-1].split(":")\r
-                                               if len(progress) > 2:\r
-                                                       maxValue = int(progress[2])\r
-                                               wx.CallAfter(self.SetProgress, progress[0], int(progress[1]), maxValue)\r
-                                       else:\r
-                                               self.progressLog.append(line)\r
-                                               wx.CallAfter(self.statusText.SetLabel, line)\r
-                                       if self.abort:\r
-                                               p.terminate()\r
-                                               wx.CallAfter(self.statusText.SetLabel, "Aborted by user.")\r
-                                               resultFile.close()\r
-                                               return\r
-                                       line = p.stdout.readline()\r
-                               self.returnCode = p.wait()\r
-                       \r
-                       put('object_center_x', action.centerX - self.extruderOffset[action.extruder][0])\r
-                       put('object_center_y', action.centerY - self.extruderOffset[action.extruder][1])\r
-                       put('clear_z', action.clearZ)\r
-                       put('extruder', action.extruder)\r
-                       put('print_temperature', action.temperature)\r
-                       \r
-                       if action == self.actionList[0]:\r
-                               resultFile.write(';TYPE:CUSTOM\n')\r
-                               resultFile.write('T%d\n' % (action.extruder))\r
-                               currentExtruder = action.extruder\r
-                               prevTemp = action.temperature\r
-                               startGCode = profile.getAlterationFileContents('start.gcode')\r
-                               startGCode = startGCode.replace('?filename?', 'Multiple files')\r
-                               resultFile.write(startGCode)\r
-                       else:\r
-                               #reset the extrusion length, and move to the next object center.\r
-                               resultFile.write(';TYPE:CUSTOM\n')\r
-                               if prevTemp != action.temperature and action.temperature > 0:\r
-                                       resultFile.write('M104 S%d\n' % (int(action.temperature)))\r
-                                       prevTemp = action.temperature\r
-                               resultFile.write(profile.getAlterationFileContents('nextobject.gcode'))\r
-                       resultFile.write(';PRINTNR:%d\n' % self.actionList.index(action))\r
-                       profile.resetTempOverride()\r
-                       \r
-                       if not action.usePreviousSlice:\r
-                               f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")\r
-                               data = f.read(4096)\r
-                               while data != '':\r
-                                       resultFile.write(data)\r
-                                       data = f.read(4096)\r
-                               f.close()\r
-                               savedCenterX = action.centerX\r
-                               savedCenterY = action.centerY\r
-                       else:\r
-                               f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")\r
-                               for line in f:\r
-                                       if line[0] != ';':\r
-                                               if 'X' in line:\r
-                                                       line = self._adjustNumberInLine(line, 'X', action.centerX - savedCenterX)\r
-                                               if 'Y' in line:\r
-                                                       line = self._adjustNumberInLine(line, 'Y', action.centerY - savedCenterY)\r
-                                       resultFile.write(line)\r
-                               f.close()\r
-\r
-                       if not action.leaveResultForNextSlice:\r
-                               os.remove(sliceRun.getExportFilename(action.filename, "project_tmp"))\r
-                       \r
-                       wx.CallAfter(self.progressGauge.SetValue, 10000)\r
-                       self.totalDoneFactor = 0.0\r
-                       wx.CallAfter(self.progressGauge2.SetValue, self.actionList.index(action) + 1)\r
-               \r
-               resultFile.write(';TYPE:CUSTOM\n')\r
-               if len(self.actionList) > 1 and self.actionList[-1].clearZ > 1:\r
-                       #only move to higher Z if we have sliced more then 1 object. This solves the "move into print after printing" problem with the print-all-at-once option.\r
-                       resultFile.write('G1 Z%f F%f\n' % (self.actionList[-1].clearZ, profile.getProfileSettingFloat('max_z_speed') * 60))\r
-               resultFile.write(profile.getAlterationFileContents('end.gcode'))\r
-               resultFile.close()\r
-               \r
-               gcode = gcodeInterpreter.gcode()\r
-               gcode.load(self.resultFilename)\r
-               \r
-               self.abort = True\r
-               sliceTime = time.time() - self.sliceStartTime\r
-               status = "Build: %s" % (self.resultFilename)\r
-               status += "\nSlicing took: %02d:%02d" % (sliceTime / 60, sliceTime % 60)\r
-               status += "\nFilament: %.2fm %.2fg" % (gcode.extrusionAmount / 1000, gcode.calculateWeight() * 1000)\r
-               status += "\nPrint time: %02d:%02d" % (int(gcode.totalMoveTimeMinute / 60), int(gcode.totalMoveTimeMinute % 60))\r
-               cost = gcode.calculateCost()\r
-               if cost != False:\r
-                       status += "\nCost: %s" % (cost)\r
-               profile.replaceGCodeTags(self.resultFilename, gcode)\r
-               wx.CallAfter(self.statusText.SetLabel, status)\r
-               wx.CallAfter(self.OnSliceDone)\r
-       \r
-       def _adjustNumberInLine(self, line, tag, f):\r
-               m = re.search('^(.*'+tag+')([0-9\.]*)(.*)$', line)\r
-               return m.group(1) + str(float(m.group(2)) + f) + m.group(3) + '\n'\r
-       \r
-       def OnSliceDone(self):\r
-               self.abortButton.Destroy()\r
-               self.closeButton = wx.Button(self, -1, "Close")\r
-               self.printButton = wx.Button(self, -1, "Print")\r
-               self.logButton = wx.Button(self, -1, "Show log")\r
-               self.sizer.Add(self.closeButton, (3,0), span=(1,1))\r
-               self.sizer.Add(self.printButton, (3,1), span=(1,1))\r
-               self.sizer.Add(self.logButton, (3,2), span=(1,1))\r
-               if exporer.hasExporer():\r
-                       self.openFileLocationButton = wx.Button(self, -1, "Open file location")\r
-                       self.Bind(wx.EVT_BUTTON, self.OnOpenFileLocation, self.openFileLocationButton)\r
-                       self.sizer.Add(self.openFileLocationButton, (3,3), span=(1,1))\r
-               if profile.getPreference('sdpath') != '':\r
-                       self.copyToSDButton = wx.Button(self, -1, "To SDCard")\r
-                       self.Bind(wx.EVT_BUTTON, self.OnCopyToSD, self.copyToSDButton)\r
-                       self.sizer.Add(self.copyToSDButton, (3,4), span=(1,1))\r
-               self.Bind(wx.EVT_BUTTON, self.OnAbort, self.closeButton)\r
-               self.Bind(wx.EVT_BUTTON, self.OnPrint, self.printButton)\r
-               self.Bind(wx.EVT_BUTTON, self.OnShowLog, self.logButton)\r
-               self.Layout()\r
-               self.Fit()\r
-               taskbar.setBusy(self, False)\r
-\r
-       def OnCopyToSD(self, e):\r
-               filename = os.path.basename(self.resultFilename)\r
-               if profile.getPreference('sdshortnames') == 'True':\r
-                       filename = sliceRun.getShortFilename(filename)\r
-               shutil.copy(self.resultFilename, os.path.join(profile.getPreference('sdpath'), filename))\r
-       \r
-       def OnOpenFileLocation(self, e):\r
-               exporer.openExporer(self.resultFilename)\r
-       \r
-       def OnPrint(self, e):\r
-               printWindow.printFile(self.resultFilename)\r
-\r
-       def OnShowLog(self, e):\r
-               LogWindow('\n'.join(self.progressLog))\r
-\r
-class preferencesDialog(configBase.configWindowBase):\r
-       def __init__(self, parent):\r
-               super(preferencesDialog, self).__init__(title="Project Planner Preferences")\r
-               \r
-               self.parent = parent\r
-               wx.EVT_CLOSE(self, self.OnClose)\r
-               \r
-               extruderAmount = int(profile.getPreference('extruder_amount'))\r
-               \r
-               left, right, main = self.CreateConfigPanel(self)\r
-               configBase.TitleRow(left, 'Machine head size')\r
-               c = configBase.SettingRow(left, 'Head size - X towards home (mm)', 'extruder_head_size_min_x', '0', 'Size of your printer head in the X direction, on the Ultimaker your fan is in this direction.', type = 'preference')\r
-               validators.validFloat(c, 0.1)\r
-               c = configBase.SettingRow(left, 'Head size - X towards end (mm)', 'extruder_head_size_max_x', '0', 'Size of your printer head in the X direction.', type = 'preference')\r
-               validators.validFloat(c, 0.1)\r
-               c = configBase.SettingRow(left, 'Head size - Y towards home (mm)', 'extruder_head_size_min_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')\r
-               validators.validFloat(c, 0.1)\r
-               c = configBase.SettingRow(left, 'Head size - Y towards end (mm)', 'extruder_head_size_max_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')\r
-               validators.validFloat(c, 0.1)\r
-               c = configBase.SettingRow(left, 'Head gantry height (mm)', 'extruder_head_size_height', '0', 'The tallest object height that will always fit under your printers gantry system when the printer head is at the lowest Z position.', type = 'preference')\r
-               validators.validFloat(c)\r
-               \r
-               self.okButton = wx.Button(left, -1, 'Ok')\r
-               left.GetSizer().Add(self.okButton, (left.GetSizer().GetRows(), 1))\r
-               self.okButton.Bind(wx.EVT_BUTTON, self.OnClose)\r
-               \r
-               self.MakeModal(True)\r
-               main.Fit()\r
-               self.Fit()\r
-\r
-       def OnClose(self, e):\r
-               self.parent.headSizeMin = numpy.array([profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0])\r
-               self.parent.headSizeMax = numpy.array([profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0])\r
-               self.parent.Refresh()\r
-\r
-               self.MakeModal(False)\r
-               self.Destroy()\r
-\r
-class LogWindow(wx.Frame):\r
-       def __init__(self, logText):\r
-               super(LogWindow, self).__init__(None, title="Slice log")\r
-               self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_READONLY)\r
-               self.SetSize((400,300))\r
-               self.Centre()\r
-               self.Show(True)\r
-\r
-def main():\r
-       app = wx.App(False)\r
-       projectPlanner().Show(True)\r
-       app.MainLoop()\r
-\r
-if __name__ == '__main__':\r
-       main()\r
+from __future__ import absolute_import
+import __init__
+
+import wx, os, platform, types, webbrowser, math, subprocess, threading, time, re, shutil
+import ConfigParser
+import numpy
+
+from wx import glcanvas
+try:
+       import OpenGL
+       OpenGL.ERROR_CHECKING = False
+       from OpenGL.GLU import *
+       from OpenGL.GL import *
+       hasOpenGLlibs = True
+except:
+       print "Failed to find PyOpenGL: http://pyopengl.sourceforge.net/"
+       hasOpenGLlibs = False
+
+from gui import opengl
+from gui import toolbarUtil
+from gui import icon
+from gui import configBase
+from gui import printWindow
+from gui import dropTarget
+from gui import taskbar
+from util import validators
+from util import profile
+from util import util3d
+from util import meshLoader
+from util import stl
+from util import mesh
+from util import sliceRun
+from util import gcodeInterpreter
+from util import exporer
+
+class Action(object):
+       pass
+
+class ProjectObject(object):
+       def __init__(self, parent, filename):
+               super(ProjectObject, self).__init__()
+
+               self.mesh = meshLoader.loadMesh(filename)
+
+               self.parent = parent
+               self.filename = filename
+               self.scale = 1.0
+               self.rotate = 0.0
+               self.flipX = False
+               self.flipY = False
+               self.flipZ = False
+               self.swapXZ = False
+               self.swapYZ = False
+               self.extruder = 0
+               self.profile = None
+               
+               self.modelDisplayList = None
+               self.modelDirty = False
+
+               self.mesh.getMinimumZ()
+               
+               self.centerX = -self.getMinimum()[0] + 5
+               self.centerY = -self.getMinimum()[1] + 5
+               
+               self.updateModelTransform()
+
+               self.centerX = -self.getMinimum()[0] + 5
+               self.centerY = -self.getMinimum()[1] + 5
+
+       def isSameExceptForPosition(self, other):
+               if self.filename != other.filename:
+                       return False
+               if self.scale != other.scale:
+                       return False
+               if self.rotate != other.rotate:
+                       return False
+               if self.flipX != other.flipX:
+                       return False
+               if self.flipY != other.flipY:
+                       return False
+               if self.flipZ != other.flipZ:
+                       return False
+               if self.swapXZ != other.swapXZ:
+                       return False
+               if self.swapYZ != other.swapYZ:
+                       return False
+               if self.extruder != other.extruder:
+                       return False
+               if self.profile != other.profile:
+                       return False
+               return True
+
+       def updateModelTransform(self):
+               self.mesh.setRotateMirror(self.rotate, self.flipX, self.flipY, self.flipZ, self.swapXZ, self.swapYZ)
+               minZ = self.mesh.getMinimumZ()
+               minV = self.getMinimum()
+               maxV = self.getMaximum()
+               self.mesh.vertexes -= numpy.array([minV[0] + (maxV[0] - minV[0]) / 2, minV[1] + (maxV[1] - minV[1]) / 2, minZ])
+               minZ = self.mesh.getMinimumZ()
+               self.modelDirty = True
+       
+       def getMinimum(self):
+               return self.mesh.getMinimum()
+       def getMaximum(self):
+               return self.mesh.getMaximum()
+       def getSize(self):
+               return self.mesh.getSize()
+       
+       def clone(self):
+               p = ProjectObject(self.parent, self.filename)
+
+               p.centerX = self.centerX + 5
+               p.centerY = self.centerY + 5
+               
+               p.filename = self.filename
+               p.scale = self.scale
+               p.rotate = self.rotate
+               p.flipX = self.flipX
+               p.flipY = self.flipY
+               p.flipZ = self.flipZ
+               p.swapXZ = self.swapXZ
+               p.swapYZ = self.swapYZ
+               p.extruder = self.extruder
+               p.profile = self.profile
+               
+               p.updateModelTransform()
+               
+               return p
+       
+       def clampXY(self):
+               if self.centerX < -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]:
+                       self.centerX = -self.getMinimum()[0] * self.scale + self.parent.extruderOffset[self.extruder][0]
+               if self.centerY < -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]:
+                       self.centerY = -self.getMinimum()[1] * self.scale + self.parent.extruderOffset[self.extruder][1]
+               if self.centerX > self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale:
+                       self.centerX = self.parent.machineSize[0] + self.parent.extruderOffset[self.extruder][0] - self.getMaximum()[0] * self.scale
+               if self.centerY > self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale:
+                       self.centerY = self.parent.machineSize[1] + self.parent.extruderOffset[self.extruder][1] - self.getMaximum()[1] * self.scale
+
+class projectPlanner(wx.Frame):
+       "Main user interface window"
+       def __init__(self):
+               super(projectPlanner, self).__init__(None, title='Cura - Project Planner')
+               
+               wx.EVT_CLOSE(self, self.OnClose)
+               self.panel = wx.Panel(self, -1)
+               self.SetSizer(wx.BoxSizer(wx.VERTICAL))
+               self.GetSizer().Add(self.panel, 1, flag=wx.EXPAND)
+               #self.SetIcon(icon.getMainIcon())
+               
+               self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
+               
+               self.list = []
+               self.selection = None
+               self.printMode = 0
+               self.alwaysAutoPlace = True
+
+               self.machineSize = numpy.array([profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')])
+               self.headSizeMin = numpy.array([profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0])
+               self.headSizeMax = numpy.array([profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0])
+
+               self.extruderOffset = [
+                       numpy.array([0,0,0]),
+                       numpy.array([profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1'), 0]),
+                       numpy.array([profile.getPreferenceFloat('extruder_offset_x2'), profile.getPreferenceFloat('extruder_offset_y2'), 0]),
+                       numpy.array([profile.getPreferenceFloat('extruder_offset_x3'), profile.getPreferenceFloat('extruder_offset_y3'), 0])]
+
+               self.toolbar = toolbarUtil.Toolbar(self.panel)
+
+               toolbarUtil.NormalButton(self.toolbar, self.OnLoadProject, 'open.png', 'Open project')
+               toolbarUtil.NormalButton(self.toolbar, self.OnSaveProject, 'save.png', 'Save project')
+               self.toolbar.AddSeparator()
+               group = []
+               toolbarUtil.RadioButton(self.toolbar, group, 'object-3d-on.png', 'object-3d-off.png', '3D view', callback=self.On3DClick).SetValue(self.alwaysAutoPlace)
+               toolbarUtil.RadioButton(self.toolbar, group, 'object-top-on.png', 'object-top-off.png', 'Topdown view', callback=self.OnTopClick).SetValue(not self.alwaysAutoPlace)
+               self.toolbar.AddSeparator()
+               toolbarUtil.NormalButton(self.toolbar, self.OnPreferences, 'preferences.png', 'Project planner preferences')
+               self.toolbar.AddSeparator()
+               toolbarUtil.NormalButton(self.toolbar, self.OnCutMesh, 'cut-mesh.png', 'Cut a plate STL into multiple STL files, and add those files to the project.\nNote: Splitting up plates sometimes takes a few minutes.')
+               toolbarUtil.NormalButton(self.toolbar, self.OnSaveCombinedSTL, 'save-combination.png', 'Save all the combined STL files into a single STL file as a plate.')
+               self.toolbar.AddSeparator()
+               group = []
+               self.printOneAtATime = toolbarUtil.RadioButton(self.toolbar, group, 'view-normal-on.png', 'view-normal-off.png', 'Print one object at a time', callback=self.OnPrintTypeChange)
+               self.printAllAtOnce = toolbarUtil.RadioButton(self.toolbar, group, 'all-at-once-on.png', 'all-at-once-off.png', 'Print all the objects at once', callback=self.OnPrintTypeChange)
+               self.toolbar.AddSeparator()
+               toolbarUtil.NormalButton(self.toolbar, self.OnQuit, 'exit.png', 'Close project planner')
+               
+               self.toolbar.Realize()
+
+               self.toolbar2 = toolbarUtil.Toolbar(self.panel)
+
+               toolbarUtil.NormalButton(self.toolbar2, self.OnAddModel, 'object-add.png', 'Add model')
+               toolbarUtil.NormalButton(self.toolbar2, self.OnRemModel, 'object-remove.png', 'Remove model')
+               self.toolbar2.AddSeparator()
+               toolbarUtil.NormalButton(self.toolbar2, self.OnMoveUp, 'move-up.png', 'Move model up in print list')
+               toolbarUtil.NormalButton(self.toolbar2, self.OnMoveDown, 'move-down.png', 'Move model down in print list')
+               toolbarUtil.NormalButton(self.toolbar2, self.OnCopy, 'copy.png', 'Make a copy of the current selected object')
+               toolbarUtil.NormalButton(self.toolbar2, self.OnSetCustomProfile, 'set-profile.png', 'Set a custom profile to be used to prepare a specific object.')
+               self.toolbar2.AddSeparator()
+               if not self.alwaysAutoPlace:
+                       toolbarUtil.NormalButton(self.toolbar2, self.OnAutoPlace, 'autoplace.png', 'Automaticly organize the objects on the platform.')
+               toolbarUtil.NormalButton(self.toolbar2, self.OnSlice, 'slice.png', 'Prepare to project into a gcode file.')
+               self.toolbar2.Realize()
+
+               self.toolbar3 = toolbarUtil.Toolbar(self.panel)
+               self.mirrorX = toolbarUtil.ToggleButton(self.toolbar3, 'flip_x', 'object-mirror-x-on.png', 'object-mirror-x-off.png', 'Mirror X', callback=self.OnMirrorChange)
+               self.mirrorY = toolbarUtil.ToggleButton(self.toolbar3, 'flip_y', 'object-mirror-y-on.png', 'object-mirror-y-off.png', 'Mirror Y', callback=self.OnMirrorChange)
+               self.mirrorZ = toolbarUtil.ToggleButton(self.toolbar3, 'flip_z', 'object-mirror-z-on.png', 'object-mirror-z-off.png', 'Mirror Z', callback=self.OnMirrorChange)
+               self.toolbar3.AddSeparator()
+
+               # Swap
+               self.swapXZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_xz', 'object-swap-xz-on.png', 'object-swap-xz-off.png', 'Swap XZ', callback=self.OnMirrorChange)
+               self.swapYZ = toolbarUtil.ToggleButton(self.toolbar3, 'swap_yz', 'object-swap-yz-on.png', 'object-swap-yz-off.png', 'Swap YZ', callback=self.OnMirrorChange)
+               self.toolbar3.Realize()
+               
+               sizer = wx.GridBagSizer(2,2)
+               self.panel.SetSizer(sizer)
+               self.preview = PreviewGLCanvas(self.panel, self)
+               self.listbox = wx.ListBox(self.panel, -1, choices=[])
+               self.addButton = wx.Button(self.panel, -1, "Add")
+               self.remButton = wx.Button(self.panel, -1, "Remove")
+               self.sliceButton = wx.Button(self.panel, -1, "Prepare")
+               if not self.alwaysAutoPlace:
+                       self.autoPlaceButton = wx.Button(self.panel, -1, "Auto Place")
+               
+               sizer.Add(self.toolbar, (0,0), span=(1,1), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
+               sizer.Add(self.toolbar2, (0,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
+               sizer.Add(self.preview, (1,0), span=(5,1), flag=wx.EXPAND)
+               sizer.Add(self.listbox, (1,1), span=(1,2), flag=wx.EXPAND)
+               sizer.Add(self.toolbar3, (2,1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.RIGHT)
+               sizer.Add(self.addButton, (3,1), span=(1,1))
+               sizer.Add(self.remButton, (3,2), span=(1,1))
+               sizer.Add(self.sliceButton, (4,1), span=(1,1))
+               if not self.alwaysAutoPlace:
+                       sizer.Add(self.autoPlaceButton, (4,2), span=(1,1))
+               sizer.AddGrowableCol(0)
+               sizer.AddGrowableRow(1)
+               
+               self.addButton.Bind(wx.EVT_BUTTON, self.OnAddModel)
+               self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel)
+               self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice)
+               if not self.alwaysAutoPlace:
+                       self.autoPlaceButton.Bind(wx.EVT_BUTTON, self.OnAutoPlace)
+               self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect)
+
+               panel = wx.Panel(self.panel, -1)
+               sizer.Add(panel, (5,1), span=(1,2))
+               
+               sizer = wx.GridBagSizer(2,2)
+               panel.SetSizer(sizer)
+               
+               self.scaleCtrl = wx.TextCtrl(panel, -1, '')
+               self.rotateCtrl = wx.SpinCtrl(panel, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
+               self.rotateCtrl.SetRange(0, 360)
+
+               sizer.Add(wx.StaticText(panel, -1, 'Scale'), (0,0), flag=wx.ALIGN_CENTER_VERTICAL)
+               sizer.Add(self.scaleCtrl, (0,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
+               sizer.Add(wx.StaticText(panel, -1, 'Rotate'), (1,0), flag=wx.ALIGN_CENTER_VERTICAL)
+               sizer.Add(self.rotateCtrl, (1,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
+
+               if int(profile.getPreference('extruder_amount')) > 1:
+                       self.extruderCtrl = wx.ComboBox(panel, -1, '1', choices=map(str, range(1, int(profile.getPreference('extruder_amount'))+1)), style=wx.CB_DROPDOWN|wx.CB_READONLY)
+                       sizer.Add(wx.StaticText(panel, -1, 'Extruder'), (2,0), flag=wx.ALIGN_CENTER_VERTICAL)
+                       sizer.Add(self.extruderCtrl, (2,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)
+                       self.extruderCtrl.Bind(wx.EVT_COMBOBOX, self.OnExtruderChange)
+
+               self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange)
+               self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange)
+
+               self.SetSize((800,600))
+
+       def OnClose(self, e):
+               self.Destroy()
+
+       def OnQuit(self, e):
+               self.Close()
+       
+       def OnPreferences(self, e):
+               prefDialog = preferencesDialog(self)
+               prefDialog.Centre()
+               prefDialog.Show(True)
+       
+       def OnCutMesh(self, e):
+               dlg=wx.FileDialog(self, "Open file to cut", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
+               dlg.SetWildcard(meshLoader.wildcardFilter())
+               if dlg.ShowModal() == wx.ID_OK:
+                       filename = dlg.GetPath()
+                       model = meshLoader.loadMesh(filename)
+                       pd = wx.ProgressDialog('Splitting model.', 'Splitting model into multiple parts.', model.vertexCount, self, wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_SMOOTH)
+                       parts = model.splitToParts(pd.Update)
+                       for part in parts:
+                               partFilename = filename[:filename.rfind('.')] + "_part%d.stl" % (parts.index(part))
+                               stl.saveAsSTL(part, partFilename)
+                               item = ProjectObject(self, partFilename)
+                               self.list.append(item)
+                               self.selection = item
+                               self._updateListbox()
+                               self.OnListSelect(None)
+                       pd.Destroy()
+               self.preview.Refresh()
+               dlg.Destroy()
+       
+       def OnDropFiles(self, filenames):
+               for filename in filenames:
+                       item = ProjectObject(self, filename)
+                       profile.putPreference('lastFile', item.filename)
+                       self.list.append(item)
+                       self.selection = item
+                       self._updateListbox()
+               self.OnListSelect(None)
+               self.preview.Refresh()
+
+       def OnPrintTypeChange(self):
+               self.printMode = 0
+               if self.printAllAtOnce.GetValue():
+                       self.printMode = 1
+               if self.alwaysAutoPlace:
+                       self.OnAutoPlace(None)
+               self.preview.Refresh()
+       
+       def OnSaveCombinedSTL(self, e):
+               dlg=wx.FileDialog(self, "Save as STL", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
+               dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL")
+               if dlg.ShowModal() == wx.ID_OK:
+                       self._saveCombinedSTL(dlg.GetPath())
+               dlg.Destroy()
+       
+       def _saveCombinedSTL(self, filename):
+               totalCount = 0
+               for item in self.list:
+                       totalCount += item.mesh.vertexCount
+               output = mesh.mesh()
+               output._prepareVertexCount(totalCount)
+               for item in self.list:
+                       offset = numpy.array([item.centerX, item.centerY, 0])
+                       for v in item.mesh.vertexes:
+                               v0 = v * item.scale + offset
+                               output.addVertex(v0[0], v0[1], v0[2])
+               stl.saveAsSTL(output, filename)
+       
+       def OnSaveProject(self, e):
+               dlg=wx.FileDialog(self, "Save project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
+               dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")
+               if dlg.ShowModal() == wx.ID_OK:
+                       cp = ConfigParser.ConfigParser()
+                       i = 0
+                       for item in self.list:
+                               section = 'model_%d' % (i)
+                               cp.add_section(section)
+                               cp.set(section, 'filename', item.filename.encode("utf-8"))
+                               cp.set(section, 'centerX', str(item.centerX))
+                               cp.set(section, 'centerY', str(item.centerY))
+                               cp.set(section, 'scale', str(item.scale))
+                               cp.set(section, 'rotate', str(item.rotate))
+                               cp.set(section, 'flipX', str(item.flipX))
+                               cp.set(section, 'flipY', str(item.flipY))
+                               cp.set(section, 'flipZ', str(item.flipZ))
+                               cp.set(section, 'swapXZ', str(item.swapXZ))
+                               cp.set(section, 'swapYZ', str(item.swapYZ))
+                               cp.set(section, 'extruder', str(item.extruder+1))
+                               if item.profile != None:
+                                       cp.set(section, 'profile', item.profile)
+                               i += 1
+                       cp.write(open(dlg.GetPath(), "w"))
+               dlg.Destroy()
+
+       def OnLoadProject(self, e):
+               dlg=wx.FileDialog(self, "Open project file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
+               dlg.SetWildcard("Project files (*.curaproject)|*.curaproject")
+               if dlg.ShowModal() == wx.ID_OK:
+                       cp = ConfigParser.ConfigParser()
+                       cp.read(dlg.GetPath())
+                       self.list = []
+                       i = 0
+                       while cp.has_section('model_%d' % (i)):
+                               section = 'model_%d' % (i)
+                               
+                               item = ProjectObject(self, unicode(cp.get(section, 'filename'), "utf-8"))
+                               item.centerX = float(cp.get(section, 'centerX'))
+                               item.centerY = float(cp.get(section, 'centerY'))
+                               item.scale = float(cp.get(section, 'scale'))
+                               item.rotate = float(cp.get(section, 'rotate'))
+                               item.flipX = cp.get(section, 'flipX') == 'True'
+                               item.flipY = cp.get(section, 'flipY') == 'True'
+                               item.flipZ = cp.get(section, 'flipZ') == 'True'
+                               item.swapXZ = cp.get(section, 'swapXZ') == 'True'
+                               item.swapYZ = cp.get(section, 'swapYZ') == 'True'
+                               if cp.has_option(section, 'extruder'):
+                                       item.extuder = int(cp.get(section, 'extruder')) - 1
+                               if cp.has_option(section, 'profile'):
+                                       item.profile = cp.get(section, 'profile')
+                               item.updateModelTransform()
+                               i += 1
+                               
+                               self.list.append(item)
+
+                       self.selected = self.list[0]
+                       self._updateListbox()                   
+                       self.OnListSelect(None)
+                       self.preview.Refresh()
+
+               dlg.Destroy()
+
+       def On3DClick(self):
+               self.preview.yaw = 30
+               self.preview.pitch = 60
+               self.preview.zoom = 300
+               self.preview.view3D = True
+               self.preview.Refresh()
+
+       def OnTopClick(self):
+               self.preview.view3D = False
+               self.preview.zoom = self.machineSize[0] / 2 + 10
+               self.preview.offsetX = 0
+               self.preview.offsetY = 0
+               self.preview.Refresh()
+
+       def OnListSelect(self, e):
+               if self.listbox.GetSelection() == -1:
+                       return
+               self.selection = self.list[self.listbox.GetSelection()]
+               self.scaleCtrl.SetValue(str(self.selection.scale))
+               self.rotateCtrl.SetValue(int(self.selection.rotate))
+               if int(profile.getPreference('extruder_amount')) > 1:
+
+                       self.extruderCtrl.SetValue(str(self.selection.extruder+1))
+
+               self.mirrorX.SetValue(self.selection.flipX)
+               self.mirrorY.SetValue(self.selection.flipY)
+               self.mirrorZ.SetValue(self.selection.flipZ)
+               self.swapXZ.SetValue(self.selection.swapXZ)
+               self.swapYZ.SetValue(self.selection.swapYZ)
+               
+               self.preview.Refresh()
+       
+       def OnAddModel(self, e):
+               dlg=wx.FileDialog(self, "Open file to print", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
+               dlg.SetWildcard(meshLoader.wildcardFilter())
+               if dlg.ShowModal() == wx.ID_OK:
+                       for filename in dlg.GetPaths():
+                               item = ProjectObject(self, filename)
+                               profile.putPreference('lastFile', item.filename)
+                               self.list.append(item)
+                               self.selection = item
+                               self._updateListbox()
+                               self.OnListSelect(None)
+               self.preview.Refresh()
+               dlg.Destroy()
+       
+       def OnRemModel(self, e):
+               if self.selection == None:
+                       return
+               self.list.remove(self.selection)
+               self._updateListbox()
+               self.preview.Refresh()
+       
+       def OnMoveUp(self, e):
+               if self.selection == None:
+                       return
+               i = self.listbox.GetSelection()
+               if i == 0:
+                       return
+               self.list.remove(self.selection)
+               self.list.insert(i-1, self.selection)
+               self._updateListbox()
+               self.preview.Refresh()
+
+       def OnMoveDown(self, e):
+               if self.selection == None:
+                       return
+               i = self.listbox.GetSelection()
+               if i == len(self.list) - 1:
+                       return
+               self.list.remove(self.selection)
+               self.list.insert(i+1, self.selection)
+               self._updateListbox()
+               self.preview.Refresh()
+       
+       def OnCopy(self, e):
+               if self.selection == None:
+                       return
+               
+               item = self.selection.clone()
+               self.list.insert(self.list.index(self.selection), item)
+               self.selection = item
+               
+               self._updateListbox()
+               self.preview.Refresh()
+       
+       def OnSetCustomProfile(self, e):
+               if self.selection == None:
+                       return
+
+               dlg=wx.FileDialog(self, "Select profile", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
+               dlg.SetWildcard("Profile files (*.ini)|*.ini;*.INI")
+               if dlg.ShowModal() == wx.ID_OK:
+                       self.selection.profile = dlg.GetPath()
+               else:
+                       self.selection.profile = None
+               self._updateListbox()
+               dlg.Destroy()
+       
+       def _updateListbox(self):
+               self.listbox.Clear()
+               for item in self.list:
+                       if item.profile != None:
+                               self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1] + " *")
+                       else:
+                               self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1])
+               if self.selection in self.list:
+                       self.listbox.SetSelection(self.list.index(self.selection))
+               elif len(self.list) > 0:
+                       self.selection = self.list[0]
+                       self.listbox.SetSelection(0)
+               else:
+                       self.selection = None
+                       self.listbox.SetSelection(-1)
+               if self.alwaysAutoPlace:
+                       self.OnAutoPlace(None)
+
+       def OnAutoPlace(self, e):
+               bestAllowedSize = int(self.machineSize[1])
+               bestArea = self._doAutoPlace(bestAllowedSize)
+               for i in xrange(10, int(self.machineSize[1]), 10):
+                       area = self._doAutoPlace(i)
+                       if area < bestArea:
+                               bestAllowedSize = i
+                               bestArea = area
+               self._doAutoPlace(bestAllowedSize)
+               for item in self.list:
+                       item.clampXY()
+               self.preview.Refresh()
+       
+       def _doAutoPlace(self, allowedSizeY):
+               extraSizeMin, extraSizeMax = self.getExtraHeadSize()
+
+               if extraSizeMin[0] > extraSizeMax[0]:
+                       posX = self.machineSize[0]
+                       dirX = -1
+               else:
+                       posX = 0
+                       dirX = 1
+               posY = 0
+               dirY = 1
+               
+               minX = self.machineSize[0]
+               minY = self.machineSize[1]
+               maxX = 0
+               maxY = 0
+               for item in self.list:
+                       item.centerX = posX + item.getMaximum()[0] * item.scale * dirX
+                       item.centerY = posY + item.getMaximum()[1] * item.scale * dirY
+                       if item.centerY + item.getSize()[1] >= allowedSizeY:
+                               if dirX < 0:
+                                       posX = minX - extraSizeMax[0] - 1
+                               else:
+                                       posX = maxX + extraSizeMin[0] + 1
+                               posY = 0
+                               item.centerX = posX + item.getMaximum()[0] * item.scale * dirX
+                               item.centerY = posY + item.getMaximum()[1] * item.scale * dirY
+                       posY += item.getSize()[1]  * item.scale * dirY + extraSizeMin[1] + 1
+                       minX = min(minX, item.centerX - item.getSize()[0] * item.scale / 2)
+                       minY = min(minY, item.centerY - item.getSize()[1] * item.scale / 2)
+                       maxX = max(maxX, item.centerX + item.getSize()[0] * item.scale / 2)
+                       maxY = max(maxY, item.centerY + item.getSize()[1] * item.scale / 2)
+               
+               for item in self.list:
+                       if dirX < 0:
+                               item.centerX -= minX / 2
+                       else:
+                               item.centerX += (self.machineSize[0] - maxX) / 2
+                       item.centerY += (self.machineSize[1] - maxY) / 2
+               
+               if minX < 0 or maxX > self.machineSize[0]:
+                       return ((maxX - minX) + (maxY - minY)) * 100
+               
+               return (maxX - minX) + (maxY - minY)
+
+       def OnSlice(self, e):
+               dlg=wx.FileDialog(self, "Save project gcode file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
+               dlg.SetWildcard("GCode file (*.gcode)|*.gcode")
+               if dlg.ShowModal() != wx.ID_OK:
+                       dlg.Destroy()
+                       return
+               resultFilename = dlg.GetPath()
+               dlg.Destroy()
+
+               put = profile.setTempOverride
+               oldProfile = profile.getGlobalProfileString()
+               
+               put('add_start_end_gcode', 'False')
+               put('gcode_extension', 'project_tmp')
+               if self.printMode == 0:
+                       clearZ = 0
+                       actionList = []
+                       for item in self.list:
+                               if item.profile != None and os.path.isfile(item.profile):
+                                       profile.loadGlobalProfile(item.profile)
+                               put('object_center_x', item.centerX - self.extruderOffset[item.extruder][0])
+                               put('object_center_y', item.centerY - self.extruderOffset[item.extruder][1])
+                               put('model_scale', item.scale)
+                               put('flip_x', item.flipX)
+                               put('flip_y', item.flipY)
+                               put('flip_z', item.flipZ)
+                               put('model_rotate_base', item.rotate)
+                               put('swap_xz', item.swapXZ)
+                               put('swap_yz', item.swapYZ)
+                               
+                               action = Action()
+                               action.sliceCmd = sliceRun.getSliceCommand(item.filename)
+                               action.centerX = item.centerX
+                               action.centerY = item.centerY
+                               action.temperature = profile.getProfileSettingFloat('print_temperature')
+                               action.extruder = item.extruder
+                               action.filename = item.filename
+                               clearZ = max(clearZ, item.getSize()[2] * item.scale + 5.0)
+                               action.clearZ = clearZ
+                               action.leaveResultForNextSlice = False
+                               action.usePreviousSlice = False
+                               actionList.append(action)
+
+                               if self.list.index(item) > 0 and item.isSameExceptForPosition(self.list[self.list.index(item)-1]):
+                                       actionList[-2].leaveResultForNextSlice = True
+                                       actionList[-1].usePreviousSlice = True
+
+                               if item.profile != None:
+                                       profile.loadGlobalProfileFromString(oldProfile)
+                       
+               else:
+                       self._saveCombinedSTL(resultFilename + "_temp_.stl")
+                       put('model_scale', 1.0)
+                       put('flip_x', False)
+                       put('flip_y', False)
+                       put('flip_z', False)
+                       put('model_rotate_base', 0)
+                       put('swap_xz', False)
+                       put('swap_yz', False)
+                       actionList = []
+                       
+                       action = Action()
+                       action.sliceCmd = sliceRun.getSliceCommand(resultFilename + "_temp_.stl")
+                       action.centerX = profile.getPreferenceFloat('machine_width') / 2
+                       action.centerY = profile.getPreferenceFloat('machine_depth') / 2
+                       action.temperature = profile.getProfileSettingFloat('print_temperature')
+                       action.extruder = 0
+                       action.filename = resultFilename + "_temp_.stl"
+                       action.clearZ = 0
+                       action.leaveResultForNextSlice = False
+                       action.usePreviousSlice = False
+
+                       actionList.append(action)
+               
+               #Restore the old profile.
+               profile.resetTempOverride()
+               
+               pspw = ProjectSliceProgressWindow(actionList, resultFilename)
+               pspw.extruderOffset = self.extruderOffset
+               pspw.Centre()
+               pspw.Show(True)
+       
+       def OnScaleChange(self, e):
+               if self.selection == None:
+                       return
+               try:
+                       self.selection.scale = float(self.scaleCtrl.GetValue())
+               except ValueError:
+                       self.selection.scale = 1.0
+               self.preview.Refresh()
+       
+       def OnRotateChange(self, e):
+               if self.selection == None:
+                       return
+               self.selection.rotate = float(self.rotateCtrl.GetValue())
+               self.selection.updateModelTransform()
+               if self.alwaysAutoPlace:
+                       self.OnAutoPlace(None)
+               self.preview.Refresh()
+
+       def OnExtruderChange(self, e):
+               if self.selection == None:
+                       return
+               self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1
+               self.preview.Refresh()
+               
+       def OnMirrorChange(self):
+               if self.selection == None:
+                       return
+               self.selection.flipX = self.mirrorX.GetValue()
+               self.selection.flipY = self.mirrorY.GetValue()
+               self.selection.flipZ = self.mirrorZ.GetValue()
+               self.selection.swapXZ = self.swapXZ.GetValue()
+               self.selection.swapYZ = self.swapYZ.GetValue()
+               self.selection.updateModelTransform()
+               if self.alwaysAutoPlace:
+                       self.OnAutoPlace(None)
+               self.preview.Refresh()
+
+       def getExtraHeadSize(self):
+               extraSizeMin = self.headSizeMin
+               extraSizeMax = self.headSizeMax
+               if profile.getProfileSettingFloat('skirt_line_count') > 0:
+                       skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap')
+                       extraSizeMin = extraSizeMin + numpy.array([skirtSize, skirtSize, 0])
+                       extraSizeMax = extraSizeMax + numpy.array([skirtSize, skirtSize, 0])
+               if profile.getProfileSetting('enable_raft') != 'False':
+                       raftSize = profile.getProfileSettingFloat('raft_margin') * 2
+                       extraSizeMin = extraSizeMin + numpy.array([raftSize, raftSize, 0])
+                       extraSizeMax = extraSizeMax + numpy.array([raftSize, raftSize, 0])
+               if profile.getProfileSetting('support') != 'None':
+                       extraSizeMin = extraSizeMin + numpy.array([3.0, 0, 0])
+                       extraSizeMax = extraSizeMax + numpy.array([3.0, 0, 0])
+
+               if self.printMode == 1:
+                       extraSizeMin = numpy.array([6.0, 6.0, 0])
+                       extraSizeMax = numpy.array([6.0, 6.0, 0])
+               
+               return extraSizeMin, extraSizeMax
+
+class PreviewGLCanvas(glcanvas.GLCanvas):
+       def __init__(self, parent, projectPlannerWindow):
+               attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
+               glcanvas.GLCanvas.__init__(self, parent, attribList = attribList)
+               self.parent = projectPlannerWindow
+               self.context = glcanvas.GLContext(self)
+               wx.EVT_PAINT(self, self.OnPaint)
+               wx.EVT_SIZE(self, self.OnSize)
+               wx.EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
+               wx.EVT_LEFT_DOWN(self, self.OnMouseLeftDown)
+               wx.EVT_MOTION(self, self.OnMouseMotion)
+               wx.EVT_MOUSEWHEEL(self, self.OnMouseWheel)
+               self.yaw = 30
+               self.pitch = 60
+               self.offsetX = 0
+               self.offsetY = 0
+               self.view3D = self.parent.alwaysAutoPlace
+               if self.view3D:
+                       self.zoom = 300
+               else:
+                       self.zoom = self.parent.machineSize[0] / 2 + 10
+               self.allowDrag = False
+
+               self.objColor = profile.getPreferenceColour('model_colour')
+
+       def OnMouseLeftDown(self,e):
+               self.allowDrag = True
+               if not self.parent.alwaysAutoPlace and not self.view3D:
+                       #TODO: Translate mouse X/Y to 3D X/Y/Z
+                       for item in self.parent.list:
+                               iMin = (item.getMinimum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - self.parent.extruderOffset[item.extruder]
+                               iMax = (item.getMaximum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - self.parent.extruderOffset[item.extruder]
+               
+       def OnMouseMotion(self,e):
+               if self.allowDrag and e.Dragging() and e.LeftIsDown():
+                       if self.view3D:
+                               self.yaw += e.GetX() - self.oldX
+                               self.pitch -= e.GetY() - self.oldY
+                               if self.pitch > 170:
+                                       self.pitch = 170
+                               if self.pitch < 10:
+                                       self.pitch = 10
+                       elif not self.parent.alwaysAutoPlace:
+                               item = self.parent.selection
+                               if item != None:
+                                       item.centerX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2
+                                       item.centerY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2
+                                       item.clampXY()
+                       self.Refresh()
+               else:
+                       self.allowDrag = False
+               if e.Dragging() and e.RightIsDown():
+                       if self.view3D:
+                               self.zoom += e.GetY() - self.oldY
+                               if self.zoom < 1:
+                                       self.zoom = 1
+                       self.Refresh()
+               self.oldX = e.GetX()
+               self.oldY = e.GetY()
+       
+       def OnMouseWheel(self,e):
+               if self.view3D:
+                       self.zoom *= 1.0 - float(e.GetWheelRotation() / e.GetWheelDelta()) / 10.0
+                       if self.zoom < 1.0:
+                               self.zoom = 1.0
+                       self.Refresh()
+       
+       def OnEraseBackground(self,event):
+               #Workaround for windows background redraw flicker.
+               pass
+       
+       def OnSize(self,event):
+               self.Refresh()
+
+       def OnPaint(self,event):
+               dc = wx.PaintDC(self)
+               if not hasOpenGLlibs:
+                       dc.Clear()
+                       dc.DrawText("No PyOpenGL installation found.\nNo preview window available.", 10, 10)
+                       return
+               self.SetCurrent(self.context)
+               opengl.InitGL(self, self.view3D, self.zoom)
+               if self.view3D:
+                       glTranslate(0,0,-self.zoom)
+                       glRotate(-self.pitch, 1,0,0)
+                       glRotate(self.yaw, 0,0,1)
+               else:
+                       glTranslate(self.offsetX, self.offsetY, 0.0)
+               glTranslate(-self.parent.machineSize[0]/2, -self.parent.machineSize[1]/2, 0)
+
+               self.OnDraw()
+               self.SwapBuffers()
+
+       def OnDraw(self):
+               machineSize = self.parent.machineSize
+               extraSizeMin, extraSizeMax = self.parent.getExtraHeadSize()
+
+               for item in self.parent.list:
+                       item.validPlacement = True
+                       item.gotHit = False
+               
+               for idx1 in xrange(0, len(self.parent.list)):
+                       item = self.parent.list[idx1]
+                       iMin1 = (item.getMinimum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) - extraSizeMin - self.parent.extruderOffset[item.extruder]
+                       iMax1 = (item.getMaximum() * item.scale) + numpy.array([item.centerX, item.centerY, 0]) + extraSizeMax - self.parent.extruderOffset[item.extruder]
+                       for idx2 in xrange(0, idx1):
+                               item2 = self.parent.list[idx2]
+                               iMin2 = (item2.getMinimum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])
+                               iMax2 = (item2.getMaximum() * item2.scale) + numpy.array([item2.centerX, item2.centerY, 0])
+                               if item != item2 and iMax1[0] >= iMin2[0] and iMin1[0] <= iMax2[0] and iMax1[1] >= iMin2[1] and iMin1[1] <= iMax2[1]:
+                                       item.validPlacement = False
+                                       item2.gotHit = True
+               
+               seenSelected = False
+               for item in self.parent.list:
+                       if item == self.parent.selection:
+                               seenSelected = True
+                       if item.modelDisplayList == None:
+                               item.modelDisplayList = glGenLists(1);
+                       if item.modelDirty:
+                               item.modelDirty = False
+                               modelSize = item.getMaximum() - item.getMinimum()
+                               glNewList(item.modelDisplayList, GL_COMPILE)
+                               opengl.DrawMesh(item.mesh)
+                               glEndList()
+                       
+                       if item.validPlacement:
+                               if self.parent.selection == item:
+                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  map(lambda x: x + 0.2, self.objColor))
+                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  map(lambda x: x / 2, self.objColor))
+                               else:
+                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  self.objColor)
+                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  map(lambda x: x / 2, self.objColor))
+                       else:
+                               if self.parent.selection == item:
+                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])
+                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])
+                               else:
+                                       glLightfv(GL_LIGHT0, GL_DIFFUSE,  [1.0, 0.0, 0.0, 0.0])
+                                       glLightfv(GL_LIGHT0, GL_AMBIENT,  [0.2, 0.0, 0.0, 0.0])
+                       glPushMatrix()
+                       
+                       glEnable(GL_LIGHTING)
+                       glTranslate(item.centerX, item.centerY, 0)
+                       glPushMatrix()
+                       glScalef(item.scale, item.scale, item.scale)
+                       glCallList(item.modelDisplayList)
+                       glPopMatrix()
+                       
+                       vMin = item.getMinimum() * item.scale
+                       vMax = item.getMaximum() * item.scale
+                       vMinHead = vMin - extraSizeMin - self.parent.extruderOffset[item.extruder]
+                       vMaxHead = vMax + extraSizeMax - self.parent.extruderOffset[item.extruder]
+
+                       glDisable(GL_LIGHTING)
+
+                       if not self.parent.alwaysAutoPlace:
+                               if self.parent.selection == item:
+                                       if item.gotHit:
+                                               glColor3f(1.0,0.0,0.3)
+                                       else:
+                                               glColor3f(1.0,0.0,1.0)
+                                       opengl.DrawBox(vMin, vMax)
+                                       if item.gotHit:
+                                               glColor3f(1.0,0.3,0.0)
+                                       else:
+                                               glColor3f(1.0,1.0,0.0)
+                                       opengl.DrawBox(vMinHead, vMaxHead)
+                               elif seenSelected:
+                                       if item.gotHit:
+                                               glColor3f(0.5,0.0,0.1)
+                                       else:
+                                               glColor3f(0.5,0.0,0.5)
+                                       opengl.DrawBox(vMinHead, vMaxHead)
+                               else:
+                                       if item.gotHit:
+                                               glColor3f(0.7,0.1,0.0)
+                                       else:
+                                               glColor3f(0.7,0.7,0.0)
+                                       opengl.DrawBox(vMin, vMax)
+                       
+                       glPopMatrix()
+               
+               opengl.DrawMachine(util3d.Vector3(machineSize[0], machineSize[1], machineSize[2]))
+               glFlush()
+
+class ProjectSliceProgressWindow(wx.Frame):
+       def __init__(self, actionList, resultFilename):
+               super(ProjectSliceProgressWindow, self).__init__(None, title='Cura')
+               self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
+               
+               self.actionList = actionList
+               self.resultFilename = resultFilename
+               self.abort = False
+               self.prevStep = 'start'
+               self.totalDoneFactor = 0.0
+               self.startTime = time.time()
+               self.sliceStartTime = time.time()
+               
+               self.sizer = wx.GridBagSizer(2, 2) 
+               self.statusText = wx.StaticText(self, -1, "Building: %s" % (resultFilename))
+               self.progressGauge = wx.Gauge(self, -1)
+               self.progressGauge.SetRange(10000)
+               self.progressGauge2 = wx.Gauge(self, -1)
+               self.progressGauge2.SetRange(len(self.actionList))
+               self.abortButton = wx.Button(self, -1, "Abort")
+               self.sizer.Add(self.statusText, (0,0), span=(1,5))
+               self.sizer.Add(self.progressGauge, (1, 0), span=(1,5), flag=wx.EXPAND)
+               self.sizer.Add(self.progressGauge2, (2, 0), span=(1,5), flag=wx.EXPAND)
+
+               self.sizer.Add(self.abortButton, (3,0), span=(1,5), flag=wx.ALIGN_CENTER)
+               self.sizer.AddGrowableCol(0)
+               self.sizer.AddGrowableRow(0)
+
+               self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton)
+               self.SetSizer(self.sizer)
+               self.Layout()
+               self.Fit()
+               
+               threading.Thread(target=self.OnRun).start()
+
+       def OnAbort(self, e):
+               if self.abort:
+                       self.Close()
+               else:
+                       self.abort = True
+                       self.abortButton.SetLabel('Close')
+
+       def SetProgress(self, stepName, layer, maxLayer):
+               if self.prevStep != stepName:
+                       self.totalDoneFactor += sliceRun.sliceStepTimeFactor[self.prevStep]
+                       newTime = time.time()
+                       #print "#####" + str(newTime-self.startTime) + " " + self.prevStep + " -> " + stepName
+                       self.startTime = newTime
+                       self.prevStep = stepName
+               
+               progresValue = ((self.totalDoneFactor + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000
+               self.progressGauge.SetValue(int(progresValue))
+               self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]")
+               taskbar.setProgress(self, 10000 * self.progressGauge2.GetValue() + int(progresValue), 10000 * len(self.actionList))
+       
+       def OnRun(self):
+               resultFile = open(self.resultFilename, "w")
+               put = profile.setTempOverride
+               self.progressLog = []
+               for action in self.actionList:
+                       wx.CallAfter(self.SetTitle, "Building: [%d/%d]"  % (self.actionList.index(action) + 1, len(self.actionList)))
+                       if not action.usePreviousSlice:
+                               p = sliceRun.startSliceCommandProcess(action.sliceCmd)
+                               line = p.stdout.readline()
+               
+                               maxValue = 1
+                               while(len(line) > 0):
+                                       line = line.rstrip()
+                                       if line[0:9] == "Progress[" and line[-1:] == "]":
+                                               progress = line[9:-1].split(":")
+                                               if len(progress) > 2:
+                                                       maxValue = int(progress[2])
+                                               wx.CallAfter(self.SetProgress, progress[0], int(progress[1]), maxValue)
+                                       else:
+                                               self.progressLog.append(line)
+                                               wx.CallAfter(self.statusText.SetLabel, line)
+                                       if self.abort:
+                                               p.terminate()
+                                               wx.CallAfter(self.statusText.SetLabel, "Aborted by user.")
+                                               resultFile.close()
+                                               return
+                                       line = p.stdout.readline()
+                               self.returnCode = p.wait()
+                       
+                       put('object_center_x', action.centerX - self.extruderOffset[action.extruder][0])
+                       put('object_center_y', action.centerY - self.extruderOffset[action.extruder][1])
+                       put('clear_z', action.clearZ)
+                       put('extruder', action.extruder)
+                       put('print_temperature', action.temperature)
+                       
+                       if action == self.actionList[0]:
+                               resultFile.write(';TYPE:CUSTOM\n')
+                               resultFile.write('T%d\n' % (action.extruder))
+                               currentExtruder = action.extruder
+                               prevTemp = action.temperature
+                               startGCode = profile.getAlterationFileContents('start.gcode')
+                               startGCode = startGCode.replace('?filename?', 'Multiple files')
+                               resultFile.write(startGCode)
+                       else:
+                               #reset the extrusion length, and move to the next object center.
+                               resultFile.write(';TYPE:CUSTOM\n')
+                               if prevTemp != action.temperature and action.temperature > 0:
+                                       resultFile.write('M104 S%d\n' % (int(action.temperature)))
+                                       prevTemp = action.temperature
+                               resultFile.write(profile.getAlterationFileContents('nextobject.gcode'))
+                       resultFile.write(';PRINTNR:%d\n' % self.actionList.index(action))
+                       profile.resetTempOverride()
+                       
+                       if not action.usePreviousSlice:
+                               f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")
+                               data = f.read(4096)
+                               while data != '':
+                                       resultFile.write(data)
+                                       data = f.read(4096)
+                               f.close()
+                               savedCenterX = action.centerX
+                               savedCenterY = action.centerY
+                       else:
+                               f = open(sliceRun.getExportFilename(action.filename, "project_tmp"), "r")
+                               for line in f:
+                                       if line[0] != ';':
+                                               if 'X' in line:
+                                                       line = self._adjustNumberInLine(line, 'X', action.centerX - savedCenterX)
+                                               if 'Y' in line:
+                                                       line = self._adjustNumberInLine(line, 'Y', action.centerY - savedCenterY)
+                                       resultFile.write(line)
+                               f.close()
+
+                       if not action.leaveResultForNextSlice:
+                               os.remove(sliceRun.getExportFilename(action.filename, "project_tmp"))
+                       
+                       wx.CallAfter(self.progressGauge.SetValue, 10000)
+                       self.totalDoneFactor = 0.0
+                       wx.CallAfter(self.progressGauge2.SetValue, self.actionList.index(action) + 1)
+               
+               resultFile.write(';TYPE:CUSTOM\n')
+               if len(self.actionList) > 1 and self.actionList[-1].clearZ > 1:
+                       #only move to higher Z if we have sliced more then 1 object. This solves the "move into print after printing" problem with the print-all-at-once option.
+                       resultFile.write('G1 Z%f F%f\n' % (self.actionList[-1].clearZ, profile.getProfileSettingFloat('max_z_speed') * 60))
+               resultFile.write(profile.getAlterationFileContents('end.gcode'))
+               resultFile.close()
+               
+               gcode = gcodeInterpreter.gcode()
+               gcode.load(self.resultFilename)
+               
+               self.abort = True
+               sliceTime = time.time() - self.sliceStartTime
+               status = "Build: %s" % (self.resultFilename)
+               status += "\nSlicing took: %02d:%02d" % (sliceTime / 60, sliceTime % 60)
+               status += "\nFilament: %.2fm %.2fg" % (gcode.extrusionAmount / 1000, gcode.calculateWeight() * 1000)
+               status += "\nPrint time: %02d:%02d" % (int(gcode.totalMoveTimeMinute / 60), int(gcode.totalMoveTimeMinute % 60))
+               cost = gcode.calculateCost()
+               if cost != False:
+                       status += "\nCost: %s" % (cost)
+               profile.replaceGCodeTags(self.resultFilename, gcode)
+               wx.CallAfter(self.statusText.SetLabel, status)
+               wx.CallAfter(self.OnSliceDone)
+       
+       def _adjustNumberInLine(self, line, tag, f):
+               m = re.search('^(.*'+tag+')([0-9\.]*)(.*)$', line)
+               return m.group(1) + str(float(m.group(2)) + f) + m.group(3) + '\n'
+       
+       def OnSliceDone(self):
+               self.abortButton.Destroy()
+               self.closeButton = wx.Button(self, -1, "Close")
+               self.printButton = wx.Button(self, -1, "Print")
+               self.logButton = wx.Button(self, -1, "Show log")
+               self.sizer.Add(self.closeButton, (3,0), span=(1,1))
+               self.sizer.Add(self.printButton, (3,1), span=(1,1))
+               self.sizer.Add(self.logButton, (3,2), span=(1,1))
+               if exporer.hasExporer():
+                       self.openFileLocationButton = wx.Button(self, -1, "Open file location")
+                       self.Bind(wx.EVT_BUTTON, self.OnOpenFileLocation, self.openFileLocationButton)
+                       self.sizer.Add(self.openFileLocationButton, (3,3), span=(1,1))
+               if profile.getPreference('sdpath') != '':
+                       self.copyToSDButton = wx.Button(self, -1, "To SDCard")
+                       self.Bind(wx.EVT_BUTTON, self.OnCopyToSD, self.copyToSDButton)
+                       self.sizer.Add(self.copyToSDButton, (3,4), span=(1,1))
+               self.Bind(wx.EVT_BUTTON, self.OnAbort, self.closeButton)
+               self.Bind(wx.EVT_BUTTON, self.OnPrint, self.printButton)
+               self.Bind(wx.EVT_BUTTON, self.OnShowLog, self.logButton)
+               self.Layout()
+               self.Fit()
+               taskbar.setBusy(self, False)
+
+       def OnCopyToSD(self, e):
+               filename = os.path.basename(self.resultFilename)
+               if profile.getPreference('sdshortnames') == 'True':
+                       filename = sliceRun.getShortFilename(filename)
+               shutil.copy(self.resultFilename, os.path.join(profile.getPreference('sdpath'), filename))
+       
+       def OnOpenFileLocation(self, e):
+               exporer.openExporer(self.resultFilename)
+       
+       def OnPrint(self, e):
+               printWindow.printFile(self.resultFilename)
+
+       def OnShowLog(self, e):
+               LogWindow('\n'.join(self.progressLog))
+
+class preferencesDialog(configBase.configWindowBase):
+       def __init__(self, parent):
+               super(preferencesDialog, self).__init__(title="Project Planner Preferences")
+               
+               self.parent = parent
+               wx.EVT_CLOSE(self, self.OnClose)
+               
+               extruderAmount = int(profile.getPreference('extruder_amount'))
+               
+               left, right, main = self.CreateConfigPanel(self)
+               configBase.TitleRow(left, 'Machine head size')
+               c = configBase.SettingRow(left, 'Head size - X towards home (mm)', 'extruder_head_size_min_x', '0', 'Size of your printer head in the X direction, on the Ultimaker your fan is in this direction.', type = 'preference')
+               validators.validFloat(c, 0.1)
+               c = configBase.SettingRow(left, 'Head size - X towards end (mm)', 'extruder_head_size_max_x', '0', 'Size of your printer head in the X direction.', type = 'preference')
+               validators.validFloat(c, 0.1)
+               c = configBase.SettingRow(left, 'Head size - Y towards home (mm)', 'extruder_head_size_min_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')
+               validators.validFloat(c, 0.1)
+               c = configBase.SettingRow(left, 'Head size - Y towards end (mm)', 'extruder_head_size_max_y', '0', 'Size of your printer head in the Y direction.', type = 'preference')
+               validators.validFloat(c, 0.1)
+               c = configBase.SettingRow(left, 'Head gantry height (mm)', 'extruder_head_size_height', '0', 'The tallest object height that will always fit under your printers gantry system when the printer head is at the lowest Z position.', type = 'preference')
+               validators.validFloat(c)
+               
+               self.okButton = wx.Button(left, -1, 'Ok')
+               left.GetSizer().Add(self.okButton, (left.GetSizer().GetRows(), 1))
+               self.okButton.Bind(wx.EVT_BUTTON, self.OnClose)
+               
+               self.MakeModal(True)
+               main.Fit()
+               self.Fit()
+
+       def OnClose(self, e):
+               self.parent.headSizeMin = numpy.array([profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0])
+               self.parent.headSizeMax = numpy.array([profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0])
+               self.parent.Refresh()
+
+               self.MakeModal(False)
+               self.Destroy()
+
+class LogWindow(wx.Frame):
+       def __init__(self, logText):
+               super(LogWindow, self).__init__(None, title="Slice log")
+               self.textBox = wx.TextCtrl(self, -1, logText, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_READONLY)
+               self.SetSize((400,300))
+               self.Centre()
+               self.Show(True)
+
+def main():
+       app = wx.App(False)
+       projectPlanner().Show(True)
+       app.MainLoop()
+
+if __name__ == '__main__':
+       main()
index a36ff20f519e01dd1e390acb05fc0bfd6e5fdefc..485d427024b7ee3207548fc85e643dcaedbe09c3 100644 (file)
-from __future__ import absolute_import\r
-from __future__ import division\r
-#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.\r
-import __init__\r
-\r
-import os, traceback, math, re, zlib, base64, time, sys, platform, glob, string, stat\r
-import cPickle as pickle\r
-if sys.version_info[0] < 3:\r
-       import ConfigParser\r
-else:\r
-       import configparser as ConfigParser\r
+from __future__ import absolute_import
+from __future__ import division
+#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
+import __init__
+
+import os, traceback, math, re, zlib, base64, time, sys, platform, glob, string, stat
+import cPickle as pickle
+if sys.version_info[0] < 3:
+       import ConfigParser
+else:
+       import configparser as ConfigParser
 
 from util import version
-\r
-#########################################################\r
-## Default settings when none are found.\r
-#########################################################\r
-\r
-#Single place to store the defaults, so we have a consistent set of default settings.\r
-profileDefaultSettings = {\r
-       'nozzle_size': '0.4',\r
-       'layer_height': '0.2',\r
-       'wall_thickness': '0.8',\r
-       'solid_layer_thickness': '0.6',\r
-       'fill_density': '20',\r
-       'skirt_line_count': '1',\r
-       'skirt_gap': '3.0',\r
-       'print_speed': '50',\r
-       'print_temperature': '220',\r
-       'print_bed_temperature': '70',\r
-       'support': 'None',\r
-       'filament_diameter': '2.89',\r
-       'filament_density': '1.00',\r
-       'retraction_min_travel': '5.0',\r
-       'retraction_enable': 'False',\r
-       'retraction_speed': '40.0',\r
-       'retraction_amount': '4.5',\r
-       'retraction_extra': '0.0',\r
-       'retract_on_jumps_only': 'True',\r
-       'travel_speed': '150',\r
-       'max_z_speed': '3.0',\r
-       'bottom_layer_speed': '20',\r
-       'cool_min_layer_time': '5',\r
-       'fan_enabled': 'True',\r
-       'fan_layer': '1',\r
-       'fan_speed': '100',\r
-       'fan_speed_max': '100',\r
-       'model_scale': '1.0',\r
-       'flip_x': 'False',\r
-       'flip_y': 'False',\r
-       'flip_z': 'False',\r
-       'swap_xz': 'False',\r
-       'swap_yz': 'False',\r
-       'model_rotate_base': '0',\r
-       'model_multiply_x': '1',\r
-       'model_multiply_y': '1',\r
-       'extra_base_wall_thickness': '0.0',\r
-       'sequence': 'Loops > Perimeter > Infill',\r
-       'force_first_layer_sequence': 'True',\r
-       'infill_type': 'Line',\r
-       'solid_top': 'True',\r
-       'fill_overlap': '15',\r
-       'support_rate': '50',\r
-       'support_distance': '0.5',\r
-       'support_dual_extrusion': 'False',\r
-       'joris': 'False',\r
-       'enable_skin': 'False',\r
-       'enable_raft': 'False',\r
-       'cool_min_feedrate': '10',\r
-       'bridge_speed': '100',\r
-       'raft_margin': '5',\r
-       'raft_base_material_amount': '100',\r
-       'raft_interface_material_amount': '100',\r
-       'bottom_thickness': '0.3',\r
-       'hop_on_move': 'False',\r
-       'plugin_config': '',\r
-       \r
-       'add_start_end_gcode': 'True',\r
-       'gcode_extension': 'gcode',\r
-       'alternative_center': '',\r
-       'clear_z': '0.0',\r
-       'extruder': '0',\r
-}\r
-alterationDefault = {\r
-#######################################################################################\r
-       'start.gcode': """;Sliced {filename} at: {day} {date} {time}\r
-;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\r
-;Print time: {print_time}\r
-;Filament used: {filament_amount}m {filament_weight}g\r
-;Filament cost: {filament_cost}\r
-G21        ;metric values\r
-G90        ;absolute positioning\r
-M107       ;start with the fan off\r
-\r
-G28 X0 Y0  ;move X/Y to min endstops\r
-G28 Z0     ;move Z to min endstops\r
-G92 X0 Y0 Z0 E0         ;reset software position to front/left/z=0.0\r
-\r
-G1 Z15.0 F{max_z_speed} ;move the platform down 15mm\r
-\r
-G92 E0                  ;zero the extruded length\r
-G1 F200 E3              ;extrude 3mm of feed stock\r
-G92 E0                  ;zero the extruded length again\r
-G1 F{travel_speed}\r
-""",\r
-#######################################################################################\r
-       'end.gcode': """;End GCode\r
-M104 S0                     ;extruder heater off\r
-M140 S0                     ;heated bed heater off (if you have it)\r
-\r
-G91                                    ;relative positioning\r
-G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure\r
-G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\r
-G28 X0 Y0                              ;move X/Y to min endstops, so the head is out of the way\r
-\r
-M84                         ;steppers off\r
-G90                         ;absolute positioning\r
-""",\r
-#######################################################################################\r
-       'support_start.gcode': '',\r
-       'support_end.gcode': '',\r
-       'cool_start.gcode': '',\r
-       'cool_end.gcode': '',\r
-       'replace.csv': '',\r
-#######################################################################################\r
-       'nextobject.gcode': """;Move to next object on the platform. clear_z is the minimal z height we need to make sure we do not hit any objects.\r
-G92 E0\r
-\r
-G91                                    ;relative positioning\r
-G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure\r
-G1 Z+0.5 E-5 F{travel_speed}           ;move Z up a bit and retract filament even more\r
-G90                                    ;absolute positioning\r
-\r
-G1 Z{clear_z} F{max_z_speed}\r
-G92 E0\r
-G1 X{object_center_x} Y{object_center_x} F{travel_speed}\r
-G1 F200 E6\r
-G92 E0\r
-""",\r
-#######################################################################################\r
-       'switchExtruder.gcode': """;Switch between the current extruder and the next extruder, when printing with multiple extruders.\r
-G92 E0\r
-G1 E-5 F5000\r
-G92 E0\r
-T{extruder}\r
-G1 E5 F5000\r
-G92 E0\r
-""",\r
-}\r
-preferencesDefaultSettings = {\r
-       'startMode': 'Simple',\r
-       'lastFile': os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'example', 'UltimakerRobot_support.stl')),\r
-       'machine_width': '205',\r
-       'machine_depth': '205',\r
-       'machine_height': '200',\r
-       'machine_type': 'unknown',\r
-       'ultimaker_extruder_upgrade': 'False',\r
-       'has_heated_bed': 'False',\r
-       'extruder_amount': '1',\r
-       'extruder_offset_x1': '-22.0',\r
-       'extruder_offset_y1': '0.0',\r
-       'extruder_offset_x2': '0.0',\r
-       'extruder_offset_y2': '0.0',\r
-       'extruder_offset_x3': '0.0',\r
-       'extruder_offset_y3': '0.0',\r
-       'filament_density': '1300',\r
-       'steps_per_e': '0',\r
-       'serial_port': 'AUTO',\r
-       'serial_port_auto': '',\r
-       'serial_baud': 'AUTO',\r
-       'serial_baud_auto': '',\r
-       'slicer': 'Cura (Skeinforge based)',\r
-       'save_profile': 'False',\r
-       'filament_cost_kg': '0',\r
-       'filament_cost_meter': '0',\r
-       'sdpath': '',\r
-       'sdshortnames': 'True',\r
-       \r
-       'extruder_head_size_min_x': '70.0',\r
-       'extruder_head_size_min_y': '18.0',\r
-       'extruder_head_size_max_x': '18.0',\r
-       'extruder_head_size_max_y': '35.0',\r
-       'extruder_head_size_height': '80.0',\r
-       \r
-       'model_colour': '#72CB30',\r
-       'model_colour2': '#CB3030',\r
-       'model_colour3': '#DDD93C',\r
-       'model_colour4': '#4550D3',\r
-}\r
-\r
-#########################################################\r
-## Profile and preferences functions\r
-#########################################################\r
-\r
-## Profile functions\r
-def getDefaultProfilePath():\r
+
+#########################################################
+## Default settings when none are found.
+#########################################################
+
+#Single place to store the defaults, so we have a consistent set of default settings.
+profileDefaultSettings = {
+       'nozzle_size': '0.4',
+       'layer_height': '0.2',
+       'wall_thickness': '0.8',
+       'solid_layer_thickness': '0.6',
+       'fill_density': '20',
+       'skirt_line_count': '1',
+       'skirt_gap': '3.0',
+       'print_speed': '50',
+       'print_temperature': '220',
+       'print_bed_temperature': '70',
+       'support': 'None',
+       'filament_diameter': '2.89',
+       'filament_density': '1.00',
+       'retraction_min_travel': '5.0',
+       'retraction_enable': 'False',
+       'retraction_speed': '40.0',
+       'retraction_amount': '4.5',
+       'retraction_extra': '0.0',
+       'retract_on_jumps_only': 'True',
+       'travel_speed': '150',
+       'max_z_speed': '3.0',
+       'bottom_layer_speed': '20',
+       'cool_min_layer_time': '5',
+       'fan_enabled': 'True',
+       'fan_layer': '1',
+       'fan_speed': '100',
+       'fan_speed_max': '100',
+       'model_scale': '1.0',
+       'flip_x': 'False',
+       'flip_y': 'False',
+       'flip_z': 'False',
+       'swap_xz': 'False',
+       'swap_yz': 'False',
+       'model_rotate_base': '0',
+       'model_multiply_x': '1',
+       'model_multiply_y': '1',
+       'extra_base_wall_thickness': '0.0',
+       'sequence': 'Loops > Perimeter > Infill',
+       'force_first_layer_sequence': 'True',
+       'infill_type': 'Line',
+       'solid_top': 'True',
+       'fill_overlap': '15',
+       'support_rate': '50',
+       'support_distance': '0.5',
+       'support_dual_extrusion': 'False',
+       'joris': 'False',
+       'enable_skin': 'False',
+       'enable_raft': 'False',
+       'cool_min_feedrate': '10',
+       'bridge_speed': '100',
+       'raft_margin': '5',
+       'raft_base_material_amount': '100',
+       'raft_interface_material_amount': '100',
+       'bottom_thickness': '0.3',
+       'hop_on_move': 'False',
+       'plugin_config': '',
+       
+       'add_start_end_gcode': 'True',
+       'gcode_extension': 'gcode',
+       'alternative_center': '',
+       'clear_z': '0.0',
+       'extruder': '0',
+}
+alterationDefault = {
+#######################################################################################
+       'start.gcode': """;Sliced {filename} at: {day} {date} {time}
+;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
+;Print time: {print_time}
+;Filament used: {filament_amount}m {filament_weight}g
+;Filament cost: {filament_cost}
+G21        ;metric values
+G90        ;absolute positioning
+M107       ;start with the fan off
+
+G28 X0 Y0  ;move X/Y to min endstops
+G28 Z0     ;move Z to min endstops
+G92 X0 Y0 Z0 E0         ;reset software position to front/left/z=0.0
+
+G1 Z15.0 F{max_z_speed} ;move the platform down 15mm
+
+G92 E0                  ;zero the extruded length
+G1 F200 E3              ;extrude 3mm of feed stock
+G92 E0                  ;zero the extruded length again
+G1 F{travel_speed}
+""",
+#######################################################################################
+       'end.gcode': """;End GCode
+M104 S0                     ;extruder heater off
+M140 S0                     ;heated bed heater off (if you have it)
+
+G91                                    ;relative positioning
+G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure
+G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
+G28 X0 Y0                              ;move X/Y to min endstops, so the head is out of the way
+
+M84                         ;steppers off
+G90                         ;absolute positioning
+""",
+#######################################################################################
+       'support_start.gcode': '',
+       'support_end.gcode': '',
+       'cool_start.gcode': '',
+       'cool_end.gcode': '',
+       'replace.csv': '',
+#######################################################################################
+       'nextobject.gcode': """;Move to next object on the platform. clear_z is the minimal z height we need to make sure we do not hit any objects.
+G92 E0
+
+G91                                    ;relative positioning
+G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure
+G1 Z+0.5 E-5 F{travel_speed}           ;move Z up a bit and retract filament even more
+G90                                    ;absolute positioning
+
+G1 Z{clear_z} F{max_z_speed}
+G92 E0
+G1 X{object_center_x} Y{object_center_x} F{travel_speed}
+G1 F200 E6
+G92 E0
+""",
+#######################################################################################
+       'switchExtruder.gcode': """;Switch between the current extruder and the next extruder, when printing with multiple extruders.
+G92 E0
+G1 E-5 F5000
+G92 E0
+T{extruder}
+G1 E5 F5000
+G92 E0
+""",
+}
+preferencesDefaultSettings = {
+       'startMode': 'Simple',
+       'lastFile': os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'example', 'UltimakerRobot_support.stl')),
+       'machine_width': '205',
+       'machine_depth': '205',
+       'machine_height': '200',
+       'machine_type': 'unknown',
+       'ultimaker_extruder_upgrade': 'False',
+       'has_heated_bed': 'False',
+       'extruder_amount': '1',
+       'extruder_offset_x1': '-22.0',
+       'extruder_offset_y1': '0.0',
+       'extruder_offset_x2': '0.0',
+       'extruder_offset_y2': '0.0',
+       'extruder_offset_x3': '0.0',
+       'extruder_offset_y3': '0.0',
+       'filament_density': '1300',
+       'steps_per_e': '0',
+       'serial_port': 'AUTO',
+       'serial_port_auto': '',
+       'serial_baud': 'AUTO',
+       'serial_baud_auto': '',
+       'slicer': 'Cura (Skeinforge based)',
+       'save_profile': 'False',
+       'filament_cost_kg': '0',
+       'filament_cost_meter': '0',
+       'sdpath': '',
+       'sdshortnames': 'True',
+       
+       'extruder_head_size_min_x': '70.0',
+       'extruder_head_size_min_y': '18.0',
+       'extruder_head_size_max_x': '18.0',
+       'extruder_head_size_max_y': '35.0',
+       'extruder_head_size_height': '80.0',
+       
+       'model_colour': '#72CB30',
+       'model_colour2': '#CB3030',
+       'model_colour3': '#DDD93C',
+       'model_colour4': '#4550D3',
+}
+
+#########################################################
+## Profile and preferences functions
+#########################################################
+
+## Profile functions
+def getDefaultProfilePath():
        if platform.system() == "Windows":
-               basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))\r
-               #If we have a frozen python install, we need to step out of the library.zip\r
-               if hasattr(sys, 'frozen'):\r
+               basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
+               #If we have a frozen python install, we need to step out of the library.zip
+               if hasattr(sys, 'frozen'):
                        basePath = os.path.normpath(os.path.join(basePath, ".."))
        else:
                basePath = os.path.expanduser('~/.cura/%s' % version.getVersion(False))
        if not os.path.isdir(basePath):
-               os.makedirs(basePath)\r
-       return os.path.join(basePath, 'current_profile.ini')\r
-\r
-def loadGlobalProfile(filename):\r
-       #Read a configuration file as global config\r
-       global globalProfileParser\r
-       globalProfileParser = ConfigParser.ConfigParser()\r
-       globalProfileParser.read(filename)\r
-\r
-def resetGlobalProfile():\r
-       #Read a configuration file as global config\r
-       global globalProfileParser\r
-       globalProfileParser = ConfigParser.ConfigParser()\r
-\r
-       if getPreference('machine_type') == 'ultimaker':\r
-               putProfileSetting('nozzle_size', '0.4')\r
-               if getPreference('ultimaker_extruder_upgrade') == 'True':\r
-                       putProfileSetting('retraction_enable', 'True')\r
-       else:\r
-               putProfileSetting('nozzle_size', '0.5')\r
-\r
-def saveGlobalProfile(filename):\r
-       #Save the current profile to an ini file\r
-       globalProfileParser.write(open(filename, 'w'))\r
-\r
-def loadGlobalProfileFromString(options):\r
-       global globalProfileParser\r
-       globalProfileParser = ConfigParser.ConfigParser()\r
-       globalProfileParser.add_section('profile')\r
-       globalProfileParser.add_section('alterations')\r
-       options = base64.b64decode(options)\r
-       options = zlib.decompress(options)\r
-       (profileOpts, alt) = options.split('\f', 1)\r
-       for option in profileOpts.split('\b'):\r
-               if len(option) > 0:\r
-                       (key, value) = option.split('=', 1)\r
-                       globalProfileParser.set('profile', key, value)\r
-       for option in alt.split('\b'):\r
-               if len(option) > 0:\r
-                       (key, value) = option.split('=', 1)\r
-                       globalProfileParser.set('alterations', key, value)\r
-\r
-def getGlobalProfileString():\r
-       global globalProfileParser\r
-       if not globals().has_key('globalProfileParser'):\r
-               loadGlobalProfile(getDefaultProfilePath())\r
-       \r
-       p = []\r
-       alt = []\r
-       tempDone = []\r
-       if globalProfileParser.has_section('profile'):\r
-               for key in globalProfileParser.options('profile'):\r
-                       if key in tempOverride:\r
-                               p.append(key + "=" + tempOverride[key])\r
-                               tempDone.append(key)\r
-                       else:\r
-                               p.append(key + "=" + globalProfileParser.get('profile', key))\r
-       if globalProfileParser.has_section('alterations'):\r
-               for key in globalProfileParser.options('alterations'):\r
-                       if key in tempOverride:\r
-                               p.append(key + "=" + tempOverride[key])\r
-                               tempDone.append(key)\r
-                       else:\r
-                               alt.append(key + "=" + globalProfileParser.get('alterations', key))\r
-       for key in tempOverride:\r
-               if key not in tempDone:\r
-                       p.append(key + "=" + tempOverride[key])\r
-       ret = '\b'.join(p) + '\f' + '\b'.join(alt)\r
-       ret = base64.b64encode(zlib.compress(ret, 9))\r
-       return ret\r
-\r
-def getProfileSetting(name):\r
-       if name in tempOverride:\r
-               return unicode(tempOverride[name], "utf-8")\r
-       #Check if we have a configuration file loaded, else load the default.\r
-       if not globals().has_key('globalProfileParser'):\r
-               loadGlobalProfile(getDefaultProfilePath())\r
-       if not globalProfileParser.has_option('profile', name):\r
-               if name in profileDefaultSettings:\r
-                       default = profileDefaultSettings[name]\r
-               else:\r
-                       print("Missing default setting for: '" + name + "'")\r
-                       profileDefaultSettings[name] = ''\r
-                       default = ''\r
-               if not globalProfileParser.has_section('profile'):\r
-                       globalProfileParser.add_section('profile')\r
-               globalProfileParser.set('profile', name, str(default))\r
-               #print(name + " not found in profile, so using default: " + str(default))\r
-               return default\r
-       return globalProfileParser.get('profile', name)\r
-\r
-def getProfileSettingFloat(name):\r
-       try:\r
-               setting = getProfileSetting(name).replace(',', '.')\r
-               return float(eval(setting, {}, {}))\r
-       except (ValueError, SyntaxError, TypeError):\r
-               return 0.0\r
-\r
-def putProfileSetting(name, value):\r
-       #Check if we have a configuration file loaded, else load the default.\r
-       if not globals().has_key('globalProfileParser'):\r
-               loadGlobalProfile(getDefaultProfilePath())\r
-       if not globalProfileParser.has_section('profile'):\r
-               globalProfileParser.add_section('profile')\r
-       globalProfileParser.set('profile', name, str(value))\r
-\r
-def isProfileSetting(name):\r
-       if name in profileDefaultSettings:\r
-               return True\r
-       return False\r
-\r
-## Preferences functions\r
-global globalPreferenceParser\r
-globalPreferenceParser = None\r
-\r
-def getPreferencePath():\r
+               os.makedirs(basePath)
+       return os.path.join(basePath, 'current_profile.ini')
+
+def loadGlobalProfile(filename):
+       #Read a configuration file as global config
+       global globalProfileParser
+       globalProfileParser = ConfigParser.ConfigParser()
+       globalProfileParser.read(filename)
+
+def resetGlobalProfile():
+       #Read a configuration file as global config
+       global globalProfileParser
+       globalProfileParser = ConfigParser.ConfigParser()
+
+       if getPreference('machine_type') == 'ultimaker':
+               putProfileSetting('nozzle_size', '0.4')
+               if getPreference('ultimaker_extruder_upgrade') == 'True':
+                       putProfileSetting('retraction_enable', 'True')
+       else:
+               putProfileSetting('nozzle_size', '0.5')
+
+def saveGlobalProfile(filename):
+       #Save the current profile to an ini file
+       globalProfileParser.write(open(filename, 'w'))
+
+def loadGlobalProfileFromString(options):
+       global globalProfileParser
+       globalProfileParser = ConfigParser.ConfigParser()
+       globalProfileParser.add_section('profile')
+       globalProfileParser.add_section('alterations')
+       options = base64.b64decode(options)
+       options = zlib.decompress(options)
+       (profileOpts, alt) = options.split('\f', 1)
+       for option in profileOpts.split('\b'):
+               if len(option) > 0:
+                       (key, value) = option.split('=', 1)
+                       globalProfileParser.set('profile', key, value)
+       for option in alt.split('\b'):
+               if len(option) > 0:
+                       (key, value) = option.split('=', 1)
+                       globalProfileParser.set('alterations', key, value)
+
+def getGlobalProfileString():
+       global globalProfileParser
+       if not globals().has_key('globalProfileParser'):
+               loadGlobalProfile(getDefaultProfilePath())
+       
+       p = []
+       alt = []
+       tempDone = []
+       if globalProfileParser.has_section('profile'):
+               for key in globalProfileParser.options('profile'):
+                       if key in tempOverride:
+                               p.append(key + "=" + tempOverride[key])
+                               tempDone.append(key)
+                       else:
+                               p.append(key + "=" + globalProfileParser.get('profile', key))
+       if globalProfileParser.has_section('alterations'):
+               for key in globalProfileParser.options('alterations'):
+                       if key in tempOverride:
+                               p.append(key + "=" + tempOverride[key])
+                               tempDone.append(key)
+                       else:
+                               alt.append(key + "=" + globalProfileParser.get('alterations', key))
+       for key in tempOverride:
+               if key not in tempDone:
+                       p.append(key + "=" + tempOverride[key])
+       ret = '\b'.join(p) + '\f' + '\b'.join(alt)
+       ret = base64.b64encode(zlib.compress(ret, 9))
+       return ret
+
+def getProfileSetting(name):
+       if name in tempOverride:
+               return unicode(tempOverride[name], "utf-8")
+       #Check if we have a configuration file loaded, else load the default.
+       if not globals().has_key('globalProfileParser'):
+               loadGlobalProfile(getDefaultProfilePath())
+       if not globalProfileParser.has_option('profile', name):
+               if name in profileDefaultSettings:
+                       default = profileDefaultSettings[name]
+               else:
+                       print("Missing default setting for: '" + name + "'")
+                       profileDefaultSettings[name] = ''
+                       default = ''
+               if not globalProfileParser.has_section('profile'):
+                       globalProfileParser.add_section('profile')
+               globalProfileParser.set('profile', name, str(default))
+               #print(name + " not found in profile, so using default: " + str(default))
+               return default
+       return globalProfileParser.get('profile', name)
+
+def getProfileSettingFloat(name):
+       try:
+               setting = getProfileSetting(name).replace(',', '.')
+               return float(eval(setting, {}, {}))
+       except (ValueError, SyntaxError, TypeError):
+               return 0.0
+
+def putProfileSetting(name, value):
+       #Check if we have a configuration file loaded, else load the default.
+       if not globals().has_key('globalProfileParser'):
+               loadGlobalProfile(getDefaultProfilePath())
+       if not globalProfileParser.has_section('profile'):
+               globalProfileParser.add_section('profile')
+       globalProfileParser.set('profile', name, str(value))
+
+def isProfileSetting(name):
+       if name in profileDefaultSettings:
+               return True
+       return False
+
+## Preferences functions
+global globalPreferenceParser
+globalPreferenceParser = None
+
+def getPreferencePath():
        if platform.system() == "Windows":
-               basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))\r
-               #If we have a frozen python install, we need to step out of the library.zip\r
-               if hasattr(sys, 'frozen'):\r
+               basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
+               #If we have a frozen python install, we need to step out of the library.zip
+               if hasattr(sys, 'frozen'):
                        basePath = os.path.normpath(os.path.join(basePath, ".."))
        else:
                basePath = os.path.expanduser('~/.cura/%s' % version.getVersion(False))
        if not os.path.isdir(basePath):
-               os.makedirs(basePath)\r
-       return os.path.join(basePath, 'preferences.ini')\r
-\r
-def getPreferenceFloat(name):\r
-       try:\r
-               setting = getPreference(name).replace(',', '.')\r
-               return float(eval(setting, {}, {}))\r
-       except (ValueError, SyntaxError, TypeError):\r
-               return 0.0\r
-\r
-def getPreferenceColour(name):\r
-       colorString = getPreference(name)\r
-       return [float(int(colorString[1:3], 16)) / 255, float(int(colorString[3:5], 16)) / 255, float(int(colorString[5:7], 16)) / 255, 1.0]\r
-\r
-def getPreference(name):\r
-       if name in tempOverride:\r
-               return unicode(tempOverride[name])\r
-       global globalPreferenceParser\r
-       if globalPreferenceParser == None:\r
-               globalPreferenceParser = ConfigParser.ConfigParser()\r
-               globalPreferenceParser.read(getPreferencePath())\r
-       if not globalPreferenceParser.has_option('preference', name):\r
-               if name in preferencesDefaultSettings:\r
-                       default = preferencesDefaultSettings[name]\r
-               else:\r
-                       print("Missing default setting for: '" + name + "'")\r
-                       preferencesDefaultSettings[name] = ''\r
-                       default = ''\r
-               if not globalPreferenceParser.has_section('preference'):\r
-                       globalPreferenceParser.add_section('preference')\r
-               globalPreferenceParser.set('preference', name, str(default))\r
-               #print(name + " not found in preferences, so using default: " + str(default))\r
-               return default\r
-       return unicode(globalPreferenceParser.get('preference', name), "utf-8")\r
-\r
-def putPreference(name, value):\r
-       #Check if we have a configuration file loaded, else load the default.\r
-       global globalPreferenceParser\r
-       if globalPreferenceParser == None:\r
-               globalPreferenceParser = ConfigParser.ConfigParser()\r
-               globalPreferenceParser.read(getPreferencePath())\r
-       if not globalPreferenceParser.has_section('preference'):\r
-               globalPreferenceParser.add_section('preference')\r
-       globalPreferenceParser.set('preference', name, unicode(value).encode("utf-8"))\r
-       globalPreferenceParser.write(open(getPreferencePath(), 'w'))\r
-\r
-def isPreference(name):\r
-       if name in preferencesDefaultSettings:\r
-               return True\r
-       return False\r
-\r
-## Temp overrides for multi-extruder slicing and the project planner.\r
-tempOverride = {}\r
-def setTempOverride(name, value):\r
-       tempOverride[name] = unicode(value).encode("utf-8")\r
-def clearTempOverride(name):\r
-       del tempOverride[name]\r
-def resetTempOverride():\r
-       tempOverride.clear()\r
-\r
-#########################################################\r
-## Utility functions to calculate common profile values\r
-#########################################################\r
-def calculateEdgeWidth():\r
-       wallThickness = getProfileSettingFloat('wall_thickness')\r
-       nozzleSize = getProfileSettingFloat('nozzle_size')\r
-       \r
-       if wallThickness < nozzleSize:\r
-               return wallThickness\r
-\r
-       lineCount = int(wallThickness / nozzleSize)\r
-       lineWidth = wallThickness / lineCount\r
-       lineWidthAlt = wallThickness / (lineCount + 1)\r
-       if lineWidth > nozzleSize * 1.5:\r
-               return lineWidthAlt\r
-       return lineWidth\r
-\r
-def calculateLineCount():\r
-       wallThickness = getProfileSettingFloat('wall_thickness')\r
-       nozzleSize = getProfileSettingFloat('nozzle_size')\r
-       \r
-       if wallThickness < nozzleSize:\r
-               return 1\r
-\r
-       lineCount = int(wallThickness / nozzleSize + 0.0001)\r
-       lineWidth = wallThickness / lineCount\r
-       lineWidthAlt = wallThickness / (lineCount + 1)\r
-       if lineWidth > nozzleSize * 1.5:\r
-               return lineCount + 1\r
-       return lineCount\r
-\r
-def calculateSolidLayerCount():\r
-       layerHeight = getProfileSettingFloat('layer_height')\r
-       solidThickness = getProfileSettingFloat('solid_layer_thickness')\r
-       return int(math.ceil(solidThickness / layerHeight - 0.0001))\r
-\r
-#########################################################\r
-## Alteration file functions\r
-#########################################################\r
-def replaceTagMatch(m):\r
-       pre = m.group(1)\r
-       tag = m.group(2)\r
-       if tag == 'time':\r
-               return pre + time.strftime('%H:%M:%S')\r
-       if tag == 'date':\r
-               return pre + time.strftime('%d %b %Y')\r
-       if tag == 'day':\r
-               return pre + time.strftime('%a')\r
-       if tag == 'print_time':\r
-               return pre + '#P_TIME#'\r
-       if tag == 'filament_amount':\r
-               return pre + '#F_AMNT#'\r
-       if tag == 'filament_weight':\r
-               return pre + '#F_WGHT#'\r
-       if tag == 'filament_cost':\r
-               return pre + '#F_COST#'\r
-       if pre == 'F' and tag in ['print_speed', 'retraction_speed', 'travel_speed', 'max_z_speed', 'bottom_layer_speed', 'cool_min_feedrate']:\r
-               f = getProfileSettingFloat(tag) * 60\r
-       elif isProfileSetting(tag):\r
-               f = getProfileSettingFloat(tag)\r
-       elif isPreference(tag):\r
-               f = getProfileSettingFloat(tag)\r
-       else:\r
-               return '%s?%s?' % (pre, tag)\r
-       if (f % 1) == 0:\r
-               return pre + str(int(f))\r
-       return pre + str(f)\r
-\r
-def replaceGCodeTags(filename, gcodeInt):\r
-       f = open(filename, 'r+')\r
-       data = f.read(2048)\r
-       data = data.replace('#P_TIME#', ('%5d:%02d' % (int(gcodeInt.totalMoveTimeMinute / 60), int(gcodeInt.totalMoveTimeMinute % 60)))[-8:])\r
-       data = data.replace('#F_AMNT#', ('%8.2f' % (gcodeInt.extrusionAmount / 1000))[-8:])\r
-       data = data.replace('#F_WGHT#', ('%8.2f' % (gcodeInt.calculateWeight() * 1000))[-8:])\r
-       cost = gcodeInt.calculateCost()\r
-       if cost == False:\r
-               cost = 'Unknown'\r
-       data = data.replace('#F_COST#', ('%8s' % (cost.split(' ')[0]))[-8:])\r
-       f.seek(0)\r
-       f.write(data)\r
-       f.close()\r
-\r
-### Get aleration raw contents. (Used internally in Cura)\r
-def getAlterationFile(filename):\r
-       #Check if we have a configuration file loaded, else load the default.\r
-       if not globals().has_key('globalProfileParser'):\r
-               loadGlobalProfile(getDefaultProfilePath())\r
-       \r
-       if not globalProfileParser.has_option('alterations', filename):\r
-               if filename in alterationDefault:\r
-                       default = alterationDefault[filename]\r
-               else:\r
-                       print("Missing default alteration for: '" + filename + "'")\r
-                       alterationDefault[filename] = ''\r
-                       default = ''\r
-               if not globalProfileParser.has_section('alterations'):\r
-                       globalProfileParser.add_section('alterations')\r
-               #print("Using default for: %s" % (filename))\r
-               globalProfileParser.set('alterations', filename, default)\r
-       return unicode(globalProfileParser.get('alterations', filename), "utf-8")\r
-\r
-def setAlterationFile(filename, value):\r
-       #Check if we have a configuration file loaded, else load the default.\r
-       if not globals().has_key('globalProfileParser'):\r
-               loadGlobalProfile(getDefaultProfilePath())\r
-       if not globalProfileParser.has_section('alterations'):\r
-               globalProfileParser.add_section('alterations')\r
-       globalProfileParser.set('alterations', filename, value.encode("utf-8"))\r
-       saveGlobalProfile(getDefaultProfilePath())\r
-\r
-### Get the alteration file for output. (Used by Skeinforge)\r
-def getAlterationFileContents(filename):\r
-       prefix = ''\r
-       postfix = ''\r
-       alterationContents = getAlterationFile(filename)\r
-       if filename == 'start.gcode':\r
-               #For the start code, hack the temperature and the steps per E value into it. So the temperature is reached before the start code extrusion.\r
-               #We also set our steps per E here, if configured.\r
-               eSteps = getPreferenceFloat('steps_per_e')\r
-               if eSteps > 0:\r
-                       prefix += 'M92 E%f\n' % (eSteps)\r
-               temp = getProfileSettingFloat('print_temperature')\r
-               bedTemp = 0\r
-               if getPreference('has_heated_bed') == 'True':\r
-                       bedTemp = getProfileSettingFloat('print_bed_temperature')\r
-               \r
-               if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:\r
-                       prefix += 'M140 S%f\n' % (bedTemp)\r
-               if temp > 0 and not '{print_temperature}' in alterationContents:\r
-                       prefix += 'M109 S%f\n' % (temp)\r
-               if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:\r
-                       prefix += 'M190 S%f\n' % (bedTemp)\r
-       elif filename == 'end.gcode':\r
-               #Append the profile string to the end of the GCode, so we can load it from the GCode file later.\r
-               postfix = ';CURA_PROFILE_STRING:%s\n' % (getGlobalProfileString())\r
-       elif filename == 'replace.csv':\r
-               #Always remove the extruder on/off M codes. These are no longer needed in 5D printing.\r
-               prefix = 'M101\nM103\n'\r
-       elif filename == 'support_start.gcode' or filename == 'support_end.gcode':\r
-               #Add support start/end code \r
-               if getProfileSetting('support_dual_extrusion') == 'True' and int(getPreference('extruder_amount')) > 1:\r
-                       if filename == 'support_start.gcode':\r
-                               setTempOverride('extruder', '1')\r
-                       else:\r
-                               setTempOverride('extruder', '0')\r
-                       alterationContents = getAlterationFileContents('switchExtruder.gcode')\r
-                       clearTempOverride('extruder')\r
-               else:\r
-                       alterationContents = ''\r
-       return unicode(prefix + re.sub("(.)\{([^\}]*)\}", replaceTagMatch, alterationContents).rstrip() + '\n' + postfix).strip().encode('utf-8')\r
-\r
-###### PLUGIN #####\r
-\r
-def getPluginConfig():\r
-       try:\r
-               return pickle.loads(getProfileSetting('plugin_config'))\r
-       except:\r
-               return []\r
-\r
-def setPluginConfig(config):\r
-       putProfileSetting('plugin_config', pickle.dumps(config))\r
-\r
-def getPluginBasePaths():\r
-       ret = []\r
-       if platform.system() != "Windows":\r
-               ret.append(os.path.expanduser('~/.cura/plugins/'))\r
-       ret.append(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'plugins')))\r
-       return ret\r
-\r
-def getPluginList():\r
-       ret = []\r
-       for basePath in getPluginBasePaths():\r
-               for filename in glob.glob(os.path.join(basePath, '*.py')):\r
-                       filename = os.path.basename(filename)\r
-                       if filename.startswith('_'):\r
-                               continue\r
-                       with open(os.path.join(basePath, filename), "r") as f:\r
-                               item = {'filename': filename, 'name': None, 'info': None, 'type': None, 'params': []}\r
-                               for line in f:\r
-                                       line = line.strip()\r
-                                       if not line.startswith('#'):\r
-                                               break\r
-                                       line = line[1:].split(':', 1)\r
-                                       if len(line) != 2:\r
-                                               continue\r
-                                       if line[0].upper() == 'NAME':\r
-                                               item['name'] = line[1].strip()\r
-                                       elif line[0].upper() == 'INFO':\r
-                                               item['info'] = line[1].strip()\r
-                                       elif line[0].upper() == 'TYPE':\r
-                                               item['type'] = line[1].strip()\r
-                                       elif line[0].upper() == 'DEPEND':\r
-                                               pass\r
-                                       elif line[0].upper() == 'PARAM':\r
-                                               m = re.match('([a-zA-Z]*)\(([a-zA-Z_]*)(?:\:([^\)]*))?\) +(.*)', line[1].strip())\r
-                                               if m != None:\r
-                                                       item['params'].append({'name': m.group(1), 'type': m.group(2), 'default': m.group(3), 'description': m.group(4)})\r
-                                       else:\r
-                                               print "Unknown item in effect meta data: %s %s" % (line[0], line[1])\r
-                               if item['name'] != None and item['type'] == 'postprocess':\r
-                                       ret.append(item)\r
-       return ret\r
-\r
-def runPostProcessingPlugins(gcodefilename):\r
-       pluginConfigList = getPluginConfig()\r
-       pluginList = getPluginList()\r
-       \r
-       for pluginConfig in pluginConfigList:\r
-               plugin = None\r
-               for pluginTest in pluginList:\r
-                       if pluginTest['filename'] == pluginConfig['filename']:\r
-                               plugin = pluginTest\r
-               if plugin == None:\r
-                       continue\r
-               \r
-               pythonFile = None\r
-               for basePath in getPluginBasePaths():\r
-                       testFilename = os.path.join(basePath, pluginConfig['filename'])\r
-                       if os.path.isfile(testFilename):\r
-                               pythonFile = testFilename\r
-               if pythonFile == None:\r
-                       continue\r
-               \r
-               locals = {'filename': gcodefilename}\r
-               for param in plugin['params']:\r
-                       value = param['default']\r
-                       if param['name'] in pluginConfig['params']:\r
-                               value = pluginConfig['params'][param['name']]\r
-                       \r
-                       if param['type'] == 'float':\r
-                               try:\r
-                                       value = float(value)\r
-                               except:\r
-                                       value = float(param['default'])\r
-                       \r
-                       locals[param['name']] = value\r
-               try:\r
-                       execfile(pythonFile, locals)\r
-               except:\r
-                       locationInfo = traceback.extract_tb(sys.exc_info()[2])[-1]\r
-                       return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1])\r
-       return None\r
-\r
-def getSDcardDrives():\r
-       drives = ['']\r
-       if platform.system() == "Windows":\r
-               from ctypes import windll\r
-               bitmask = windll.kernel32.GetLogicalDrives()\r
-               for letter in string.uppercase:\r
-                       if bitmask & 1:\r
-                               drives.append(letter + ':/')\r
-                       bitmask >>= 1\r
-       if platform.system() == "Darwin":\r
-               drives = []\r
-               for volume in glob.glob('/Volumes/*'):\r
-                       if stat.S_ISLNK(os.lstat(volume).st_mode):\r
-                               continue\r
-                       drives.append(volume)\r
-       return drives\r
+               os.makedirs(basePath)
+       return os.path.join(basePath, 'preferences.ini')
+
+def getPreferenceFloat(name):
+       try:
+               setting = getPreference(name).replace(',', '.')
+               return float(eval(setting, {}, {}))
+       except (ValueError, SyntaxError, TypeError):
+               return 0.0
+
+def getPreferenceColour(name):
+       colorString = getPreference(name)
+       return [float(int(colorString[1:3], 16)) / 255, float(int(colorString[3:5], 16)) / 255, float(int(colorString[5:7], 16)) / 255, 1.0]
+
+def getPreference(name):
+       if name in tempOverride:
+               return unicode(tempOverride[name])
+       global globalPreferenceParser
+       if globalPreferenceParser == None:
+               globalPreferenceParser = ConfigParser.ConfigParser()
+               globalPreferenceParser.read(getPreferencePath())
+       if not globalPreferenceParser.has_option('preference', name):
+               if name in preferencesDefaultSettings:
+                       default = preferencesDefaultSettings[name]
+               else:
+                       print("Missing default setting for: '" + name + "'")
+                       preferencesDefaultSettings[name] = ''
+                       default = ''
+               if not globalPreferenceParser.has_section('preference'):
+                       globalPreferenceParser.add_section('preference')
+               globalPreferenceParser.set('preference', name, str(default))
+               #print(name + " not found in preferences, so using default: " + str(default))
+               return default
+       return unicode(globalPreferenceParser.get('preference', name), "utf-8")
+
+def putPreference(name, value):
+       #Check if we have a configuration file loaded, else load the default.
+       global globalPreferenceParser
+       if globalPreferenceParser == None:
+               globalPreferenceParser = ConfigParser.ConfigParser()
+               globalPreferenceParser.read(getPreferencePath())
+       if not globalPreferenceParser.has_section('preference'):
+               globalPreferenceParser.add_section('preference')
+       globalPreferenceParser.set('preference', name, unicode(value).encode("utf-8"))
+       globalPreferenceParser.write(open(getPreferencePath(), 'w'))
+
+def isPreference(name):
+       if name in preferencesDefaultSettings:
+               return True
+       return False
+
+## Temp overrides for multi-extruder slicing and the project planner.
+tempOverride = {}
+def setTempOverride(name, value):
+       tempOverride[name] = unicode(value).encode("utf-8")
+def clearTempOverride(name):
+       del tempOverride[name]
+def resetTempOverride():
+       tempOverride.clear()
+
+#########################################################
+## Utility functions to calculate common profile values
+#########################################################
+def calculateEdgeWidth():
+       wallThickness = getProfileSettingFloat('wall_thickness')
+       nozzleSize = getProfileSettingFloat('nozzle_size')
+       
+       if wallThickness < nozzleSize:
+               return wallThickness
+
+       lineCount = int(wallThickness / nozzleSize)
+       lineWidth = wallThickness / lineCount
+       lineWidthAlt = wallThickness / (lineCount + 1)
+       if lineWidth > nozzleSize * 1.5:
+               return lineWidthAlt
+       return lineWidth
+
+def calculateLineCount():
+       wallThickness = getProfileSettingFloat('wall_thickness')
+       nozzleSize = getProfileSettingFloat('nozzle_size')
+       
+       if wallThickness < nozzleSize:
+               return 1
+
+       lineCount = int(wallThickness / nozzleSize + 0.0001)
+       lineWidth = wallThickness / lineCount
+       lineWidthAlt = wallThickness / (lineCount + 1)
+       if lineWidth > nozzleSize * 1.5:
+               return lineCount + 1
+       return lineCount
+
+def calculateSolidLayerCount():
+       layerHeight = getProfileSettingFloat('layer_height')
+       solidThickness = getProfileSettingFloat('solid_layer_thickness')
+       return int(math.ceil(solidThickness / layerHeight - 0.0001))
+
+#########################################################
+## Alteration file functions
+#########################################################
+def replaceTagMatch(m):
+       pre = m.group(1)
+       tag = m.group(2)
+       if tag == 'time':
+               return pre + time.strftime('%H:%M:%S')
+       if tag == 'date':
+               return pre + time.strftime('%d %b %Y')
+       if tag == 'day':
+               return pre + time.strftime('%a')
+       if tag == 'print_time':
+               return pre + '#P_TIME#'
+       if tag == 'filament_amount':
+               return pre + '#F_AMNT#'
+       if tag == 'filament_weight':
+               return pre + '#F_WGHT#'
+       if tag == 'filament_cost':
+               return pre + '#F_COST#'
+       if pre == 'F' and tag in ['print_speed', 'retraction_speed', 'travel_speed', 'max_z_speed', 'bottom_layer_speed', 'cool_min_feedrate']:
+               f = getProfileSettingFloat(tag) * 60
+       elif isProfileSetting(tag):
+               f = getProfileSettingFloat(tag)
+       elif isPreference(tag):
+               f = getProfileSettingFloat(tag)
+       else:
+               return '%s?%s?' % (pre, tag)
+       if (f % 1) == 0:
+               return pre + str(int(f))
+       return pre + str(f)
+
+def replaceGCodeTags(filename, gcodeInt):
+       f = open(filename, 'r+')
+       data = f.read(2048)
+       data = data.replace('#P_TIME#', ('%5d:%02d' % (int(gcodeInt.totalMoveTimeMinute / 60), int(gcodeInt.totalMoveTimeMinute % 60)))[-8:])
+       data = data.replace('#F_AMNT#', ('%8.2f' % (gcodeInt.extrusionAmount / 1000))[-8:])
+       data = data.replace('#F_WGHT#', ('%8.2f' % (gcodeInt.calculateWeight() * 1000))[-8:])
+       cost = gcodeInt.calculateCost()
+       if cost == False:
+               cost = 'Unknown'
+       data = data.replace('#F_COST#', ('%8s' % (cost.split(' ')[0]))[-8:])
+       f.seek(0)
+       f.write(data)
+       f.close()
+
+### Get aleration raw contents. (Used internally in Cura)
+def getAlterationFile(filename):
+       #Check if we have a configuration file loaded, else load the default.
+       if not globals().has_key('globalProfileParser'):
+               loadGlobalProfile(getDefaultProfilePath())
+       
+       if not globalProfileParser.has_option('alterations', filename):
+               if filename in alterationDefault:
+                       default = alterationDefault[filename]
+               else:
+                       print("Missing default alteration for: '" + filename + "'")
+                       alterationDefault[filename] = ''
+                       default = ''
+               if not globalProfileParser.has_section('alterations'):
+                       globalProfileParser.add_section('alterations')
+               #print("Using default for: %s" % (filename))
+               globalProfileParser.set('alterations', filename, default)
+       return unicode(globalProfileParser.get('alterations', filename), "utf-8")
+
+def setAlterationFile(filename, value):
+       #Check if we have a configuration file loaded, else load the default.
+       if not globals().has_key('globalProfileParser'):
+               loadGlobalProfile(getDefaultProfilePath())
+       if not globalProfileParser.has_section('alterations'):
+               globalProfileParser.add_section('alterations')
+       globalProfileParser.set('alterations', filename, value.encode("utf-8"))
+       saveGlobalProfile(getDefaultProfilePath())
+
+### Get the alteration file for output. (Used by Skeinforge)
+def getAlterationFileContents(filename):
+       prefix = ''
+       postfix = ''
+       alterationContents = getAlterationFile(filename)
+       if filename == 'start.gcode':
+               #For the start code, hack the temperature and the steps per E value into it. So the temperature is reached before the start code extrusion.
+               #We also set our steps per E here, if configured.
+               eSteps = getPreferenceFloat('steps_per_e')
+               if eSteps > 0:
+                       prefix += 'M92 E%f\n' % (eSteps)
+               temp = getProfileSettingFloat('print_temperature')
+               bedTemp = 0
+               if getPreference('has_heated_bed') == 'True':
+                       bedTemp = getProfileSettingFloat('print_bed_temperature')
+               
+               if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
+                       prefix += 'M140 S%f\n' % (bedTemp)
+               if temp > 0 and not '{print_temperature}' in alterationContents:
+                       prefix += 'M109 S%f\n' % (temp)
+               if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
+                       prefix += 'M190 S%f\n' % (bedTemp)
+       elif filename == 'end.gcode':
+               #Append the profile string to the end of the GCode, so we can load it from the GCode file later.
+               postfix = ';CURA_PROFILE_STRING:%s\n' % (getGlobalProfileString())
+       elif filename == 'replace.csv':
+               #Always remove the extruder on/off M codes. These are no longer needed in 5D printing.
+               prefix = 'M101\nM103\n'
+       elif filename == 'support_start.gcode' or filename == 'support_end.gcode':
+               #Add support start/end code 
+               if getProfileSetting('support_dual_extrusion') == 'True' and int(getPreference('extruder_amount')) > 1:
+                       if filename == 'support_start.gcode':
+                               setTempOverride('extruder', '1')
+                       else:
+                               setTempOverride('extruder', '0')
+                       alterationContents = getAlterationFileContents('switchExtruder.gcode')
+                       clearTempOverride('extruder')
+               else:
+                       alterationContents = ''
+       return unicode(prefix + re.sub("(.)\{([^\}]*)\}", replaceTagMatch, alterationContents).rstrip() + '\n' + postfix).strip().encode('utf-8')
+
+###### PLUGIN #####
+
+def getPluginConfig():
+       try:
+               return pickle.loads(getProfileSetting('plugin_config'))
+       except:
+               return []
+
+def setPluginConfig(config):
+       putProfileSetting('plugin_config', pickle.dumps(config))
+
+def getPluginBasePaths():
+       ret = []
+       if platform.system() != "Windows":
+               ret.append(os.path.expanduser('~/.cura/plugins/'))
+       ret.append(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'plugins')))
+       return ret
+
+def getPluginList():
+       ret = []
+       for basePath in getPluginBasePaths():
+               for filename in glob.glob(os.path.join(basePath, '*.py')):
+                       filename = os.path.basename(filename)
+                       if filename.startswith('_'):
+                               continue
+                       with open(os.path.join(basePath, filename), "r") as f:
+                               item = {'filename': filename, 'name': None, 'info': None, 'type': None, 'params': []}
+                               for line in f:
+                                       line = line.strip()
+                                       if not line.startswith('#'):
+                                               break
+                                       line = line[1:].split(':', 1)
+                                       if len(line) != 2:
+                                               continue
+                                       if line[0].upper() == 'NAME':
+                                               item['name'] = line[1].strip()
+                                       elif line[0].upper() == 'INFO':
+                                               item['info'] = line[1].strip()
+                                       elif line[0].upper() == 'TYPE':
+                                               item['type'] = line[1].strip()
+                                       elif line[0].upper() == 'DEPEND':
+                                               pass
+                                       elif line[0].upper() == 'PARAM':
+                                               m = re.match('([a-zA-Z]*)\(([a-zA-Z_]*)(?:\:([^\)]*))?\) +(.*)', line[1].strip())
+                                               if m != None:
+                                                       item['params'].append({'name': m.group(1), 'type': m.group(2), 'default': m.group(3), 'description': m.group(4)})
+                                       else:
+                                               print "Unknown item in effect meta data: %s %s" % (line[0], line[1])
+                               if item['name'] != None and item['type'] == 'postprocess':
+                                       ret.append(item)
+       return ret
+
+def runPostProcessingPlugins(gcodefilename):
+       pluginConfigList = getPluginConfig()
+       pluginList = getPluginList()
+       
+       for pluginConfig in pluginConfigList:
+               plugin = None
+               for pluginTest in pluginList:
+                       if pluginTest['filename'] == pluginConfig['filename']:
+                               plugin = pluginTest
+               if plugin == None:
+                       continue
+               
+               pythonFile = None
+               for basePath in getPluginBasePaths():
+                       testFilename = os.path.join(basePath, pluginConfig['filename'])
+                       if os.path.isfile(testFilename):
+                               pythonFile = testFilename
+               if pythonFile == None:
+                       continue
+               
+               locals = {'filename': gcodefilename}
+               for param in plugin['params']:
+                       value = param['default']
+                       if param['name'] in pluginConfig['params']:
+                               value = pluginConfig['params'][param['name']]
+                       
+                       if param['type'] == 'float':
+                               try:
+                                       value = float(value)
+                               except:
+                                       value = float(param['default'])
+                       
+                       locals[param['name']] = value
+               try:
+                       execfile(pythonFile, locals)
+               except:
+                       locationInfo = traceback.extract_tb(sys.exc_info()[2])[-1]
+                       return "%s: '%s' @ %s:%s:%d" % (str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]), os.path.basename(locationInfo[0]), locationInfo[2], locationInfo[1])
+       return None
+
+def getSDcardDrives():
+       drives = ['']
+       if platform.system() == "Windows":
+               from ctypes import windll
+               bitmask = windll.kernel32.GetLogicalDrives()
+               for letter in string.uppercase:
+                       if bitmask & 1:
+                               drives.append(letter + ':/')
+                       bitmask >>= 1
+       if platform.system() == "Darwin":
+               drives = []
+               for volume in glob.glob('/Volumes/*'):
+                       if stat.S_ISLNK(os.lstat(volume).st_mode):
+                               continue
+                       drives.append(volume)
+       return drives