2012-03-17 11:03:38 +00:00
from __future__ import absolute_import
2012-04-10 14:45:53 +00:00
from __future__ import division
2012-03-17 11:03:38 +00:00
#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__
2012-04-23 16:02:30 +00:00
import ConfigParser , os , traceback , math , re , zlib , base64
2012-03-17 11:03:38 +00:00
2012-03-27 11:48:40 +00:00
#########################################################
2012-04-23 16:02:30 +00:00
## Default settings when none are found.
2012-03-27 11:48:40 +00:00
#########################################################
2012-03-17 11:03:38 +00:00
#Single place to store the defaults, so we have a consistent set of default settings.
profileDefaultSettings = {
2012-03-26 11:45:31 +00:00
' nozzle_size ' : ' 0.4 ' ,
2012-03-17 11:03:38 +00:00
' layer_height ' : ' 0.2 ' ,
' wall_thickness ' : ' 0.8 ' ,
' solid_layer_thickness ' : ' 0.6 ' ,
' fill_density ' : ' 20 ' ,
' skirt_line_count ' : ' 1 ' ,
2012-04-23 09:35:52 +00:00
' skirt_gap ' : ' 3.0 ' ,
2012-03-17 11:03:38 +00:00
' print_speed ' : ' 50 ' ,
' print_temperature ' : ' 0 ' ,
' support ' : ' None ' ,
' filament_diameter ' : ' 2.89 ' ,
' filament_density ' : ' 1.00 ' ,
' machine_center_x ' : ' 100 ' ,
' machine_center_y ' : ' 100 ' ,
' retraction_min_travel ' : ' 5.0 ' ,
2012-04-18 08:50:56 +00:00
' retraction_speed ' : ' 40.0 ' ,
2012-03-17 11:03:38 +00:00
' retraction_amount ' : ' 0.0 ' ,
' retraction_extra ' : ' 0.0 ' ,
' travel_speed ' : ' 150 ' ,
2012-04-23 09:35:52 +00:00
' max_z_speed ' : ' 3.0 ' ,
' bottom_layer_speed ' : ' 20 ' ,
2012-03-17 11:03:38 +00:00
' cool_min_layer_time ' : ' 10 ' ,
2012-04-02 14:09:56 +00:00
' fan_enabled ' : ' True ' ,
2012-04-27 11:45:32 +00:00
' fan_layer ' : ' 1 ' ,
2012-04-02 14:09:56 +00:00
' fan_speed ' : ' 100 ' ,
2012-03-17 11:03:38 +00:00
' model_scale ' : ' 1.0 ' ,
' flip_x ' : ' False ' ,
' flip_y ' : ' False ' ,
' flip_z ' : ' False ' ,
2012-04-06 13:15:41 +00:00
' swap_xz ' : ' False ' ,
' swap_yz ' : ' False ' ,
2012-03-17 11:03:38 +00:00
' 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 ' ,
2012-04-05 16:00:22 +00:00
' support_rate ' : ' 50 ' ,
2012-03-17 11:03:38 +00:00
' support_distance ' : ' 0.5 ' ,
2012-04-23 16:02:30 +00:00
' support_margin ' : ' 3.0 ' ,
2012-03-17 11:03:38 +00:00
' joris ' : ' False ' ,
2012-04-06 15:08:49 +00:00
' enable_skin ' : ' False ' ,
2012-03-18 22:00:33 +00:00
' enable_raft ' : ' False ' ,
2012-03-20 10:01:47 +00:00
' cool_min_feedrate ' : ' 5 ' ,
' bridge_speed ' : ' 100 ' ,
' bridge_material_amount ' : ' 100 ' ,
2012-03-20 14:59:31 +00:00
' raft_margin ' : ' 5 ' ,
' raft_base_material_amount ' : ' 100 ' ,
2012-04-19 13:08:50 +00:00
' raft_interface_material_amount ' : ' 100 ' ,
2012-04-16 10:26:55 +00:00
' bottom_thickness ' : ' 0.3 ' ,
2012-04-24 09:59:17 +00:00
2012-05-14 14:01:55 +00:00
' enable_dwindle ' : ' False ' ,
' dwindle_pent_up_volume ' : ' 0.4 ' ,
' dwindle_slowdown_volume ' : ' 5.0 ' ,
2012-04-17 17:12:40 +00:00
' add_start_end_gcode ' : ' True ' ,
2012-04-19 13:08:50 +00:00
' gcode_extension ' : ' gcode ' ,
2012-05-03 10:29:55 +00:00
' alternative_center ' : ' ' ,
' clear_z ' : ' 0.0 ' ,
' extruder ' : ' 0 ' ,
2012-03-17 11:03:38 +00:00
}
2012-04-23 16:02:30 +00:00
alterationDefault = {
#######################################################################################
' start.gcode ' : """ ;Start GCode
G21 ; metric values
G90 ; absolute positioning
G28 X0 Y0 ; move X / Y to min endstops
G28 Z0 ; move Z to min endstops
; if your prints start too high , try changing the Z0 .0 below
; to Z1 .0 - the number after the Z is the actual , physical
; height of the nozzle in mm . This can take some messing around
; with to get just right . . .
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 15 mm
G92 E0 ; zero the extruded length
G1 F200 E5 ; extrude 5 mm of feed stock
G1 F200 E3 .5 ; reverse feed stock by 1.5 mm
G92 E0 ; zero the extruded length again
; go to the middle of the platform , and move to Z = 0 before starting the print .
G1 X { machine_center_x } Y { machine_center_y } F { travel_speed }
G1 Z0 .0 F { max_z_speed }
""" ,
#######################################################################################
' end.gcode ' : """ ;End GCode
M104 S0 ; extruder heat off
G91 ; relative positioning
G1 Z + 10 E - 5 F { max_z_speed } ; move Z up a bit and retract filament by 5 mm
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
G1 Z { clear_z } E - 5 F { max_z_speed }
G92 E0
G1 X { machine_center_x } Y { machine_center_y } F { travel_speed }
G1 F200 E5 .5
G92 E0
G1 Z0 F { max_z_speed }
""" ,
#######################################################################################
2012-05-07 15:42:46 +00:00
' switchExtruder.gcode ' : """ ;Switch between the current extruder and the next extruder, when printing with multiple extruders.
G1 E - 5 F5000
G92 E0
T { extruder }
G1 E5 F5000
G92 E0
""" ,
2012-04-23 16:02:30 +00:00
}
2012-03-17 11:03:38 +00:00
preferencesDefaultSettings = {
' wizardDone ' : ' False ' ,
2012-03-29 12:45:14 +00:00
' startMode ' : ' Simple ' ,
2012-04-24 16:46:29 +00:00
' lastFile ' : ' ' ,
2012-03-17 11:03:38 +00:00
' machine_width ' : ' 205 ' ,
' machine_depth ' : ' 205 ' ,
' machine_height ' : ' 200 ' ,
2012-04-24 16:46:29 +00:00
' extruder_amount ' : ' 1 ' ,
2012-04-29 09:35:36 +00:00
' 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 ' ,
2012-04-03 10:06:02 +00:00
' filament_density ' : ' 1300 ' ,
2012-03-17 11:03:38 +00:00
' steps_per_e ' : ' 0 ' ,
' serial_port ' : ' AUTO ' ,
' serial_baud ' : ' 250000 ' ,
2012-03-26 13:03:26 +00:00
' slicer ' : ' Cura (Skeinforge based) ' ,
2012-04-12 11:58:19 +00:00
' save_profile ' : ' False ' ,
2012-04-12 12:26:03 +00:00
' filament_cost_kg ' : ' 0 ' ,
' filament_cost_meter ' : ' 0 ' ,
2012-04-29 09:35:36 +00:00
' 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 ' ,
2012-03-17 11:03:38 +00:00
}
2012-04-23 16:02:30 +00:00
#########################################################
## Profile and preferences functions
#########################################################
2012-05-03 10:29:55 +00:00
## Profile functions
2012-03-17 11:03:38 +00:00
def getDefaultProfilePath ( ) :
return os . path . normpath ( os . path . join ( os . path . dirname ( os . path . abspath ( __file__ ) ) , " ../current_profile.ini " ) )
def loadGlobalProfile ( filename ) :
2012-03-21 17:22:00 +00:00
#Read a configuration file as global config
2012-03-17 11:03:38 +00:00
global globalProfileParser
globalProfileParser = ConfigParser . ConfigParser ( )
globalProfileParser . read ( filename )
def saveGlobalProfile ( filename ) :
2012-03-21 17:22:00 +00:00
#Save the current profile to an ini file
2012-03-17 11:03:38 +00:00
globalProfileParser . write ( open ( filename , ' w ' ) )
2012-03-30 11:54:49 +00:00
def loadGlobalProfileFromString ( options ) :
2012-03-21 17:22:00 +00:00
global globalProfileParser
globalProfileParser = ConfigParser . ConfigParser ( )
2012-03-30 11:54:49 +00:00
globalProfileParser . add_section ( ' profile ' )
2012-04-23 16:02:30 +00:00
globalProfileParser . add_section ( ' alterations ' )
options = base64 . b64decode ( options )
options = zlib . decompress ( options )
( profileOpts , alt ) = options . split ( ' \f ' , 1 )
for option in profileOpts . split ( ' \b ' ) :
2012-05-15 07:34:45 +00:00
if len ( option ) > 0 :
( key , value ) = option . split ( ' = ' , 1 )
globalProfileParser . set ( ' profile ' , key , value )
2012-04-23 16:02:30 +00:00
for option in alt . split ( ' \b ' ) :
2012-05-15 07:34:45 +00:00
if len ( option ) > 0 :
( key , value ) = option . split ( ' = ' , 1 )
globalProfileParser . set ( ' alterations ' , key , value )
2012-03-30 11:54:49 +00:00
def getGlobalProfileString ( ) :
global globalProfileParser
if not globals ( ) . has_key ( ' globalProfileParser ' ) :
loadGlobalProfile ( getDefaultProfilePath ( ) )
2012-04-23 16:02:30 +00:00
p = [ ]
alt = [ ]
2012-05-03 10:29:55 +00:00
tempDone = [ ]
2012-04-28 16:32:34 +00:00
if globalProfileParser . has_section ( ' profile ' ) :
for key in globalProfileParser . options ( ' profile ' ) :
2012-05-03 10:29:55 +00:00
if key in tempOverride :
p . append ( key + " = " + unicode ( tempOverride [ key ] ) )
tempDone . append ( key )
else :
p . append ( key + " = " + globalProfileParser . get ( ' profile ' , key ) )
2012-04-28 16:32:34 +00:00
if globalProfileParser . has_section ( ' alterations ' ) :
for key in globalProfileParser . options ( ' alterations ' ) :
2012-05-03 10:29:55 +00:00
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 + " = " + unicode ( tempOverride [ key ] ) )
2012-04-23 16:02:30 +00:00
ret = ' \b ' . join ( p ) + ' \f ' + ' \b ' . join ( alt )
ret = base64 . b64encode ( zlib . compress ( ret , 9 ) )
return ret
2012-03-21 17:22:00 +00:00
2012-03-17 11:03:38 +00:00
def getProfileSetting ( name ) :
2012-05-03 10:29:55 +00:00
if name in tempOverride :
return unicode ( tempOverride [ name ] )
2012-03-17 11:03:38 +00:00
#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 ) :
2012-04-20 15:06:50 +00:00
if name in profileDefaultSettings :
default = profileDefaultSettings [ name ]
else :
print " Missing default setting for: ' " + name + " ' "
profileDefaultSettings [ name ] = ' '
default = ' '
2012-03-17 11:03:38 +00:00
if not globalProfileParser . has_section ( ' profile ' ) :
globalProfileParser . add_section ( ' profile ' )
globalProfileParser . set ( ' profile ' , name , str ( default ) )
2012-04-10 14:45:53 +00:00
#print name + " not found in profile, so using default: " + str(default)
2012-03-17 11:03:38 +00:00
return default
return globalProfileParser . get ( ' profile ' , name )
2012-04-10 14:45:53 +00:00
def getProfileSettingFloat ( name ) :
try :
return float ( eval ( getProfileSetting ( name ) , { } , { } ) )
except ( ValueError , SyntaxError ) :
return 0.0
2012-03-17 11:03:38 +00:00
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 ) )
2012-05-03 10:29:55 +00:00
def isProfileSetting ( name ) :
if name in profileDefaultSettings :
return True
return False
## Preferences functions
2012-03-17 11:03:38 +00:00
global globalPreferenceParser
globalPreferenceParser = None
def getPreferencePath ( ) :
return os . path . normpath ( os . path . join ( os . path . dirname ( os . path . abspath ( __file__ ) ) , " ../preferences.ini " ) )
2012-05-01 09:39:52 +00:00
def getPreferenceFloat ( name ) :
try :
return float ( eval ( getPreference ( name ) , { } , { } ) )
except ( ValueError , SyntaxError ) :
return 0.0
2012-03-17 11:03:38 +00:00
def getPreference ( name ) :
2012-05-03 10:29:55 +00:00
if name in tempOverride :
return unicode ( tempOverride [ name ] )
2012-03-17 11:03:38 +00:00
global globalPreferenceParser
if globalPreferenceParser == None :
globalPreferenceParser = ConfigParser . ConfigParser ( )
globalPreferenceParser . read ( getPreferencePath ( ) )
if not globalPreferenceParser . has_option ( ' preference ' , name ) :
2012-04-20 13:25:48 +00:00
if name in preferencesDefaultSettings :
default = preferencesDefaultSettings [ name ]
else :
print " Missing default setting for: ' " + name + " ' "
preferencesDefaultSettings [ name ] = ' '
default = ' '
2012-03-17 11:03:38 +00:00
if not globalPreferenceParser . has_section ( ' preference ' ) :
globalPreferenceParser . add_section ( ' preference ' )
globalPreferenceParser . set ( ' preference ' , name , str ( default ) )
2012-04-10 14:45:53 +00:00
#print name + " not found in preferences, so using default: " + str(default)
2012-03-17 11:03:38 +00:00
return default
2012-04-04 09:10:08 +00:00
return unicode ( globalPreferenceParser . get ( ' preference ' , name ) , " utf-8 " )
2012-03-17 11:03:38 +00:00
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 ' )
2012-04-17 11:49:34 +00:00
globalPreferenceParser . set ( ' preference ' , name , unicode ( value ) . encode ( " utf-8 " ) )
2012-03-17 11:03:38 +00:00
globalPreferenceParser . write ( open ( getPreferencePath ( ) , ' w ' ) )
2012-03-26 13:03:26 +00:00
2012-05-03 10:29:55 +00:00
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 ] = value
def resetTempOverride ( ) :
2012-05-04 17:58:00 +00:00
tempOverride . clear ( )
2012-05-03 10:29:55 +00:00
2012-03-27 11:48:40 +00:00
#########################################################
2012-03-26 13:03:26 +00:00
## Utility functions to calculate common profile values
2012-03-27 11:48:40 +00:00
#########################################################
2012-03-26 13:03:26 +00:00
def calculateEdgeWidth ( ) :
2012-04-10 14:45:53 +00:00
wallThickness = getProfileSettingFloat ( ' wall_thickness ' )
nozzleSize = getProfileSettingFloat ( ' nozzle_size ' )
2012-03-26 13:03:26 +00:00
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 ( ) :
2012-04-10 14:45:53 +00:00
wallThickness = getProfileSettingFloat ( ' wall_thickness ' )
nozzleSize = getProfileSettingFloat ( ' nozzle_size ' )
2012-03-26 13:03:26 +00:00
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 ( ) :
2012-04-10 14:45:53 +00:00
layerHeight = getProfileSettingFloat ( ' layer_height ' )
solidThickness = getProfileSettingFloat ( ' solid_layer_thickness ' )
2012-03-26 13:03:26 +00:00
return int ( math . ceil ( solidThickness / layerHeight - 0.0001 ) )
2012-03-27 11:48:40 +00:00
#########################################################
## Alteration file functions
#########################################################
2012-04-19 13:08:50 +00:00
def replaceTagMatch ( m ) :
tag = m . group ( 0 ) [ 1 : - 1 ]
if tag in [ ' print_speed ' , ' retraction_speed ' , ' travel_speed ' , ' max_z_speed ' , ' bottom_layer_speed ' , ' cool_min_feedrate ' ] :
2012-05-09 18:16:32 +00:00
f = getProfileSettingFloat ( tag ) * 60
elif isProfileSetting ( tag ) :
f = getProfileSettingFloat ( tag )
elif isPreference ( tag ) :
f = getProfileSettingFloat ( tag )
else :
return tag
if ( f % 1 ) == 0 :
return str ( int ( f ) )
return str ( f )
2012-04-19 13:08:50 +00:00
2012-04-23 16:02:30 +00:00
### 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 " ) )
2012-04-24 03:02:50 +00:00
saveGlobalProfile ( getDefaultProfilePath ( ) )
2012-04-23 16:02:30 +00:00
### Get the alteration file for output. (Used by Skeinforge)
def getAlterationFileContents ( filename ) :
2012-03-27 11:48:40 +00:00
prefix = ' '
2012-04-24 09:59:17 +00:00
alterationContents = getAlterationFile ( filename )
2012-04-23 16:02:30 +00:00
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.
2012-05-01 09:39:52 +00:00
eSteps = getPreferenceFloat ( ' steps_per_e ' )
2012-04-23 16:02:30 +00:00
if eSteps > 0 :
prefix + = ' M92 E %f \n ' % ( eSteps )
temp = getProfileSettingFloat ( ' print_temperature ' )
2012-04-24 09:59:17 +00:00
if temp > 0 and not ' {print_temperature} ' in alterationContents :
2012-04-23 16:02:30 +00:00
prefix + = ' M109 S %f \n ' % ( temp )
elif filename == ' replace.csv ' :
#Always remove the extruder on/off M codes. These are no longer needed in 5D printing.
prefix = ' M101 \n M103 \n '
2012-04-25 11:34:25 +00:00
return unicode ( prefix + re . sub ( " \ { [^ \ }]* \ } " , replaceTagMatch , alterationContents ) . rstrip ( ) + ' \n ' ) . encode ( ' utf-8 ' )
2012-03-27 11:48:40 +00:00