1 from __future__ import absolute_import
2 from __future__ import division
4 import os, traceback, math, re, zlib, base64, time, sys, platform, glob, string, stat, types
5 import cPickle as pickle
6 if sys.version_info[0] < 3:
9 import configparser as ConfigParser
11 from Cura.util import resources
12 from Cura.util import version
13 from Cura.util import validators
15 settingsDictionary = {}
17 class setting(object):
18 def __init__(self, name, default, type, category, subcategory):
22 self._default = unicode(default)
23 self._value = self._default
25 self._category = category
26 self._subcategory = subcategory
29 if type is types.FloatType:
30 validators.validFloat(self)
31 elif type is types.IntType:
32 validators.validInt(self)
34 global settingsDictionary
35 settingsDictionary[name] = self
37 settingsList.append(self)
39 def setLabel(self, label, tooltip = ''):
41 self._tooltip = tooltip
44 def setRange(self, minValue = None, maxValue = None):
45 if len(self._validators) < 1:
47 self._validators[0].minValue = minValue
48 self._validators[0].maxValue = maxValue
57 def isPreference(self):
58 return self._category == 'preference'
60 def isAlteration(self):
61 return self._category == 'alteration'
64 return not self.isAlteration() and not self.isPreference()
78 def setValue(self, value):
79 self._value = unicode(value)
82 result = validators.SUCCESS
84 for validator in self._validators:
85 res, err = validator.validate()
86 if res == validators.ERROR:
88 elif res == validators.WARNING and result != validators.ERROR:
90 if res != validators.SUCCESS:
92 return result, '\n'.join(msgs)
94 #########################################################
96 #########################################################
97 setting('layer_height', 0.2, float, 'basic', 'Quality').setRange(0.0001).setLabel('Layer height (mm)', 'Layer height in millimeters.\n0.2 is a good value for quick prints.\n0.1 gives high quality prints.\nDepending on your printer you can go as low as 0.02mm')
98 setting('wall_thickness', 0.8, float, 'basic', 'Quality').setRange(0.0001).setLabel('Wall thickness (mm)', 'Thickness of the walls.\nThis is used in combination with the nozzle size to define the number\nof perimeter lines and the thickness of those perimeter lines.')
99 setting('retraction_enable', False, bool, 'basic', 'Quality').setLabel('Enable retraction', 'Retract the filament when the nozzle is moving over a none-printed area. Details about the retraction can be configured in the advanced tab.')
100 setting('solid_layer_thickness', 0.6, float, 'basic', 'Fill').setRange(0).setLabel('Bottom/Top thickness (mm)', 'This controls the thickness of the bottom and top layers, the amount of solid layers put down is calculated by the layer thickness and this value.\nHaving this value a multiply of the layer thickness makes sense. And keep it near your wall thickness to make an evenly strong part.')
101 setting('fill_density', 20, float, 'basic', 'Fill').setRange(0, 100).setLabel('Fill Density (%)', 'This controls how densely filled the insides of your print will be. For a solid part use 100%, for an empty part use 0%. A value around 20% is usually enough.\nThis won\'t effect the outside of the print and only adjusts how strong the part becomes.')
102 setting('nozzle_size', 0.4, float, 'advanced', 'Machine').setRange(0.1,10).setLabel('Nozzle size (mm)', 'The nozzle size is very important, this is used to calculate the line width of the infill, and used to calculate the amount of outside wall lines and thickness for the wall thickness you entered in the print settings.')
103 setting('skirt_line_count', 1, int, 'advanced', 'Skirt').setRange(0).setLabel('Line count', 'The skirt is a line drawn around the object at the first layer. This helps to prime your extruder, and to see if the object fits on your platform.\nSetting this to 0 will disable the skirt. Multiple skirt lines can help priming your extruder better for small objects.')
104 setting('skirt_gap', 3.0, float, 'advanced', 'Skirt').setRange(0).setLabel('Start distance (mm)', 'The distance between the skirt and the first layer.\nThis is the minimal distance, multiple skirt lines will be put outwards from this distance.')
105 setting('print_speed', 50, float, 'basic', 'Speed & Temperature').setRange(1).setLabel('Print speed (mm/s)', 'Speed at which printing happens. A well adjusted Ultimaker can reach 150mm/s, but for good quality prints you want to print slower. Printing speed depends on a lot of factors. So you will be experimenting with optimal settings for this.')
106 setting('print_temperature', 220, int, 'basic', 'Speed & Temperature').setRange(0,340).setLabel('Printing temperature (C)', 'Temperature used for printing. Set at 0 to pre-heat yourself.\nFor PLA a value of 210C is usually used.\nFor ABS a value of 230C or higher is required.')
107 setting('print_temperature2', 0, int, 'basic', 'Speed & Temperature').setRange(0,340).setLabel('2nd nozzle temperature (C)', 'Temperature used for printing. Set at 0 to pre-heat yourself.\nFor PLA a value of 210C is usually used.\nFor ABS a value of 230C or higher is required.')
108 setting('print_temperature3', 0, int, 'basic', 'Speed & Temperature').setRange(0,340).setLabel('3th nozzle temperature (C)', 'Temperature used for printing. Set at 0 to pre-heat yourself.\nFor PLA a value of 210C is usually used.\nFor ABS a value of 230C or higher is required.')
109 setting('print_temperature4', 0, int, 'basic', 'Speed & Temperature').setRange(0,340).setLabel('4th nozzle temperature (C)', 'Temperature used for printing. Set at 0 to pre-heat yourself.\nFor PLA a value of 210C is usually used.\nFor ABS a value of 230C or higher is required.')
110 setting('print_bed_temperature', 70, int, 'basic', 'Speed & Temperature').setRange(0,340).setLabel('Bed temperature (C)', 'Temperature used for the heated printer bed. Set at 0 to pre-heat yourself.')
111 setting('support', 'None', ['None', 'Touching buildplate', 'Everywhere'], 'Basic', 'Support structure').setLabel('Support type', 'Type of support structure build.\n"Exterior only" is the most commonly used support setting.\n\nNone does not do any support.\nTouching buildplate only creates support where the support structure will touch the build platform.\nEverywhere creates support even on top of parts of the model.')
112 setting('enable_raft', False, bool, 'basic', 'Support').setLabel('Enable raft', 'A raft is a few layers of lines below the bottom of the object. It prevents warping. Full raft settings can be found in the expert settings.\nFor PLA this is usually not required. But if you print with ABS it is almost required.')
113 setting('support_dual_extrusion', False, bool, 'basic', 'Support').setLabel('Support dual extrusion', 'Print the support material with the 2nd extruder in a dual extrusion setup. The primary extruder will be used for normal material, while the second extruder is used to print support material.')
114 setting('filament_diameter', 2.89, float, 'basic', 'Filament').setRange(1).setLabel('Diameter (mm)', 'Diameter of your filament, as accurately as possible.\nIf you cannot measure this value you will have to calibrate it, a higher number means less extrusion, a smaller number generates more extrusion.')
115 setting('filament_diameter2', 0, float, 'basic', 'Filament').setRange(0).setLabel('Diameter2 (mm)', 'Diameter of your filament for the 2nd nozzle. Use 0 to use the same diameter as for nozzle 1.')
116 setting('filament_diameter3', 0, float, 'basic', 'Filament').setRange(0).setLabel('Diameter3 (mm)', 'Diameter of your filament for the 3th nozzle. Use 0 to use the same diameter as for nozzle 1.')
117 setting('filament_diameter4', 0, float, 'basic', 'Filament').setRange(0).setLabel('Diameter4 (mm)', 'Diameter of your filament for the 4th nozzle. Use 0 to use the same diameter as for nozzle 1.')
118 setting('filament_density', 1.00, float, 'basic', 'Filament').setRange(0.5,1.5).setLabel('Packing Density', 'Packing density of your filament. This should be 1.00 for PLA and 0.85 for ABS')
119 setting('retraction_min_travel', 5.0, float, 'advanced', 'Retraction').setRange(0).setLabel('Minimum travel (mm)', 'Minimum amount of travel needed for a retraction to happen at all. To make sure you do not get a lot of retractions in a small area')
120 setting('retraction_speed', 40.0, float, 'advanced', 'Retraction').setRange(0.1).setLabel('Speed (mm/s)', 'Speed at which the filament is retracted, a higher retraction speed works better. But a very high retraction speed can lead to filament grinding.')
121 setting('retraction_amount', 4.5, float, 'advanced', 'Retraction').setRange(0).setLabel('Distance (mm)', 'Amount of retraction, set at 0 for no retraction at all. A value of 2.0mm seems to generate good results.')
122 setting('retraction_extra', 0.0, float, 'advanced', 'Retraction').setRange(0).setLabel('Extra length on start (mm)', 'Extra extrusion amount when restarting after a retraction, to better "Prime" your extruder after retraction.')
123 setting('bottom_thickness', 0.3, float, 'advanced', 'Quality').setRange(0).setLabel('Initial layer thickness (mm)', 'Layer thickness of the bottom layer. A thicker bottom layer makes sticking to the bed easier. Set to 0.0 to have the bottom layer thickness the same as the other layers.')
124 setting('object_sink', 0.0, float, 'advanced', 'Quality').setLabel('Cut off object bottom (mm)', 'Sinks the object into the platform, this can be used for objects that do not have a flat bottom and thus create a too small first layer.')
125 setting('enable_skin', False, bool, 'advanced', 'Quality').setLabel('Duplicate outlines', 'Skin prints the outer lines of the prints twice, each time with half the thickness. This gives the illusion of a higher print quality.')
126 setting('retract_on_jumps_only', True, bool, 'expert', 'Retraction').setLabel('Retract on jumps only', 'Only retract when we are making a move that is over a hole in the model, else retract on every move. This effects print quality in different ways.')
127 setting('travel_speed', 150.0, float, 'advanced', 'Speed').setRange(0.1).setLabel('Travel speed (mm/s)', 'Speed at which travel moves are done, a high quality build Ultimaker can reach speeds of 250mm/s. But some machines might miss steps then.')
128 setting('max_z_speed', 3.0, float, 'expert', 'Speed').setRange(0.1).setLabel('Max Z speed (mm/s)', 'Speed at which Z moves are done. When you Z axis is properly lubricated you can increase this for less Z blob.')
129 setting('bottom_layer_speed', 20, float, 'advanced', 'Speed').setRange(0.1).setLabel('Bottom layer speed (mm/s)', 'Print speed for the bottom layer, you want to print the first layer slower so it sticks better to the printer bed.')
130 setting('cool_min_layer_time', 5, float, 'advanced', 'Cool').setRange(0).setLabel('Minimal layer time (sec)', 'Minimum time spend in a layer, gives the layer time to cool down before the next layer is put on top. If the layer will be placed down too fast the printer will slow down to make sure it has spend at least this amount of seconds printing this layer.')
131 setting('fan_enabled', True, bool, 'advanced', 'Cool').setLabel('Enable cooling fan', 'Enable the cooling fan during the print. The extra cooling from the cooling fan is essensial during faster prints.')
132 setting('fan_layer', 1, int, 'expert', 'Cool').setRange(0).setLabel('Fan on layer number', 'The layer at which the fan is turned on. The first layer is layer 0. The first layer can stick better if you turn on the fan on, on the 2nd layer.')
133 setting('fan_speed', 100, int, 'expert', 'Cool').setRange(0,100).setLabel('Fan speed min (%)', 'When the fan is turned on, it is enabled at this speed setting. If cool slows down the layer, the fan is adjusted between the min and max speed. Minimal fan speed is used if the layer is not slowed down due to cooling.')
134 setting('fan_speed_max', 100, int, 'expert', 'Cool').setRange(0,100).setLabel('Fan speed max (%)', 'When the fan is turned on, it is enabled at this speed setting. If cool slows down the layer, the fan is adjusted between the min and max speed. Maximal fan speed is used if the layer is slowed down due to cooling by more then 200%.')
135 setting('cool_min_feedrate', 10, float, 'expert', 'Cool').setRange(0).setLabel('Minimum feedrate (mm/s)', 'The minimal layer time can cause the print to slow down so much it starts to ooze. The minimal feedrate protects against this. Even if a print gets slown down it will never be slower then this minimal feedrate.')
136 setting('extra_base_wall_thickness', 0.0, float, 'expert', 'Accuracy').setRange(0).setLabel('Extra Wall thickness for bottom/top (mm)', 'Additional wall thickness of the bottom and top layers.')
137 setting('sequence', 'Loops > Perimeter > Infill', ['Loops > Perimeter > Infill', 'Loops > Infill > Perimeter', 'Infill > Loops > Perimeter', 'Infill > Perimeter > Loops', 'Perimeter > Infill > Loops', 'Perimeter > Loops > Infill'], 'expert', 'Sequence')
138 setting('force_first_layer_sequence', True, bool, 'expert', 'Sequence').setLabel('Force first layer sequence', 'This setting forces the order of the first layer to be \'Perimeter > Loops > Infill\'')
139 setting('infill_type', 'Line', ['Line', 'Grid Circular', 'Grid Hexagonal', 'Grid Rectangular'], 'expert', 'Infill').setLabel('Infill pattern', 'Pattern of the none-solid infill. Line is default, but grids can provide a strong print.')
140 setting('solid_top', True, bool, 'expert', 'Infill').setLabel('Solid infill top', 'Create a solid top surface, if set to false the top is filled with the fill percentage. Useful for cups/vases.')
141 setting('fill_overlap', 15, int, 'expert', 'Infill').setRange(0,100).setLabel('Infill overlap (%)', 'Amount of overlap between the infill and the walls. There is a slight overlap with the walls and the infill so the walls connect firmly to the infill.')
142 setting('support_rate', 50, int, 'expert', 'Support').setRange(0,100).setLabel('Material amount (%)', 'Amount of material used for support, less material gives a weaker support structure which is easier to remove.')
143 setting('support_distance', 0.5, float, 'expert', 'Support').setRange(0).setLabel('Distance from object (mm)', 'Distance between the support structure and the object. Empty gap in which no support structure is printed.')
144 setting('joris', False, bool, 'expert', 'Joris').setLabel('Spiralize the outer contour', '[Joris] is a code name for smoothing out the Z move of the outer edge. This will create a steady Z increase over the whole print. It is intended to be used with a single walled wall thickness to make cups/vases.')
145 setting('bridge_speed', 100, int, 'expert', 'Bridge').setRange(0,100).setLabel('Bridge speed (%)', 'Speed at which layers with bridges are printed, compared to normal printing speed.')
146 setting('raft_margin', 5, float, 'expert', 'Raft').setRange(0).setLabel('Extra margin (mm)', 'If the raft is enabled, this is the extra raft area around the object which is also rafted. Increasing this margin will create a stronger raft.')
147 setting('raft_base_material_amount', 100, int, 'expert', 'Raft').setRange(0,100).setLabel('Base material amount (%)', 'The base layer is the first layer put down as a raft. This layer has thick strong lines and is put firmly on the bed to prevent warping. This setting adjust the amount of material used for the base layer.')
148 setting('raft_interface_material_amount', 100, int, 'expert', 'Raft').setRange(0,100).setLabel('Interface material amount (%)', 'The interface layer is a weak thin layer between the base layer and the printed object. It is designed to has little material to make it easy to break the base off the printed object. This setting adjusts the amount of material used for the interface layer.')
149 setting('hop_on_move', False, bool, 'expert', 'Hop').setLabel('Enable hop on move', 'When moving from print position to print position, raise the printer head 0.2mm so it does not knock off the print (experimental).')
151 setting('model_matrix', '1,0,0,0,1,0,0,0,1', str, 'hidden', 'hidden')
152 setting('plugin_config', '', str, 'hidden', 'hidden')
153 setting('object_center_x', -1, float, 'hidden', 'hidden')
154 setting('object_center_y', -1, float, 'hidden', 'hidden')
156 setting('start.gcode', """;Sliced {filename} at: {day} {date} {time}
157 ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
158 ;Print time: {print_time}
159 ;Filament used: {filament_amount}m {filament_weight}g
160 ;Filament cost: {filament_cost}
162 G90 ;absolute positioning
163 M107 ;start with the fan off
165 G28 X0 Y0 ;move X/Y to min endstops
166 G28 Z0 ;move Z to min endstops
168 G1 Z15.0 F{max_z_speed} ;move the platform down 15mm
170 G92 E0 ;zero the extruded length
171 G1 F200 E3 ;extrude 3mm of feed stock
172 G92 E0 ;zero the extruded length again
175 """, str, 'alteration', 'alteration')
176 #######################################################################################
177 setting('end.gcode', """;End GCode
178 M104 S0 ;extruder heater off
179 M140 S0 ;heated bed heater off (if you have it)
181 G91 ;relative positioning
182 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
183 G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
184 G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
187 G90 ;absolute positioning
188 """, str, 'alteration', 'alteration')
189 #######################################################################################
190 setting('support_start.gcode', '', str, 'alteration', 'alteration')
191 setting('support_end.gcode', '', str, 'alteration', 'alteration')
192 setting('cool_start.gcode', '', str, 'alteration', 'alteration')
193 setting('cool_end.gcode', '', str, 'alteration', 'alteration')
194 setting('replace.csv', '', str, 'alteration', 'alteration')
195 #######################################################################################
196 setting('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.
199 G91 ;relative positioning
200 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
201 G1 Z+0.5 E-5 F{travel_speed} ;move Z up a bit and retract filament even more
202 G90 ;absolute positioning
204 G1 Z{clear_z} F{max_z_speed}
206 G1 X{object_center_x} Y{object_center_y} F{travel_speed}
209 """, str, 'alteration', 'alteration')
210 #######################################################################################
211 setting('switchExtruder.gcode', """;Switch between the current extruder and the next extruder, when printing with multiple extruders.
216 G1 X{new_x} Y{new_y} Z{new_z} F{travel_speed}
219 """, str, 'alteration', 'alteration')
221 setting('startMode', 'Simple', ['Simple', 'Normal'], 'preference', 'hidden')
222 setting('lastFile', os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'resources', 'example', 'UltimakerRobot_support.stl')), str, 'preference', 'hidden')
223 setting('machine_width', '205', float, 'preference', 'hidden')
224 setting('machine_depth', '205', float, 'preference', 'hidden')
225 setting('machine_height', '200', float, 'preference', 'hidden')
226 setting('machine_type', 'unknown', str, 'preference', 'hidden')
227 setting('machine_center_is_zero', 'False', bool, 'preference', 'hidden')
228 setting('ultimaker_extruder_upgrade', 'False', bool, 'preference', 'hidden')
229 setting('has_heated_bed', 'False', bool, 'preference', 'hidden')
230 setting('reprap_name', 'RepRap', str, 'preference', 'hidden')
231 setting('extruder_amount', '1', int, 'preference', 'hidden')
232 setting('extruder_offset_x1', '-21.6', float, 'preference', 'hidden')
233 setting('extruder_offset_y1', '0.0', float, 'preference', 'hidden')
234 setting('extruder_offset_x2', '0.0', float, 'preference', 'hidden')
235 setting('extruder_offset_y2', '0.0', float, 'preference', 'hidden')
236 setting('extruder_offset_x3', '0.0', float, 'preference', 'hidden')
237 setting('extruder_offset_y3', '0.0', float, 'preference', 'hidden')
238 setting('filament_density', '1300', float, 'preference', 'hidden')
239 setting('steps_per_e', '0', float, 'preference', 'hidden')
240 setting('serial_port', 'AUTO', str, 'preference', 'hidden')
241 setting('serial_port_auto', '', str, 'preference', 'hidden')
242 setting('serial_baud', 'AUTO', str, 'preference', 'hidden')
243 setting('serial_baud_auto', '', int, 'preference', 'hidden')
244 setting('save_profile', 'False', bool, 'preference', 'hidden')
245 setting('filament_cost_kg', '0', float, 'preference', 'hidden')
246 setting('filament_cost_meter', '0', float, 'preference', 'hidden')
247 setting('sdpath', '', str, 'preference', 'hidden')
248 setting('sdshortnames', 'False', bool, 'preference', 'hidden')
249 setting('check_for_updates', 'True', bool, 'preference', 'hidden')
250 setting('submit_slice_information', 'False', bool, 'preference', 'hidden')
252 setting('planner_always_autoplace', 'True', bool, 'preference', 'hidden')
253 setting('extruder_head_size_min_x', '75.0', float, 'preference', 'hidden')
254 setting('extruder_head_size_min_y', '18.0', float, 'preference', 'hidden')
255 setting('extruder_head_size_max_x', '18.0', float, 'preference', 'hidden')
256 setting('extruder_head_size_max_y', '35.0', float, 'preference', 'hidden')
257 setting('extruder_head_size_height', '60.0', float, 'preference', 'hidden')
259 setting('model_colour', '#7AB645', str, 'preference', 'hidden')
260 setting('model_colour2', '#CB3030', str, 'preference', 'hidden')
261 setting('model_colour3', '#DDD93C', str, 'preference', 'hidden')
262 setting('model_colour4', '#4550D3', str, 'preference', 'hidden')
264 setting('window_maximized', 'False', bool, 'preference', 'hidden')
265 setting('window_pos_x', '-1', float, 'preference', 'hidden')
266 setting('window_pos_y', '-1', float, 'preference', 'hidden')
267 setting('window_width', '-1', float, 'preference', 'hidden')
268 setting('window_height', '-1', float, 'preference', 'hidden')
269 setting('window_normal_sash', '320', float, 'preference', 'hidden')
271 #########################################################
272 ## Profile and preferences functions
273 #########################################################
277 if platform.system() == "Windows":
278 basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
279 #If we have a frozen python install, we need to step out of the library.zip
280 if hasattr(sys, 'frozen'):
281 basePath = os.path.normpath(os.path.join(basePath, ".."))
283 basePath = os.path.expanduser('~/.cura/%s' % version.getVersion(False))
284 if not os.path.isdir(basePath):
285 os.makedirs(basePath)
288 def getDefaultProfilePath():
289 return os.path.join(getBasePath(), 'current_profile.ini')
291 def loadProfile(filename):
292 #Read a configuration file as global config
293 profileParser = ConfigParser.ConfigParser()
295 profileParser.read(filename)
296 except ConfigParser.ParsingError:
299 for set in settingsList:
300 if set.isPreference():
303 if set.isAlteration():
304 section = 'alterations'
305 if profileParser.has_option(section, set.getName()):
306 set.setValue(unicode(profileParser.get(section, set.getName()), 'utf-8', 'replace'))
308 def saveProfile(filename):
309 #Save the current profile to an ini file
310 profileParser = ConfigParser.ConfigParser()
311 profileParser.add_section('profile')
312 profileParser.add_section('alterations')
314 for set in settingsList:
315 if set.isPreference():
317 if set.isAlteration():
318 profileParser.set('alterations', set.getName(), set.getValue().encode('utf-8'))
320 profileParser.set('profile', set.getName(), set.getValue().encode('utf-8'))
322 profileParser.write(open(filename, 'w'))
325 #Read a configuration file as global config
327 for set in settingsList:
328 if set.isPreference():
330 set.setValue(set.getDefault())
332 if getPreference('machine_type') == 'ultimaker':
333 putProfileSetting('nozzle_size', '0.4')
334 if getPreference('ultimaker_extruder_upgrade') == 'True':
335 putProfileSetting('retraction_enable', 'True')
337 putProfileSetting('nozzle_size', '0.5')
339 def loadProfileFromString(options):
340 options = base64.b64decode(options)
341 options = zlib.decompress(options)
342 (profileOpts, alt) = options.split('\f', 1)
343 global settingsDictionary
344 for option in profileOpts.split('\b'):
346 (key, value) = option.split('=', 1)
347 if key in settingsDictionary:
348 if settingsDictionary[key].isProfile():
349 settingsDictionary[key].setValue(value)
350 for option in alt.split('\b'):
352 (key, value) = option.split('=', 1)
353 if key in settingsDictionary:
354 if settingsDictionary[key].isAlteration():
355 settingsDictionary[key].setValue(value)
357 def getProfileString():
361 for set in settingsList:
363 if set.getName() in tempOverride:
364 p.append(set.getName() + "=" + tempOverride[set.getName()])
366 p.append(set.getName() + "=" + set.getValue())
367 if set.isAlteration():
368 if set.getName() in tempOverride:
369 alt.append(set.getName() + "=" + tempOverride[set.getName()])
371 alt.append(set.getName() + "=" + set.getValue())
372 ret = '\b'.join(p) + '\f' + '\b'.join(alt)
373 ret = base64.b64encode(zlib.compress(ret, 9))
376 def getGlobalPreferencesString():
379 for set in settingsList:
380 if set.isPreference():
381 p.append(set.getName() + "=" + set.getValue())
383 ret = base64.b64encode(zlib.compress(ret, 9))
387 def getProfileSetting(name):
388 if name in tempOverride:
389 return tempOverride[name]
390 global settingsDictionary
391 if name in settingsDictionary and settingsDictionary[name].isProfile():
392 return settingsDictionary[name].getValue()
393 print 'Error: "%s" not found in profile settings' % (name)
396 def getProfileSettingFloat(name):
398 setting = getProfileSetting(name).replace(',', '.')
399 return float(eval(setting, {}, {}))
400 except (ValueError, SyntaxError, TypeError):
403 def putProfileSetting(name, value):
404 #Check if we have a configuration file loaded, else load the default.
405 global settingsDictionary
406 if name in settingsDictionary and settingsDictionary[name].isProfile():
407 settingsDictionary[name].setValue(value)
409 def isProfileSetting(name):
410 global settingsDictionary
411 if name in settingsDictionary and settingsDictionary[name].isProfile():
415 ## Preferences functions
416 def getPreferencePath():
417 return os.path.join(getBasePath(), 'preferences.ini')
419 def getPreferenceFloat(name):
421 setting = getPreference(name).replace(',', '.')
422 return float(eval(setting, {}, {}))
423 except (ValueError, SyntaxError, TypeError):
426 def getPreferenceColour(name):
427 colorString = getPreference(name)
428 return [float(int(colorString[1:3], 16)) / 255, float(int(colorString[3:5], 16)) / 255, float(int(colorString[5:7], 16)) / 255, 1.0]
430 def loadPreferences(filename):
431 #Read a configuration file as global config
432 profileParser = ConfigParser.ConfigParser()
434 profileParser.read(filename)
435 except ConfigParser.ParsingError:
438 for set in settingsList:
439 if set.isPreference():
440 if profileParser.has_option('preferences', set.getName()):
441 set.setValue(unicode(profileParser.get('preferences', set.getName()), 'utf-8', 'replace'))
443 def savePreferences(filename):
444 #Save the current profile to an ini file
445 parser = ConfigParser.ConfigParser()
446 parser.add_section('preferences')
448 for set in settingsList:
449 if set.isPreference():
450 parser.set('preferences', set.getName(), set.getValue().encode('utf-8'))
451 parser.write(open(filename, 'w'))
453 def getPreference(name):
454 if name in tempOverride:
455 return tempOverride[name]
456 global settingsDictionary
457 if name in settingsDictionary and settingsDictionary[name].isPreference():
458 return settingsDictionary[name].getValue()
459 print 'Error: "%s" not found in profile settings' % (name)
462 def putPreference(name, value):
463 #Check if we have a configuration file loaded, else load the default.
464 global settingsDictionary
465 if name in settingsDictionary and settingsDictionary[name].isPreference():
466 settingsDictionary[name].setValue(value)
467 savePreferences(getPreferencePath())
469 def isPreference(name):
470 global settingsDictionary
471 if name in settingsDictionary and settingsDictionary[name].isPreference():
475 ## Temp overrides for multi-extruder slicing and the project planner.
477 def setTempOverride(name, value):
478 tempOverride[name] = unicode(value).encode("utf-8")
479 def clearTempOverride(name):
480 del tempOverride[name]
481 def resetTempOverride():
484 #########################################################
485 ## Utility functions to calculate common profile values
486 #########################################################
487 def calculateEdgeWidth():
488 wallThickness = getProfileSettingFloat('wall_thickness')
489 nozzleSize = getProfileSettingFloat('nozzle_size')
491 if wallThickness < nozzleSize:
494 lineCount = int(wallThickness / nozzleSize + 0.0001)
495 lineWidth = wallThickness / lineCount
496 lineWidthAlt = wallThickness / (lineCount + 1)
497 if lineWidth > nozzleSize * 1.5:
501 def calculateLineCount():
502 wallThickness = getProfileSettingFloat('wall_thickness')
503 nozzleSize = getProfileSettingFloat('nozzle_size')
505 if wallThickness < nozzleSize:
508 lineCount = int(wallThickness / nozzleSize + 0.0001)
509 lineWidth = wallThickness / lineCount
510 lineWidthAlt = wallThickness / (lineCount + 1)
511 if lineWidth > nozzleSize * 1.5:
515 def calculateSolidLayerCount():
516 layerHeight = getProfileSettingFloat('layer_height')
517 solidThickness = getProfileSettingFloat('solid_layer_thickness')
518 return int(math.ceil(solidThickness / layerHeight - 0.0001))
520 def getMachineCenterCoords():
521 if getPreference('machine_center_is_zero') == 'True':
523 return [getPreferenceFloat('machine_width') / 2, getPreferenceFloat('machine_depth') / 2]
525 def getObjectMatrix():
527 return map(float, getProfileSetting('model_matrix').split(','))
529 return [1,0,0, 0,1,0, 0,0,1]
532 #########################################################
533 ## Alteration file functions
534 #########################################################
535 def replaceTagMatch(m):
539 return pre + time.strftime('%H:%M:%S').encode('utf-8', 'replace')
541 return pre + time.strftime('%d %b %Y').encode('utf-8', 'replace')
543 return pre + time.strftime('%a').encode('utf-8', 'replace')
544 if tag == 'print_time':
545 return pre + '#P_TIME#'
546 if tag == 'filament_amount':
547 return pre + '#F_AMNT#'
548 if tag == 'filament_weight':
549 return pre + '#F_WGHT#'
550 if tag == 'filament_cost':
551 return pre + '#F_COST#'
552 if pre == 'F' and tag in ['print_speed', 'retraction_speed', 'travel_speed', 'max_z_speed', 'bottom_layer_speed', 'cool_min_feedrate']:
553 f = getProfileSettingFloat(tag) * 60
554 elif isProfileSetting(tag):
555 f = getProfileSettingFloat(tag)
556 elif isPreference(tag):
557 f = getProfileSettingFloat(tag)
559 return '%s?%s?' % (pre, tag)
561 return pre + str(int(f))
564 def replaceGCodeTags(filename, gcodeInt):
565 f = open(filename, 'r+')
567 data = data.replace('#P_TIME#', ('%5d:%02d' % (int(gcodeInt.totalMoveTimeMinute / 60), int(gcodeInt.totalMoveTimeMinute % 60)))[-8:])
568 data = data.replace('#F_AMNT#', ('%8.2f' % (gcodeInt.extrusionAmount / 1000))[-8:])
569 data = data.replace('#F_WGHT#', ('%8.2f' % (gcodeInt.calculateWeight() * 1000))[-8:])
570 cost = gcodeInt.calculateCost()
573 data = data.replace('#F_COST#', ('%8s' % (cost.split(' ')[0]))[-8:])
578 ### Get aleration raw contents. (Used internally in Cura)
579 def getAlterationFile(filename):
580 if filename in tempOverride:
581 return tempOverride[filename]
582 global settingsDictionary
583 if filename in settingsDictionary and settingsDictionary[filename].isAlteration():
584 return settingsDictionary[filename].getValue()
585 print 'Error: "%s" not found in profile settings' % (filename)
588 def setAlterationFile(name, value):
589 #Check if we have a configuration file loaded, else load the default.
590 global settingsDictionary
591 if name in settingsDictionary and settingsDictionary[name].isAlteration():
592 settingsDictionary[name].setValue(value)
593 saveProfile(getDefaultProfilePath())
595 ### Get the alteration file for output. (Used by Skeinforge)
596 def getAlterationFileContents(filename, extruderCount = 1):
599 alterationContents = getAlterationFile(filename)
600 if filename == 'start.gcode':
601 #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.
602 #We also set our steps per E here, if configured.
603 eSteps = getPreferenceFloat('steps_per_e')
605 prefix += 'M92 E%f\n' % (eSteps)
606 temp = getProfileSettingFloat('print_temperature')
608 if getPreference('has_heated_bed') == 'True':
609 bedTemp = getProfileSettingFloat('print_bed_temperature')
611 if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
612 prefix += 'M140 S%f\n' % (bedTemp)
613 if temp > 0 and not '{print_temperature}' in alterationContents:
614 if extruderCount > 0:
615 for n in xrange(1, extruderCount):
617 if n > 0 and getProfileSettingFloat('print_temperature%d' % (n+1)) > 0:
618 t = getProfileSettingFloat('print_temperature%d' % (n+1))
619 prefix += 'M104 T%d S%f\n' % (n, temp)
620 for n in xrange(0, extruderCount):
622 if n > 0 and getProfileSettingFloat('print_temperature%d' % (n+1)) > 0:
623 t = getProfileSettingFloat('print_temperature%d' % (n+1))
624 prefix += 'M109 T%d S%f\n' % (n, temp)
627 prefix += 'M109 S%f\n' % (temp)
628 if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
629 prefix += 'M190 S%f\n' % (bedTemp)
630 elif filename == 'end.gcode':
631 #Append the profile string to the end of the GCode, so we can load it from the GCode file later.
632 postfix = ';CURA_PROFILE_STRING:%s\n' % (getProfileString())
633 elif filename == 'replace.csv':
634 #Always remove the extruder on/off M codes. These are no longer needed in 5D printing.
635 prefix = 'M101\nM103\n'
636 elif filename == 'support_start.gcode' or filename == 'support_end.gcode':
637 #Add support start/end code
638 if getProfileSetting('support_dual_extrusion') == 'True' and int(getPreference('extruder_amount')) > 1:
639 if filename == 'support_start.gcode':
640 setTempOverride('extruder', '1')
642 setTempOverride('extruder', '0')
643 alterationContents = getAlterationFileContents('switchExtruder.gcode')
644 clearTempOverride('extruder')
646 alterationContents = ''
647 return unicode(prefix + re.sub("(.)\{([^\}]*)\}", replaceTagMatch, alterationContents).rstrip() + '\n' + postfix).strip().encode('utf-8') + '\n'
651 def getPluginConfig():
653 return pickle.loads(getProfileSetting('plugin_config'))
657 def setPluginConfig(config):
658 putProfileSetting('plugin_config', pickle.dumps(config))
660 def getPluginBasePaths():
662 if platform.system() != "Windows":
663 ret.append(os.path.expanduser('~/.cura/plugins/'))
664 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
665 ret.append(os.path.normpath(os.path.join(resources.resourceBasePath, "Cura/plugins")))
667 ret.append(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'plugins')))
672 for basePath in getPluginBasePaths():
673 for filename in glob.glob(os.path.join(basePath, '*.py')):
674 filename = os.path.basename(filename)
675 if filename.startswith('_'):
677 with open(os.path.join(basePath, filename), "r") as f:
678 item = {'filename': filename, 'name': None, 'info': None, 'type': None, 'params': []}
681 if not line.startswith('#'):
683 line = line[1:].split(':', 1)
686 if line[0].upper() == 'NAME':
687 item['name'] = line[1].strip()
688 elif line[0].upper() == 'INFO':
689 item['info'] = line[1].strip()
690 elif line[0].upper() == 'TYPE':
691 item['type'] = line[1].strip()
692 elif line[0].upper() == 'DEPEND':
694 elif line[0].upper() == 'PARAM':
695 m = re.match('([a-zA-Z][a-zA-Z0-9_]*)\(([a-zA-Z_]*)(?::([^\)]*))?\) +(.*)', line[1].strip())
697 item['params'].append({'name': m.group(1), 'type': m.group(2), 'default': m.group(3), 'description': m.group(4)})
699 print "Unknown item in effect meta data: %s %s" % (line[0], line[1])
700 if item['name'] != None and item['type'] == 'postprocess':
704 def runPostProcessingPlugins(gcodefilename):
705 pluginConfigList = getPluginConfig()
706 pluginList = getPluginList()
708 for pluginConfig in pluginConfigList:
710 for pluginTest in pluginList:
711 if pluginTest['filename'] == pluginConfig['filename']:
717 for basePath in getPluginBasePaths():
718 testFilename = os.path.join(basePath, pluginConfig['filename'])
719 if os.path.isfile(testFilename):
720 pythonFile = testFilename
721 if pythonFile is None:
724 locals = {'filename': gcodefilename}
725 for param in plugin['params']:
726 value = param['default']
727 if param['name'] in pluginConfig['params']:
728 value = pluginConfig['params'][param['name']]
730 if param['type'] == 'float':
734 value = float(param['default'])
736 locals[param['name']] = value
738 execfile(pythonFile, locals)
740 locationInfo = traceback.extract_tb(sys.exc_info()[2])[-1]
741 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])
744 def getSDcardDrives():
746 if platform.system() == "Windows":
747 from ctypes import windll
748 bitmask = windll.kernel32.GetLogicalDrives()
749 for letter in string.uppercase:
751 drives.append(letter + ':/')
753 if platform.system() == "Darwin":
755 for volume in glob.glob('/Volumes/*'):
756 if stat.S_ISLNK(os.lstat(volume).st_mode):
758 drives.append(volume)