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
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
14 settingsDictionary = {}
17 def __init__(self, name, default, type, category, subcategory):
21 self._default = unicode(default)
22 self._value = self._default
24 self._category = category
25 self._subcategory = subcategory
28 global settingsDictionary
29 settingsDictionary[name] = self
31 settingsList.append(self)
33 def setLabel(self, label, tooltip = ''):
35 self._tooltip = tooltip
38 #########################################################
40 #########################################################
41 setting('layer_height', 0.2, float, 'basic', 'Quality').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')
42 setting('wall_thickness', 0.8, float, 'basic', 'Quality').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.')
43 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.')
44 setting('solid_layer_thickness', 0.6, float, 'basic', 'Fill').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.')
45 setting('fill_density', 20, float, 'basic', 'Fill').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.')
46 setting('nozzle_size', 0.4, float, 'advanced', 'Machine').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.')
47 setting('skirt_line_count', 1, int, 'advanced', 'Skirt').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.')
48 setting('skirt_gap', 3.0, float, 'advanced', 'Skirt').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.')
49 setting('print_speed', 50, float, 'basic', 'Speed & Temperature').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.')
50 setting('print_temperature', 220, int, 'basic', 'Speed & Temperature').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.')
51 setting('print_temperature2', 0, int, 'basic', 'Speed & Temperature').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.')
52 setting('print_temperature3', 0, int, 'basic', 'Speed & Temperature').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.')
53 setting('print_temperature4', 0, int, 'basic', 'Speed & Temperature').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.')
54 setting('print_bed_temperature', 70, int, 'basic', 'Speed & Temperature').setLabel('Bed temperature (C)', 'Temperature used for the heated printer bed. Set at 0 to pre-heat yourself.')
55 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.')
56 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.')
57 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.')
58 setting('filament_diameter', 2.89, float, 'basic', 'Filament').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.')
59 setting('filament_diameter2', 0, float, 'basic', 'Filament').setLabel('Diameter2 (mm)', 'Diameter of your filament for the 2nd nozzle. Use 0 to use the same diameter as for nozzle 1.')
60 setting('filament_diameter3', 0, float, 'basic', 'Filament').setLabel('Diameter3 (mm)', 'Diameter of your filament for the 3th nozzle. Use 0 to use the same diameter as for nozzle 1.')
61 setting('filament_diameter4', 0, float, 'basic', 'Filament').setLabel('Diameter4 (mm)', 'Diameter of your filament for the 4th nozzle. Use 0 to use the same diameter as for nozzle 1.')
62 setting('filament_density', 1.00, float, 'basic', 'Filament').setLabel('Packing Density', 'Packing density of your filament. This should be 1.00 for PLA and 0.85 for ABS')
63 setting('retraction_min_travel', 5.0, float, 'advanced', 'Retraction').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')
64 setting('retraction_speed', 40.0, float, 'advanced', 'Retraction').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.')
65 setting('retraction_amount', 4.5, float, 'advanced', 'Retraction').setLabel('Distance (mm)', 'Amount of retraction, set at 0 for no retraction at all. A value of 2.0mm seems to generate good results.')
66 setting('retraction_extra', 0.0, float, 'advanced', 'Retraction').setLabel('Extra length on start (mm)', 'Extra extrusion amount when restarting after a retraction, to better "Prime" your extruder after retraction.')
67 setting('bottom_thickness', 0.3, float, 'advanced', 'Quality').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.')
68 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.')
69 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.')
70 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.')
71 setting('travel_speed', 150.0, float, 'advanced', 'Speed').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.')
72 setting('max_z_speed', 3.0, float, 'expert', 'Speed').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.')
73 setting('bottom_layer_speed', 20, float, 'advanced', 'Speed').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.')
74 setting('cool_min_layer_time', 5, float, 'advanced', 'Cool').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.')
75 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.')
76 setting('fan_layer', 1, int, 'expert', 'Cool').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.')
77 setting('fan_speed', 100, int, 'expert', 'Cool').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.')
78 setting('fan_speed_max', 100, int, 'expert', 'Cool').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%.')
79 setting('cool_min_feedrate', 10, float, 'expert', 'Cool').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.')
80 setting('extra_base_wall_thickness', 0.0, float, 'expert', 'Accuracy').setLabel('Extra Wall thickness for bottom/top (mm)', 'Additional wall thickness of the bottom and top layers.')
81 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')
82 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\'')
83 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.')
84 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.')
85 setting('fill_overlap', 15, int, 'expert', 'Infill').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.')
86 setting('support_rate', 50, int, 'expert', 'Support').setLabel('Material amount (%)', 'Amount of material used for support, less material gives a weaker support structure which is easier to remove.')
87 setting('support_distance', 0.5, float, 'expert', 'Support').setLabel('Distance from object (mm)', 'Distance between the support structure and the object. Empty gap in which no support structure is printed.')
88 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.')
89 setting('bridge_speed', 100, int, 'expert', 'Bridge').setLabel('Bridge speed (%)', 'Speed at which layers with bridges are printed, compared to normal printing speed.')
90 setting('raft_margin', 5, float, 'expert', 'Raft').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.')
91 setting('raft_base_material_amount', 100, int, 'expert', 'Raft').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.')
92 setting('raft_interface_material_amount', 100, int, 'expert', 'Raft').setLabel('Interface material amount (%)', 'raft_interface_material_amount', '100', '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.')
93 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).')
95 setting('model_matrix', '1,0,0,0,1,0,0,0,1', string, 'hidden', 'hidden')
96 setting('plugin_config', '', string, 'hidden', 'hidden')
97 setting('object_center_x', -1, float, 'hidden', 'hidden')
98 setting('object_center_y', -1, float, 'hidden', 'hidden')
100 #Single place to store the defaults, so we have a consistent set of default settings.
101 profileDefaultSettings = {
102 'nozzle_size': '0.4',
103 'layer_height': '0.2',
104 'wall_thickness': '0.8',
105 'solid_layer_thickness': '0.6',
106 'fill_density': '20',
107 'skirt_line_count': '1',
110 'print_temperature': '220',
111 'print_temperature2': '0',
112 'print_temperature3': '0',
113 'print_temperature4': '0',
114 'print_bed_temperature': '70',
116 'filament_diameter': '2.89',
117 'filament_diameter2': '0',
118 'filament_diameter3': '0',
119 'filament_diameter4': '0',
120 'filament_density': '1.00',
121 'retraction_min_travel': '5.0',
122 'retraction_enable': 'False',
123 'retraction_speed': '40.0',
124 'retraction_amount': '4.5',
125 'retraction_extra': '0.0',
126 'retract_on_jumps_only': 'True',
127 'travel_speed': '150',
128 'max_z_speed': '3.0',
129 'bottom_layer_speed': '20',
130 'cool_min_layer_time': '5',
131 'fan_enabled': 'True',
134 'fan_speed_max': '100',
135 'model_matrix': '1,0,0,0,1,0,0,0,1',
136 'extra_base_wall_thickness': '0.0',
137 'sequence': 'Loops > Perimeter > Infill',
138 'force_first_layer_sequence': 'True',
139 'infill_type': 'Line',
141 'fill_overlap': '15',
142 'support_rate': '50',
143 'support_distance': '0.5',
144 'support_dual_extrusion': 'False',
146 'enable_skin': 'False',
147 'enable_raft': 'False',
148 'cool_min_feedrate': '10',
149 'bridge_speed': '100',
151 'raft_base_material_amount': '100',
152 'raft_interface_material_amount': '100',
153 'bottom_thickness': '0.3',
154 'hop_on_move': 'False',
156 'object_center_x': '-1',
157 'object_center_y': '-1',
158 'object_sink': '0.0',
160 'gcode_extension': 'gcode',
161 'alternative_center': '',
168 alterationDefault = {
169 #######################################################################################
170 'start.gcode': """;Sliced {filename} at: {day} {date} {time}
171 ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
172 ;Print time: {print_time}
173 ;Filament used: {filament_amount}m {filament_weight}g
174 ;Filament cost: {filament_cost}
176 G90 ;absolute positioning
177 M107 ;start with the fan off
179 G28 X0 Y0 ;move X/Y to min endstops
180 G28 Z0 ;move Z to min endstops
182 G1 Z15.0 F{max_z_speed} ;move the platform down 15mm
184 G92 E0 ;zero the extruded length
185 G1 F200 E3 ;extrude 3mm of feed stock
186 G92 E0 ;zero the extruded length again
190 #######################################################################################
191 'end.gcode': """;End GCode
192 M104 S0 ;extruder heater off
193 M140 S0 ;heated bed heater off (if you have it)
195 G91 ;relative positioning
196 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
197 G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
198 G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
201 G90 ;absolute positioning
203 #######################################################################################
204 'support_start.gcode': '',
205 'support_end.gcode': '',
206 'cool_start.gcode': '',
207 'cool_end.gcode': '',
209 #######################################################################################
210 '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.
213 G91 ;relative positioning
214 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
215 G1 Z+0.5 E-5 F{travel_speed} ;move Z up a bit and retract filament even more
216 G90 ;absolute positioning
218 G1 Z{clear_z} F{max_z_speed}
220 G1 X{object_center_x} Y{object_center_y} F{travel_speed}
224 #######################################################################################
225 'switchExtruder.gcode': """;Switch between the current extruder and the next extruder, when printing with multiple extruders.
230 G1 X{new_x} Y{new_y} Z{new_z} F{travel_speed}
235 preferencesDefaultSettings = {
236 'startMode': 'Simple',
237 'lastFile': os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'resources', 'example', 'UltimakerRobot_support.stl')),
238 'machine_width': '205',
239 'machine_depth': '205',
240 'machine_height': '200',
241 'machine_type': 'unknown',
242 'machine_center_is_zero': 'False',
243 'ultimaker_extruder_upgrade': 'False',
244 'has_heated_bed': 'False',
245 'reprap_name': 'RepRap',
246 'extruder_amount': '1',
247 'extruder_offset_x1': '-22.0',
248 'extruder_offset_y1': '0.0',
249 'extruder_offset_x2': '0.0',
250 'extruder_offset_y2': '0.0',
251 'extruder_offset_x3': '0.0',
252 'extruder_offset_y3': '0.0',
253 'filament_density': '1300',
255 'serial_port': 'AUTO',
256 'serial_port_auto': '',
257 'serial_baud': 'AUTO',
258 'serial_baud_auto': '',
259 'slicer': 'Cura (Skeinforge based)',
260 'save_profile': 'False',
261 'filament_cost_kg': '0',
262 'filament_cost_meter': '0',
264 'sdshortnames': 'False',
265 'check_for_updates': 'True',
266 'submit_slice_information': 'False',
268 'planner_always_autoplace': 'True',
269 'extruder_head_size_min_x': '75.0',
270 'extruder_head_size_min_y': '18.0',
271 'extruder_head_size_max_x': '18.0',
272 'extruder_head_size_max_y': '35.0',
273 'extruder_head_size_height': '60.0',
275 'model_colour': '#7AB645',
276 'model_colour2': '#CB3030',
277 'model_colour3': '#DDD93C',
278 'model_colour4': '#4550D3',
280 'window_maximized': 'False',
281 'window_pos_x': '-1',
282 'window_pos_y': '-1',
283 'window_width': '-1',
284 'window_height': '-1',
285 'window_normal_sash': '320',
288 #########################################################
289 ## Profile and preferences functions
290 #########################################################
294 if platform.system() == "Windows":
295 basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
296 #If we have a frozen python install, we need to step out of the library.zip
297 if hasattr(sys, 'frozen'):
298 basePath = os.path.normpath(os.path.join(basePath, ".."))
300 basePath = os.path.expanduser('~/.cura/%s' % version.getVersion(False))
301 if not os.path.isdir(basePath):
302 os.makedirs(basePath)
305 def getDefaultProfilePath():
306 return os.path.join(getBasePath(), 'current_profile.ini')
308 def loadGlobalProfile(filename):
309 #Read a configuration file as global config
310 global globalProfileParser
311 globalProfileParser = ConfigParser.ConfigParser()
313 globalProfileParser.read(filename)
314 except ConfigParser.ParsingError:
317 def resetGlobalProfile():
318 #Read a configuration file as global config
319 global globalProfileParser
320 globalProfileParser = ConfigParser.ConfigParser()
322 if getPreference('machine_type') == 'ultimaker':
323 putProfileSetting('nozzle_size', '0.4')
324 if getPreference('ultimaker_extruder_upgrade') == 'True':
325 putProfileSetting('retraction_enable', 'True')
327 putProfileSetting('nozzle_size', '0.5')
329 def saveGlobalProfile(filename):
330 #Save the current profile to an ini file
331 globalProfileParser.write(open(filename, 'w'))
333 def loadGlobalProfileFromString(options):
334 global globalProfileParser
335 globalProfileParser = ConfigParser.ConfigParser()
336 globalProfileParser.add_section('profile')
337 globalProfileParser.add_section('alterations')
338 options = base64.b64decode(options)
339 options = zlib.decompress(options)
340 (profileOpts, alt) = options.split('\f', 1)
341 for option in profileOpts.split('\b'):
343 (key, value) = option.split('=', 1)
344 globalProfileParser.set('profile', key, value)
345 for option in alt.split('\b'):
347 (key, value) = option.split('=', 1)
348 globalProfileParser.set('alterations', key, value)
350 def getGlobalProfileString():
351 global globalProfileParser
352 if not globals().has_key('globalProfileParser'):
353 loadGlobalProfile(getDefaultProfilePath())
358 if globalProfileParser.has_section('profile'):
359 for key in globalProfileParser.options('profile'):
360 if key in tempOverride:
361 p.append(key + "=" + tempOverride[key])
364 p.append(key + "=" + globalProfileParser.get('profile', key))
365 if globalProfileParser.has_section('alterations'):
366 for key in globalProfileParser.options('alterations'):
367 if key in tempOverride:
368 p.append(key + "=" + tempOverride[key])
371 alt.append(key + "=" + globalProfileParser.get('alterations', key))
372 for key in tempOverride:
373 if key not in tempDone:
374 p.append(key + "=" + tempOverride[key])
375 ret = '\b'.join(p) + '\f' + '\b'.join(alt)
376 ret = base64.b64encode(zlib.compress(ret, 9))
379 def getGlobalPreferencesString():
380 global globalPreferenceParser
381 if globalPreferenceParser is None:
382 globalPreferenceParser = ConfigParser.ConfigParser()
384 globalPreferenceParser.read(getPreferencePath())
385 except ConfigParser.ParsingError:
389 if globalPreferenceParser.has_section('preference'):
390 for key in globalPreferenceParser.options('preference'):
391 p.append(key + "=" + globalPreferenceParser.get('preference', key))
393 ret = base64.b64encode(zlib.compress(ret, 9))
397 def getProfileSetting(name):
398 if name in tempOverride:
399 return unicode(tempOverride[name], "utf-8")
400 #Check if we have a configuration file loaded, else load the default.
401 if not globals().has_key('globalProfileParser'):
402 loadGlobalProfile(getDefaultProfilePath())
403 if not globalProfileParser.has_option('profile', name):
404 if name in profileDefaultSettings:
405 default = profileDefaultSettings[name]
407 print("Missing default setting for: '" + name + "'")
408 profileDefaultSettings[name] = ''
410 if not globalProfileParser.has_section('profile'):
411 globalProfileParser.add_section('profile')
412 globalProfileParser.set('profile', name, str(default))
413 #print(name + " not found in profile, so using default: " + str(default))
415 return globalProfileParser.get('profile', name)
417 def getProfileSettingFloat(name):
419 setting = getProfileSetting(name).replace(',', '.')
420 return float(eval(setting, {}, {}))
421 except (ValueError, SyntaxError, TypeError):
424 def putProfileSetting(name, value):
425 #Check if we have a configuration file loaded, else load the default.
426 if not globals().has_key('globalProfileParser'):
427 loadGlobalProfile(getDefaultProfilePath())
428 if not globalProfileParser.has_section('profile'):
429 globalProfileParser.add_section('profile')
430 globalProfileParser.set('profile', name, str(value))
432 def isProfileSetting(name):
433 if name in profileDefaultSettings:
437 ## Preferences functions
438 global globalPreferenceParser
439 globalPreferenceParser = None
441 def getPreferencePath():
442 return os.path.join(getBasePath(), 'preferences.ini')
444 def getPreferenceFloat(name):
446 setting = getPreference(name).replace(',', '.')
447 return float(eval(setting, {}, {}))
448 except (ValueError, SyntaxError, TypeError):
451 def getPreferenceColour(name):
452 colorString = getPreference(name)
453 return [float(int(colorString[1:3], 16)) / 255, float(int(colorString[3:5], 16)) / 255, float(int(colorString[5:7], 16)) / 255, 1.0]
455 def getPreference(name):
456 if name in tempOverride:
457 return unicode(tempOverride[name])
458 global globalPreferenceParser
459 if globalPreferenceParser is None:
460 globalPreferenceParser = ConfigParser.ConfigParser()
462 globalPreferenceParser.read(getPreferencePath())
463 except ConfigParser.ParsingError:
465 if not globalPreferenceParser.has_option('preference', name):
466 if name in preferencesDefaultSettings:
467 default = preferencesDefaultSettings[name]
469 print("Missing default setting for: '" + name + "'")
470 preferencesDefaultSettings[name] = ''
472 if not globalPreferenceParser.has_section('preference'):
473 globalPreferenceParser.add_section('preference')
474 globalPreferenceParser.set('preference', name, str(default))
475 #print(name + " not found in preferences, so using default: " + str(default))
477 return unicode(globalPreferenceParser.get('preference', name), "utf-8")
479 def putPreference(name, value):
480 #Check if we have a configuration file loaded, else load the default.
481 global globalPreferenceParser
482 if globalPreferenceParser == None:
483 globalPreferenceParser = ConfigParser.ConfigParser()
485 globalPreferenceParser.read(getPreferencePath())
486 except ConfigParser.ParsingError:
488 if not globalPreferenceParser.has_section('preference'):
489 globalPreferenceParser.add_section('preference')
490 globalPreferenceParser.set('preference', name, unicode(value).encode("utf-8"))
491 globalPreferenceParser.write(open(getPreferencePath(), 'w'))
493 def isPreference(name):
494 if name in preferencesDefaultSettings:
498 ## Temp overrides for multi-extruder slicing and the project planner.
500 def setTempOverride(name, value):
501 tempOverride[name] = unicode(value).encode("utf-8")
502 def clearTempOverride(name):
503 del tempOverride[name]
504 def resetTempOverride():
507 #########################################################
508 ## Utility functions to calculate common profile values
509 #########################################################
510 def calculateEdgeWidth():
511 wallThickness = getProfileSettingFloat('wall_thickness')
512 nozzleSize = getProfileSettingFloat('nozzle_size')
514 if wallThickness < nozzleSize:
517 lineCount = int(wallThickness / nozzleSize + 0.0001)
518 lineWidth = wallThickness / lineCount
519 lineWidthAlt = wallThickness / (lineCount + 1)
520 if lineWidth > nozzleSize * 1.5:
524 def calculateLineCount():
525 wallThickness = getProfileSettingFloat('wall_thickness')
526 nozzleSize = getProfileSettingFloat('nozzle_size')
528 if wallThickness < nozzleSize:
531 lineCount = int(wallThickness / nozzleSize + 0.0001)
532 lineWidth = wallThickness / lineCount
533 lineWidthAlt = wallThickness / (lineCount + 1)
534 if lineWidth > nozzleSize * 1.5:
538 def calculateSolidLayerCount():
539 layerHeight = getProfileSettingFloat('layer_height')
540 solidThickness = getProfileSettingFloat('solid_layer_thickness')
541 return int(math.ceil(solidThickness / layerHeight - 0.0001))
543 def getMachineCenterCoords():
544 if getPreference('machine_center_is_zero') == 'True':
546 return [getPreferenceFloat('machine_width') / 2, getPreferenceFloat('machine_depth') / 2]
548 def getObjectMatrix():
550 return map(float, getProfileSetting('model_matrix').split(','))
552 return [1,0,0, 0,1,0, 0,0,1]
555 #########################################################
556 ## Alteration file functions
557 #########################################################
558 def replaceTagMatch(m):
562 return pre + time.strftime('%H:%M:%S').encode('utf-8', 'replace')
564 return pre + time.strftime('%d %b %Y').encode('utf-8', 'replace')
566 return pre + time.strftime('%a').encode('utf-8', 'replace')
567 if tag == 'print_time':
568 return pre + '#P_TIME#'
569 if tag == 'filament_amount':
570 return pre + '#F_AMNT#'
571 if tag == 'filament_weight':
572 return pre + '#F_WGHT#'
573 if tag == 'filament_cost':
574 return pre + '#F_COST#'
575 if pre == 'F' and tag in ['print_speed', 'retraction_speed', 'travel_speed', 'max_z_speed', 'bottom_layer_speed', 'cool_min_feedrate']:
576 f = getProfileSettingFloat(tag) * 60
577 elif isProfileSetting(tag):
578 f = getProfileSettingFloat(tag)
579 elif isPreference(tag):
580 f = getProfileSettingFloat(tag)
582 return '%s?%s?' % (pre, tag)
584 return pre + str(int(f))
587 def replaceGCodeTags(filename, gcodeInt):
588 f = open(filename, 'r+')
590 data = data.replace('#P_TIME#', ('%5d:%02d' % (int(gcodeInt.totalMoveTimeMinute / 60), int(gcodeInt.totalMoveTimeMinute % 60)))[-8:])
591 data = data.replace('#F_AMNT#', ('%8.2f' % (gcodeInt.extrusionAmount / 1000))[-8:])
592 data = data.replace('#F_WGHT#', ('%8.2f' % (gcodeInt.calculateWeight() * 1000))[-8:])
593 cost = gcodeInt.calculateCost()
596 data = data.replace('#F_COST#', ('%8s' % (cost.split(' ')[0]))[-8:])
601 ### Get aleration raw contents. (Used internally in Cura)
602 def getAlterationFile(filename):
603 #Check if we have a configuration file loaded, else load the default.
604 if not globals().has_key('globalProfileParser'):
605 loadGlobalProfile(getDefaultProfilePath())
607 if not globalProfileParser.has_option('alterations', filename):
608 if filename in alterationDefault:
609 default = alterationDefault[filename]
611 print("Missing default alteration for: '" + filename + "'")
612 alterationDefault[filename] = ''
614 if not globalProfileParser.has_section('alterations'):
615 globalProfileParser.add_section('alterations')
616 #print("Using default for: %s" % (filename))
617 globalProfileParser.set('alterations', filename, default)
618 return unicode(globalProfileParser.get('alterations', filename), "utf-8")
620 def setAlterationFile(filename, value):
621 #Check if we have a configuration file loaded, else load the default.
622 if not globals().has_key('globalProfileParser'):
623 loadGlobalProfile(getDefaultProfilePath())
624 if not globalProfileParser.has_section('alterations'):
625 globalProfileParser.add_section('alterations')
626 globalProfileParser.set('alterations', filename, value.encode("utf-8"))
627 saveGlobalProfile(getDefaultProfilePath())
629 ### Get the alteration file for output. (Used by Skeinforge)
630 def getAlterationFileContents(filename, extruderCount = 1):
633 alterationContents = getAlterationFile(filename)
634 if filename == 'start.gcode':
635 #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.
636 #We also set our steps per E here, if configured.
637 eSteps = getPreferenceFloat('steps_per_e')
639 prefix += 'M92 E%f\n' % (eSteps)
640 temp = getProfileSettingFloat('print_temperature')
642 if getPreference('has_heated_bed') == 'True':
643 bedTemp = getProfileSettingFloat('print_bed_temperature')
645 if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
646 prefix += 'M140 S%f\n' % (bedTemp)
647 if temp > 0 and not '{print_temperature}' in alterationContents:
648 if extruderCount > 0:
649 for n in xrange(1, extruderCount):
651 if n > 0 and getProfileSettingFloat('print_temperature%d' % (n+1)) > 0:
652 t = getProfileSettingFloat('print_temperature%d' % (n+1))
653 prefix += 'M104 T%d S%f\n' % (n, temp)
654 for n in xrange(0, extruderCount):
656 if n > 0 and getProfileSettingFloat('print_temperature%d' % (n+1)) > 0:
657 t = getProfileSettingFloat('print_temperature%d' % (n+1))
658 prefix += 'M109 T%d S%f\n' % (n, temp)
661 prefix += 'M109 S%f\n' % (temp)
662 if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
663 prefix += 'M190 S%f\n' % (bedTemp)
664 elif filename == 'end.gcode':
665 #Append the profile string to the end of the GCode, so we can load it from the GCode file later.
666 postfix = ';CURA_PROFILE_STRING:%s\n' % (getGlobalProfileString())
667 elif filename == 'replace.csv':
668 #Always remove the extruder on/off M codes. These are no longer needed in 5D printing.
669 prefix = 'M101\nM103\n'
670 elif filename == 'support_start.gcode' or filename == 'support_end.gcode':
671 #Add support start/end code
672 if getProfileSetting('support_dual_extrusion') == 'True' and int(getPreference('extruder_amount')) > 1:
673 if filename == 'support_start.gcode':
674 setTempOverride('extruder', '1')
676 setTempOverride('extruder', '0')
677 alterationContents = getAlterationFileContents('switchExtruder.gcode')
678 clearTempOverride('extruder')
680 alterationContents = ''
681 return unicode(prefix + re.sub("(.)\{([^\}]*)\}", replaceTagMatch, alterationContents).rstrip() + '\n' + postfix).strip().encode('utf-8') + '\n'
685 def getPluginConfig():
687 return pickle.loads(getProfileSetting('plugin_config'))
691 def setPluginConfig(config):
692 putProfileSetting('plugin_config', pickle.dumps(config))
694 def getPluginBasePaths():
696 if platform.system() != "Windows":
697 ret.append(os.path.expanduser('~/.cura/plugins/'))
698 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
699 ret.append(os.path.normpath(os.path.join(resources.resourceBasePath, "Cura/plugins")))
701 ret.append(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'plugins')))
706 for basePath in getPluginBasePaths():
707 for filename in glob.glob(os.path.join(basePath, '*.py')):
708 filename = os.path.basename(filename)
709 if filename.startswith('_'):
711 with open(os.path.join(basePath, filename), "r") as f:
712 item = {'filename': filename, 'name': None, 'info': None, 'type': None, 'params': []}
715 if not line.startswith('#'):
717 line = line[1:].split(':', 1)
720 if line[0].upper() == 'NAME':
721 item['name'] = line[1].strip()
722 elif line[0].upper() == 'INFO':
723 item['info'] = line[1].strip()
724 elif line[0].upper() == 'TYPE':
725 item['type'] = line[1].strip()
726 elif line[0].upper() == 'DEPEND':
728 elif line[0].upper() == 'PARAM':
729 m = re.match('([a-zA-Z][a-zA-Z0-9_]*)\(([a-zA-Z_]*)(?::([^\)]*))?\) +(.*)', line[1].strip())
731 item['params'].append({'name': m.group(1), 'type': m.group(2), 'default': m.group(3), 'description': m.group(4)})
733 print "Unknown item in effect meta data: %s %s" % (line[0], line[1])
734 if item['name'] != None and item['type'] == 'postprocess':
738 def runPostProcessingPlugins(gcodefilename):
739 pluginConfigList = getPluginConfig()
740 pluginList = getPluginList()
742 for pluginConfig in pluginConfigList:
744 for pluginTest in pluginList:
745 if pluginTest['filename'] == pluginConfig['filename']:
751 for basePath in getPluginBasePaths():
752 testFilename = os.path.join(basePath, pluginConfig['filename'])
753 if os.path.isfile(testFilename):
754 pythonFile = testFilename
755 if pythonFile is None:
758 locals = {'filename': gcodefilename}
759 for param in plugin['params']:
760 value = param['default']
761 if param['name'] in pluginConfig['params']:
762 value = pluginConfig['params'][param['name']]
764 if param['type'] == 'float':
768 value = float(param['default'])
770 locals[param['name']] = value
772 execfile(pythonFile, locals)
774 locationInfo = traceback.extract_tb(sys.exc_info()[2])[-1]
775 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])
778 def getSDcardDrives():
780 if platform.system() == "Windows":
781 from ctypes import windll
782 bitmask = windll.kernel32.GetLogicalDrives()
783 for letter in string.uppercase:
785 drives.append(letter + ':/')
787 if platform.system() == "Darwin":
789 for volume in glob.glob('/Volumes/*'):
790 if stat.S_ISLNK(os.lstat(volume).st_mode):
792 drives.append(volume)