chiark / gitweb /
Updated all lineends for py files to unix style.
[cura.git] / Cura / util / profile.py
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