1 from __future__ import absolute_import
2 from __future__ import division
3 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
5 import os, traceback, math, re, zlib, base64, time, sys, platform, glob, string, stat, types
6 import cPickle as pickle
7 if sys.version_info[0] < 3:
10 import configparser as ConfigParser
12 from Cura.util import resources
13 from Cura.util import version
14 from Cura.util import validators
16 #The settings dictionary contains a key/value reference to all possible settings. With the setting name as key.
17 settingsDictionary = {}
18 #The settings list is used to keep a full list of all the settings. This is needed to keep the settings in the proper order,
19 # as the dictionary will not contain insertion order.
22 #Currently selected machine (by index) Cura support multiple machines in the same preferences and can switch between them.
23 # Each machine has it's own index and unique name.
24 _selectedMachineIndex = 0
26 class setting(object):
27 #A setting object contains a configuration setting. These are globally accessible trough the quick access functions
28 # and trough the settingsDictionary function.
30 # * profile settings (settings that effect the slicing process and the print result)
31 # * preferences (settings that effect how cura works and acts)
32 # * machine settings (settings that relate to the physical configuration of your machine)
33 # * alterations (bad name copied from Skeinforge. These are the start/end code pieces)
34 # Settings have validators that check if the value is valid, but do not prevent invalid values!
35 # Settings have conditions that enable/disable this setting depending on other settings. (Ex: Dual-extrusion)
36 def __init__(self, name, default, type, category, subcategory):
40 self._default = unicode(default)
43 self._category = category
44 self._subcategory = subcategory
48 if type is types.FloatType:
49 validators.validFloat(self)
50 elif type is types.IntType:
51 validators.validInt(self)
53 global settingsDictionary
54 settingsDictionary[name] = self
56 settingsList.append(self)
58 def setLabel(self, label, tooltip = ''):
60 self._tooltip = tooltip
63 def setRange(self, minValue=None, maxValue=None):
64 if len(self._validators) < 1:
66 self._validators[0].minValue = minValue
67 self._validators[0].maxValue = maxValue
74 return _(self._tooltip)
76 def getCategory(self):
79 def getSubCategory(self):
80 return self._subcategory
82 def isPreference(self):
83 return self._category == 'preference'
85 def isMachineSetting(self):
86 return self._category == 'machine'
88 def isAlteration(self):
89 return self._category == 'alteration'
92 return not self.isAlteration() and not self.isPreference() and not self.isMachineSetting()
100 def getValue(self, index = None):
102 index = self.getValueIndex()
103 if index >= len(self._values):
105 return self._values[index]
107 def getDefault(self):
110 def setValue(self, value, index = None):
112 index = self.getValueIndex()
113 while index >= len(self._values):
114 self._values.append(self._default)
115 self._values[index] = unicode(value)
117 def getValueIndex(self):
118 if self.isMachineSetting():
119 global _selectedMachineIndex
120 return _selectedMachineIndex
124 result = validators.SUCCESS
126 for validator in self._validators:
127 res, err = validator.validate()
128 if res == validators.ERROR:
130 elif res == validators.WARNING and result != validators.ERROR:
132 if res != validators.SUCCESS:
134 return result, '\n'.join(msgs)
136 def addCondition(self, conditionFunction):
137 self._conditions.append(conditionFunction)
139 def checkConditions(self):
140 for condition in self._conditions:
145 #########################################################
147 #########################################################
149 #Define a fake _() function to fake the gettext tools in to generating strings for the profile settings.
153 setting('layer_height', 0.1, float, 'basic', _('Quality')).setRange(0.0001).setLabel(_("Layer height (mm)"), _("Layer height in millimeters.\nThis is the most important setting to determine the quality of your print. Normal quality prints are 0.1mm, high quality is 0.06mm. You can go up to 0.25mm with an Ultimaker for very fast prints at low quality."))
154 setting('wall_thickness', 0.8, float, 'basic', _('Quality')).setRange(0.0).setLabel(_("Shell thickness (mm)"), _("Thickness of the outside shell in the horizontal direction.\nThis is used in combination with the nozzle size to define the number\nof perimeter lines and the thickness of those perimeter lines."))
155 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."))
156 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 multiple of the layer thickness makes sense. And keep it near your wall thickness to make an evenly strong part."))
157 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 affect the outside of the print and only adjusts how strong the part becomes."))
158 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."))
159 setting('print_speed', 50, float, 'basic', _('Speed and 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."))
160 setting('print_temperature', 220, int, 'basic', _('Speed and 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."))
161 setting('print_temperature2', 0, int, 'basic', _('Speed and 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."))
162 setting('print_temperature3', 0, int, 'basic', _('Speed and 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."))
163 setting('print_temperature4', 0, int, 'basic', _('Speed and 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."))
164 setting('print_bed_temperature', 70, int, 'basic', _('Speed and Temperature')).setRange(0,340).setLabel(_("Bed temperature (C)"), _("Temperature used for the heated printer bed. Set at 0 to pre-heat yourself."))
165 setting('support', 'None', [_('None'), _('Touching buildplate'), _('Everywhere')], 'basic', _('Support')).setLabel(_("Support type"), _("Type of support structure build.\n\"Touching buildplate\" 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."))
166 setting('platform_adhesion', 'None', [_('None'), _('Brim'), _('Raft')], 'basic', _('Support')).setLabel(_("Platform adhesion type"), _("Different options that help in preventing corners from lifting due to warping.\nBrim adds a single layer thick flat area around your object which is easy to cut off afterwards, and the recommended option.\nRaft adds a thick raster at below the object and a thin interface between this and your object.\n(Note that enabling the brim or raft disables the skirt)"))
167 setting('support_dual_extrusion', 'Both', [_('Both'), _('First extruder'), _('Second extruder')], 'basic', _('Support')).setLabel(_("Support dual extrusion"), _("Which extruder to use for support material, for break-away support you can use both extruders.\nBut if one of the materials is more expensive then the other you could select an extruder to use for support material. This causes more extruder switches.\nYou can also use the 2nd extruder for soluble support materials."))
168 setting('wipe_tower', False, bool, 'basic', _('Dual extrusion')).setLabel(_("Wipe&prime tower"), _("The wipe-tower is a tower printed on every layer when switching between nozzles.\nThe old nozzle is wiped off on the tower before the new nozzle is used to print the 2nd color."))
169 setting('ooze_shield', False, bool, 'basic', _('Dual extrusion')).setLabel(_("Ooze shield"), _("The ooze shield is a 1 line thick shell around the object which stands a few mm from the object.\nThis shield catches any oozing from the unused nozzle in dual-extrusion."))
170 setting('filament_diameter', 2.85, 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."))
171 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."))
172 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."))
173 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."))
174 setting('filament_flow', 100., float, 'basic', _('Filament')).setRange(5,300).setLabel(_("Flow (%)"), _("Flow compensation, the amount of material extruded is multiplied by this value"))
175 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."))
176 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 4.5mm seems to generate good results."))
177 setting('retraction_dual_amount', 16.5, float, 'advanced', _('Retraction')).setRange(0).setLabel(_("Dual extrusion switch amount (mm)"), _("Amount of retraction when switching nozzle with dual-extrusion, set at 0 for no retraction at all. A value of 16.0mm seems to generate good results."))
178 setting('retraction_min_travel', 1.5, float, 'expert', _('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."))
179 setting('retraction_combing', True, bool, 'expert', _('Retraction')).setLabel(_("Enable combing"), _("Combing is the act of avoiding holes in the print for the head to travel over. If combing is disabled the printer head moves straight from the start point to the end point and it will always retract."))
180 setting('retraction_minimal_extrusion',0.1, float,'expert', _('Retraction')).setRange(0).setLabel(_("Minimal extrusion before retracting (mm)"), _("The minimal amount of extrusion that needs to be done before retracting again if a retraction needs to happen before this minimal is reached the retraction is ignored.\nThis avoids retraction a lot on the same piece of filament which flattens the filament and causes grinding issues."))
181 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."))
182 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."))
183 #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."))
184 setting('overlap_dual', 0.15, float, 'advanced', _('Quality')).setLabel(_("Dual extrusion overlap (mm)"), _("Add a certain amount of overlapping extrusion on dual-extrusion prints. This bonds the different colors better together."))
185 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."))
186 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."))
187 setting('infill_speed', 0.0, float, 'advanced', _('Speed')).setRange(0.0).setLabel(_("Infill speed (mm/s)"), _("Speed at which infill parts are printed. If set to 0 then the print speed is used for the infill. Printing the infill faster can greatly reduce printing, but this can negatively effect print quality.."))
188 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."))
189 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 essential during faster prints."))
191 setting('skirt_line_count', 1, int, 'expert', '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."))
192 setting('skirt_gap', 3.0, float, 'expert', '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."))
193 setting('skirt_minimal_length', 150.0, float, 'expert', 'Skirt').setRange(0).setLabel(_("Minimal length (mm)"), _("The minimal length of the skirt, if this minimal length is not reached it will add more skirt lines to reach this minimal lenght.\nNote: If the line count is set to 0 this is ignored."))
194 #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."))
195 #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.'))
196 setting('fan_full_height', 0.5, float, 'expert', _('Cool')).setRange(0).setLabel(_("Fan full on at height"), _("The height at which the fan is turned on completely. For the layers below this the fan speed is scaled linear with the fan off at layer 0."))
197 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."))
198 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 than 200%."))
199 setting('cool_min_feedrate', 10, float, 'expert', _('Cool')).setRange(0).setLabel(_("Minimum speed (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 than this minimal speed."))
200 setting('cool_head_lift', False, bool, 'expert', _('Cool')).setLabel(_("Cool head lift"), _("Lift the head if the minimal speed is hit because of cool slowdown, and wait the extra time so the minimal layer time is always hit."))
201 #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."))
202 #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')
203 #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\''))
204 #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."))
205 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."))
206 setting('solid_bottom', True, bool, 'expert', _('Infill')).setLabel(_("Solid infill bottom"), _("Create a solid bottom surface, if set to false the bottom is filled with the fill percentage. Useful for buildings."))
207 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."))
208 setting('support_fill_rate', 15, int, 'expert', _('Support')).setRange(0,100).setLabel(_("Fill amount (%)"), _("Amount of infill structure in the support material, less material gives weaker support which is easier to remove. 15% seems to be a good average."))
209 setting('support_xy_distance', 0.7, float, 'expert', _('Support')).setRange(0,10).setLabel(_("Distance X/Y (mm)"), _("Distance of the support material from the print, in the X/Y directions.\n0.7mm gives a nice distance from the print so the support does not stick to the print."))
210 setting('support_z_distance', 0.15, float, 'expert', _('Support')).setRange(0,10).setLabel(_("Distance Z (mm)"), _("Distance from the top/bottom of the support to the print. A small gap here makes it easier to remove the support but makes the print a bit uglier.\n0.15mm gives a good seperation of the support material."))
211 setting('spiralize', False, bool, 'expert', 'Spiralize').setLabel(_("Spiralize the outer contour"), _("Spiralize is smoothing out the Z move of the outer edge. This will create a steady Z increase over the whole print. This feature turns a solid object into a single walled print with a solid bottom."))
212 #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."))
213 setting('brim_line_count', 20, int, 'expert', _('Brim')).setRange(1,100).setLabel(_("Brim line amount"), _("The amount of lines used for a brim, more lines means a larger brim which sticks better, but this also makes your effective print area smaller."))
214 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 while using more material and leaving less are for your print."))
215 setting('raft_line_spacing', 1.0, float, 'expert', _('Raft')).setRange(0).setLabel(_("Line spacing (mm)"), _("When you are using the raft this is the distance between the centerlines of the raft line."))
216 setting('raft_base_thickness', 0.3, float, 'expert', _('Raft')).setRange(0).setLabel(_("Base thickness (mm)"), _("When you are using the raft this is the thickness of the base layer which is put down."))
217 setting('raft_base_linewidth', 0.7, float, 'expert', _('Raft')).setRange(0).setLabel(_("Base line width (mm)"), _("When you are using the raft this is the width of the base layer lines which are put down."))
218 setting('raft_interface_thickness', 0.2, float, 'expert', _('Raft')).setRange(0).setLabel(_("Interface thickness (mm)"), _("When you are using the raft this is the thickness of the interface layer which is put down."))
219 setting('raft_interface_linewidth', 0.2, float, 'expert', _('Raft')).setRange(0).setLabel(_("Interface line width (mm)"), _("When you are using the raft this is the width of the interface layer lines which are put down."))
220 #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)."))
221 setting('fix_horrible_union_all_type_a', False, bool, 'expert', _('Fix horrible')).setLabel(_("Combine everything (Type-A)"), _("This expert option adds all parts of the model together. The result is usually that internal cavities disappear. Depending on the model this can be intended or not. Enabling this option is at your own risk. Type-A is depended on the model normals and tries to keep some internal holes intact. Type-B ignores all internal holes and only keeps the outside shape per layer."))
222 setting('fix_horrible_union_all_type_b', False, bool, 'expert', _('Fix horrible')).setLabel(_("Combine everything (Type-B)"), _("This expert option adds all parts of the model together. The result is usually that internal cavities disappear. Depending on the model this can be intended or not. Enabling this option is at your own risk. Type-A is depended on the model normals and tries to keep some internal holes intact. Type-B ignores all internal holes and only keeps the outside shape per layer."))
223 setting('fix_horrible_use_open_bits', False, bool, 'expert', _('Fix horrible')).setLabel(_("Keep open faces"), _("This expert option keeps all the open bits of the model intact. Normally Cura tries to stitch up small holes and remove everything with big holes, but this option keeps bits that are not properly part of anything and just goes with whatever it is left. This option is usually not what you want, but it might enable you to slice models otherwise failing to produce proper paths.\nAs with all \"Fix horrible\" options, results may vary and use at your own risk."))
224 setting('fix_horrible_extensive_stitching', False, bool, 'expert', _('Fix horrible')).setLabel(_("Extensive stitching"), _("Extrensive stitching tries to fix up open holes in the model by closing the hole with touching polygons. This algorthm is quite expensive and could introduce a lot of processing time.\nAs with all \"Fix horrible\" options, results may vary and use at your own risk."))
226 setting('plugin_config', '', str, 'hidden', 'hidden')
227 setting('object_center_x', -1, float, 'hidden', 'hidden')
228 setting('object_center_y', -1, float, 'hidden', 'hidden')
230 setting('start.gcode', """;Sliced at: {day} {date} {time}
231 ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
232 ;Print time: {print_time}
233 ;Filament used: {filament_amount}m {filament_weight}g
234 ;Filament cost: {filament_cost}
235 ;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line
236 ;M109 S{print_temperature} ;Uncomment to add your own temperature line
238 G90 ;absolute positioning
239 M107 ;start with the fan off
241 G28 X0 Y0 ;move X/Y to min endstops
242 G28 Z0 ;move Z to min endstops
244 G1 Z15.0 F{travel_speed} ;move the platform down 15mm
246 G92 E0 ;zero the extruded length
247 G1 F200 E3 ;extrude 3mm of feed stock
248 G92 E0 ;zero the extruded length again
250 ;Put printing message on LCD screen
252 """, str, 'alteration', 'alteration')
253 #######################################################################################
254 setting('end.gcode', """;End GCode
255 M104 S0 ;extruder heater off
256 M140 S0 ;heated bed heater off (if you have it)
258 G91 ;relative positioning
259 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
260 G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
261 G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
264 G90 ;absolute positioning
265 """, str, 'alteration', 'alteration')
266 #######################################################################################
267 setting('start2.gcode', """;Sliced at: {day} {date} {time}
268 ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
269 ;Print time: {print_time}
270 ;Filament used: {filament_amount}m {filament_weight}g
271 ;Filament cost: {filament_cost}
272 ;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line
273 ;M104 S{print_temperature} ;Uncomment to add your own temperature line
274 ;M109 T1 S{print_temperature2} ;Uncomment to add your own temperature line
275 ;M109 T0 S{print_temperature} ;Uncomment to add your own temperature line
277 G90 ;absolute positioning
278 M107 ;start with the fan off
280 G28 X0 Y0 ;move X/Y to min endstops
281 G28 Z0 ;move Z to min endstops
283 G1 Z15.0 F{travel_speed} ;move the platform down 15mm
285 T1 ;Switch to the 2nd extruder
286 G92 E0 ;zero the extruded length
287 G1 F200 E10 ;extrude 10mm of feed stock
288 G92 E0 ;zero the extruded length again
289 G1 F200 E-{retraction_dual_amount}
291 T0 ;Switch to the first extruder
292 G92 E0 ;zero the extruded length
293 G1 F200 E10 ;extrude 10mm of feed stock
294 G92 E0 ;zero the extruded length again
296 ;Put printing message on LCD screen
298 """, str, 'alteration', 'alteration')
299 #######################################################################################
300 setting('end2.gcode', """;End GCode
301 M104 T0 S0 ;extruder heater off
302 M104 T1 S0 ;extruder heater off
303 M140 S0 ;heated bed heater off (if you have it)
305 G91 ;relative positioning
306 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
307 G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
308 G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
311 G90 ;absolute positioning
312 """, str, 'alteration', 'alteration')
313 #######################################################################################
314 setting('support_start.gcode', '', str, 'alteration', 'alteration')
315 setting('support_end.gcode', '', str, 'alteration', 'alteration')
316 setting('cool_start.gcode', '', str, 'alteration', 'alteration')
317 setting('cool_end.gcode', '', str, 'alteration', 'alteration')
318 setting('replace.csv', '', str, 'alteration', 'alteration')
319 #######################################################################################
320 setting('switchExtruder.gcode', """;Switch between the current extruder and the next extruder, when printing with multiple extruders.
325 G1 X{new_x} Y{new_y} Z{new_z} F{travel_speed}
328 """, str, 'alteration', 'alteration')
330 setting('startMode', 'Simple', ['Simple', 'Normal'], 'preference', 'hidden')
331 setting('lastFile', os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'resources', 'example', 'UltimakerRobot_support.stl')), str, 'preference', 'hidden')
332 setting('save_profile', 'False', bool, 'preference', 'hidden').setLabel(_("Save profile on slice"), _("When slicing save the profile as [stl_file]_profile.ini next to the model."))
333 setting('filament_cost_kg', '0', float, 'preference', 'hidden').setLabel(_("Cost (price/kg)"), _("Cost of your filament per kg, to estimate the cost of the final print."))
334 setting('filament_cost_meter', '0', float, 'preference', 'hidden').setLabel(_("Cost (price/m)"), _("Cost of your filament per meter, to estimate the cost of the final print."))
335 setting('auto_detect_sd', 'True', bool, 'preference', 'hidden').setLabel(_("Auto detect SD card drive"), _("Auto detect the SD card. You can disable this because on some systems external hard-drives or USB sticks are detected as SD card."))
336 setting('check_for_updates', 'True', bool, 'preference', 'hidden').setLabel(_("Check for updates"), _("Check for newer versions of Cura on startup"))
337 setting('submit_slice_information', 'False', bool, 'preference', 'hidden').setLabel(_("Send usage statistics"), _("Submit anonymous usage information to improve next versions of Cura"))
338 setting('youmagine_token', '', str, 'preference', 'hidden')
339 setting('filament_physical_density', '1240', float, 'preference', 'hidden').setRange(500.0, 3000.0).setLabel(_("Density (kg/m3)"), _("Weight of the filament per m3. Around 1240 for PLA. And around 1040 for ABS. This value is used to estimate the weight if the filament used for the print."))
340 setting('language', 'English', str, 'preference', 'hidden').setLabel(_('Language'), _('Change the language in which Cura runs. Switching language requires a restart of Cura'))
341 setting('active_machine', '0', int, 'preference', 'hidden')
343 setting('model_colour', '#FFC924', str, 'preference', 'hidden').setLabel(_('Model colour'))
344 setting('model_colour2', '#CB3030', str, 'preference', 'hidden').setLabel(_('Model colour (2)'))
345 setting('model_colour3', '#DDD93C', str, 'preference', 'hidden').setLabel(_('Model colour (3)'))
346 setting('model_colour4', '#4550D3', str, 'preference', 'hidden').setLabel(_('Model colour (4)'))
348 setting('window_maximized', 'True', bool, 'preference', 'hidden')
349 setting('window_pos_x', '-1', float, 'preference', 'hidden')
350 setting('window_pos_y', '-1', float, 'preference', 'hidden')
351 setting('window_width', '-1', float, 'preference', 'hidden')
352 setting('window_height', '-1', float, 'preference', 'hidden')
353 setting('window_normal_sash', '320', float, 'preference', 'hidden')
355 setting('machine_name', '', str, 'machine', 'hidden')
356 setting('machine_type', 'unknown', str, 'machine', 'hidden') #Ultimaker, Ultimaker2, RepRap
357 setting('machine_width', '205', float, 'machine', 'hidden').setLabel(_("Maximum width (mm)"), _("Size of the machine in mm"))
358 setting('machine_depth', '205', float, 'machine', 'hidden').setLabel(_("Maximum depth (mm)"), _("Size of the machine in mm"))
359 setting('machine_height', '200', float, 'machine', 'hidden').setLabel(_("Maximum height (mm)"), _("Size of the machine in mm"))
360 setting('machine_center_is_zero', 'False', bool, 'machine', 'hidden').setLabel(_("Machine center 0,0"), _("Machines firmware defines the center of the bed as 0,0 instead of the front left corner."))
361 setting('ultimaker_extruder_upgrade', 'False', bool, 'machine', 'hidden')
362 setting('has_heated_bed', 'False', bool, 'machine', 'hidden').setLabel(_("Heated bed"), _("If you have an heated bed, this enabled heated bed settings (requires restart)"))
363 setting('gcode_flavor', 'RepRap (Marlin/Sprinter)', ['RepRap (Marlin/Sprinter)', 'UltiGCode', 'MakerBot'], 'machine', 'hidden').setLabel(_("GCode Flavor"), _("Flavor of generated GCode.\nRepRap is normal 5D GCode which works on Marlin/Sprinter based firmwares.\nUltiGCode is a variation of the RepRap GCode which puts more settings in the machine instead of the slicer.\nMakerBot GCode has a few changes in the way GCode is generated, but still requires MakerWare to generate to X3G."))
364 setting('extruder_amount', '1', ['1','2','3','4'], 'machine', 'hidden').setLabel(_("Extruder count"), _("Amount of extruders in your machine."))
365 setting('extruder_offset_x1', '0.0', float, 'machine', 'hidden').setLabel(_("Offset X"), _("The offset of your secondary extruder compared to the primary."))
366 setting('extruder_offset_y1', '-21.6', float, 'machine', 'hidden').setLabel(_("Offset Y"), _("The offset of your secondary extruder compared to the primary."))
367 setting('extruder_offset_x2', '0.0', float, 'machine', 'hidden').setLabel(_("Offset X"), _("The offset of your tertiary extruder compared to the primary."))
368 setting('extruder_offset_y2', '0.0', float, 'machine', 'hidden').setLabel(_("Offset Y"), _("The offset of your tertiary extruder compared to the primary."))
369 setting('extruder_offset_x3', '0.0', float, 'machine', 'hidden').setLabel(_("Offset X"), _("The offset of your forth extruder compared to the primary."))
370 setting('extruder_offset_y3', '0.0', float, 'machine', 'hidden').setLabel(_("Offset Y"), _("The offset of your forth extruder compared to the primary."))
371 setting('steps_per_e', '0', float, 'machine', 'hidden').setLabel(_("E-Steps per 1mm filament"), _("Amount of steps per mm filament extrusion. If set to 0 then this value is ignored and the value in your firmware is used."))
372 setting('serial_port', 'AUTO', str, 'machine', 'hidden').setLabel(_("Serial port"), _("Serial port to use for communication with the printer"))
373 setting('serial_port_auto', '', str, 'machine', 'hidden')
374 setting('serial_baud', 'AUTO', str, 'machine', 'hidden').setLabel(_("Baudrate"), _("Speed of the serial port communication\nNeeds to match your firmware settings\nCommon values are 250000, 115200, 57600"))
375 setting('serial_baud_auto', '', int, 'machine', 'hidden')
377 setting('extruder_head_size_min_x', '0.0', float, 'machine', 'hidden').setLabel(_("Head size towards X min (mm)"), _("The head size when printing multiple objects, measured from the tip of the nozzle towards the outer part of the head. 75mm for an Ultimaker if the fan is on the left side."))
378 setting('extruder_head_size_min_y', '0.0', float, 'machine', 'hidden').setLabel(_("Head size towards Y min (mm)"), _("The head size when printing multiple objects, measured from the tip of the nozzle towards the outer part of the head. 18mm for an Ultimaker if the fan is on the left side."))
379 setting('extruder_head_size_max_x', '0.0', float, 'machine', 'hidden').setLabel(_("Head size towards X max (mm)"), _("The head size when printing multiple objects, measured from the tip of the nozzle towards the outer part of the head. 18mm for an Ultimaker if the fan is on the left side."))
380 setting('extruder_head_size_max_y', '0.0', float, 'machine', 'hidden').setLabel(_("Head size towards Y max (mm)"), _("The head size when printing multiple objects, measured from the tip of the nozzle towards the outer part of the head. 35mm for an Ultimaker if the fan is on the left side."))
381 setting('extruder_head_size_height', '0.0', float, 'machine', 'hidden').setLabel(_("Printer gantry height (mm)"), _("The height of the gantry holding up the printer head. If an object is higher then this then you cannot print multiple objects one for one. 60mm for an Ultimaker."))
383 validators.warningAbove(settingsDictionary['filament_flow'], 150, _("More flow than 150% is rare and usually not recommended."))
384 validators.warningBelow(settingsDictionary['filament_flow'], 50, _("Less flow than 50% is rare and usually not recommended."))
385 validators.warningAbove(settingsDictionary['layer_height'], lambda : (float(getProfileSetting('nozzle_size')) * 80.0 / 100.0), _("Thicker layers then %.2fmm (80%% nozzle size) usually give bad results and are not recommended."))
386 validators.wallThicknessValidator(settingsDictionary['wall_thickness'])
387 validators.warningAbove(settingsDictionary['print_speed'], 150.0, _("It is highly unlikely that your machine can achieve a printing speed above 150mm/s"))
388 validators.printSpeedValidator(settingsDictionary['print_speed'])
389 validators.warningAbove(settingsDictionary['print_temperature'], 260.0, _("Temperatures above 260C could damage your machine, be careful!"))
390 validators.warningAbove(settingsDictionary['print_temperature2'], 260.0, _("Temperatures above 260C could damage your machine, be careful!"))
391 validators.warningAbove(settingsDictionary['print_temperature3'], 260.0, _("Temperatures above 260C could damage your machine, be careful!"))
392 validators.warningAbove(settingsDictionary['print_temperature4'], 260.0, _("Temperatures above 260C could damage your machine, be careful!"))
393 validators.warningAbove(settingsDictionary['filament_diameter'], 3.5, _("Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm."))
394 validators.warningAbove(settingsDictionary['filament_diameter2'], 3.5, _("Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm."))
395 validators.warningAbove(settingsDictionary['filament_diameter3'], 3.5, _("Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm."))
396 validators.warningAbove(settingsDictionary['filament_diameter4'], 3.5, _("Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm."))
397 validators.warningAbove(settingsDictionary['travel_speed'], 300.0, _("It is highly unlikely that your machine can achieve a travel speed above 300mm/s"))
398 validators.warningAbove(settingsDictionary['bottom_thickness'], lambda : (float(getProfileSetting('nozzle_size')) * 3.0 / 4.0), _("A bottom layer of more then %.2fmm (3/4 nozzle size) usually give bad results and is not recommended."))
400 #Conditions for multiple extruders
401 settingsDictionary['print_temperature2'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 1)
402 settingsDictionary['print_temperature3'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 2)
403 settingsDictionary['print_temperature4'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 3)
404 settingsDictionary['filament_diameter2'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 1)
405 settingsDictionary['filament_diameter3'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 2)
406 settingsDictionary['filament_diameter4'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 3)
407 settingsDictionary['support_dual_extrusion'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 1)
408 settingsDictionary['retraction_dual_amount'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 1)
409 settingsDictionary['wipe_tower'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 1)
410 settingsDictionary['ooze_shield'].addCondition(lambda : int(getMachineSetting('extruder_amount')) > 1)
412 settingsDictionary['print_bed_temperature'].addCondition(lambda : getMachineSetting('has_heated_bed') == 'True')
414 #UltiGCode uses less settings, as these settings are located inside the machine instead of gcode.
415 settingsDictionary['print_temperature'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
416 settingsDictionary['print_temperature2'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
417 settingsDictionary['print_temperature3'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
418 settingsDictionary['print_temperature4'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
419 settingsDictionary['filament_diameter'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
420 settingsDictionary['filament_diameter2'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
421 settingsDictionary['filament_diameter3'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
422 settingsDictionary['filament_diameter4'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
423 settingsDictionary['filament_flow'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
424 settingsDictionary['print_bed_temperature'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
425 settingsDictionary['retraction_speed'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
426 settingsDictionary['retraction_amount'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
427 settingsDictionary['retraction_dual_amount'].addCondition(lambda : getMachineSetting('gcode_flavor') != 'UltiGCode')
429 #Remove fake defined _() because later the localization will define a global _()
432 #########################################################
433 ## Profile and preferences functions
434 #########################################################
436 def getSubCategoriesFor(category):
439 for s in settingsList:
440 if s.getCategory() == category and not s.getSubCategory() in done and s.checkConditions():
441 done[s.getSubCategory()] = True
442 ret.append(s.getSubCategory())
445 def getSettingsForCategory(category, subCategory = None):
447 for s in settingsList:
448 if s.getCategory() == category and (subCategory is None or s.getSubCategory() == subCategory) and s.checkConditions():
454 if platform.system() == "Windows":
455 basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
456 #If we have a frozen python install, we need to step out of the library.zip
457 if hasattr(sys, 'frozen'):
458 basePath = os.path.normpath(os.path.join(basePath, ".."))
460 basePath = os.path.expanduser('~/.cura/%s' % version.getVersion(False))
461 if not os.path.isdir(basePath):
462 os.makedirs(basePath)
465 def getAlternativeBasePaths():
467 basePath = os.path.normpath(os.path.join(getBasePath(), '..'))
468 for subPath in os.listdir(basePath):
469 path = os.path.join(basePath, subPath)
470 if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'preferences.ini')) and path != getBasePath():
472 path = os.path.join(basePath, subPath, 'Cura')
473 if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'preferences.ini')) and path != getBasePath():
477 def getDefaultProfilePath():
478 return os.path.join(getBasePath(), 'current_profile.ini')
480 def loadProfile(filename):
481 #Read a configuration file as global config
482 profileParser = ConfigParser.ConfigParser()
484 profileParser.read(filename)
485 except ConfigParser.ParsingError:
488 for set in settingsList:
489 if set.isPreference():
492 if set.isAlteration():
493 section = 'alterations'
494 if profileParser.has_option(section, set.getName()):
495 set.setValue(unicode(profileParser.get(section, set.getName()), 'utf-8', 'replace'))
497 def saveProfile(filename):
498 #Save the current profile to an ini file
499 profileParser = ConfigParser.ConfigParser()
500 profileParser.add_section('profile')
501 profileParser.add_section('alterations')
503 for set in settingsList:
504 if set.isPreference() or set.isMachineSetting():
506 if set.isAlteration():
507 profileParser.set('alterations', set.getName(), set.getValue().encode('utf-8'))
509 profileParser.set('profile', set.getName(), set.getValue().encode('utf-8'))
511 profileParser.write(open(filename, 'w'))
514 #Read a configuration file as global config
516 for set in settingsList:
517 if not set.isProfile():
519 set.setValue(set.getDefault())
521 if getMachineSetting('machine_type') == 'ultimaker':
522 putProfileSetting('nozzle_size', '0.4')
523 if getMachineSetting('ultimaker_extruder_upgrade') == 'True':
524 putProfileSetting('retraction_enable', 'True')
525 elif getMachineSetting('machine_type') == 'ultimaker2':
526 putProfileSetting('nozzle_size', '0.4')
527 putProfileSetting('retraction_enable', 'True')
529 putProfileSetting('nozzle_size', '0.5')
530 putProfileSetting('retraction_enable', 'True')
532 def setProfileFromString(options):
533 options = base64.b64decode(options)
534 options = zlib.decompress(options)
535 (profileOpts, alt) = options.split('\f', 1)
536 global settingsDictionary
537 for option in profileOpts.split('\b'):
539 (key, value) = option.split('=', 1)
540 if key in settingsDictionary:
541 if settingsDictionary[key].isProfile():
542 settingsDictionary[key].setValue(value)
543 for option in alt.split('\b'):
545 (key, value) = option.split('=', 1)
546 if key in settingsDictionary:
547 if settingsDictionary[key].isAlteration():
548 settingsDictionary[key].setValue(value)
550 def getProfileString():
554 for set in settingsList:
556 if set.getName() in tempOverride:
557 p.append(set.getName() + "=" + tempOverride[set.getName()])
559 p.append(set.getName() + "=" + set.getValue())
560 elif set.isAlteration():
561 if set.getName() in tempOverride:
562 alt.append(set.getName() + "=" + tempOverride[set.getName()])
564 alt.append(set.getName() + "=" + set.getValue())
565 ret = '\b'.join(p) + '\f' + '\b'.join(alt)
566 ret = base64.b64encode(zlib.compress(ret, 9))
569 def insertNewlines(string, every=64): #This should be moved to a better place then profile.
571 for i in xrange(0, len(string), every):
572 lines.append(string[i:i+every])
573 return '\n'.join(lines)
575 def getPreferencesString():
578 for set in settingsList:
579 if set.isPreference():
580 p.append(set.getName() + "=" + set.getValue())
582 ret = base64.b64encode(zlib.compress(ret, 9))
586 def getProfileSetting(name):
587 if name in tempOverride:
588 return tempOverride[name]
589 global settingsDictionary
590 if name in settingsDictionary and settingsDictionary[name].isProfile():
591 return settingsDictionary[name].getValue()
592 traceback.print_stack()
593 sys.stderr.write('Error: "%s" not found in profile settings\n' % (name))
596 def getProfileSettingFloat(name):
598 setting = getProfileSetting(name).replace(',', '.')
599 return float(eval(setting, {}, {}))
603 def putProfileSetting(name, value):
604 #Check if we have a configuration file loaded, else load the default.
605 global settingsDictionary
606 if name in settingsDictionary and settingsDictionary[name].isProfile():
607 settingsDictionary[name].setValue(value)
609 def isProfileSetting(name):
610 global settingsDictionary
611 if name in settingsDictionary and settingsDictionary[name].isProfile():
615 ## Preferences functions
616 def getPreferencePath():
617 return os.path.join(getBasePath(), 'preferences.ini')
619 def getPreferenceFloat(name):
621 setting = getPreference(name).replace(',', '.')
622 return float(eval(setting, {}, {}))
626 def getPreferenceColour(name):
627 colorString = getPreference(name)
628 return [float(int(colorString[1:3], 16)) / 255, float(int(colorString[3:5], 16)) / 255, float(int(colorString[5:7], 16)) / 255, 1.0]
630 def loadPreferences(filename):
632 #Read a configuration file as global config
633 profileParser = ConfigParser.ConfigParser()
635 profileParser.read(filename)
636 except ConfigParser.ParsingError:
639 for set in settingsList:
640 if set.isPreference():
641 if profileParser.has_option('preference', set.getName()):
642 set.setValue(unicode(profileParser.get('preference', set.getName()), 'utf-8', 'replace'))
645 while profileParser.has_section('machine_%d' % (n)):
646 for set in settingsList:
647 if set.isMachineSetting():
648 if profileParser.has_option('machine_%d' % (n), set.getName()):
649 set.setValue(unicode(profileParser.get('machine_%d' % (n), set.getName()), 'utf-8', 'replace'), n)
652 setActiveMachine(int(getPreferenceFloat('active_machine')))
654 def loadMachineSettings(filename):
656 #Read a configuration file as global config
657 profileParser = ConfigParser.ConfigParser()
659 profileParser.read(filename)
660 except ConfigParser.ParsingError:
663 for set in settingsList:
664 if set.isMachineSetting():
665 if profileParser.has_option('machine', set.getName()):
666 set.setValue(unicode(profileParser.get('machine', set.getName()), 'utf-8', 'replace'))
667 checkAndUpdateMachineName()
669 def savePreferences(filename):
671 #Save the current profile to an ini file
672 parser = ConfigParser.ConfigParser()
673 parser.add_section('preference')
675 for set in settingsList:
676 if set.isPreference():
677 parser.set('preference', set.getName(), set.getValue().encode('utf-8'))
680 while getMachineSetting('machine_name', n) != '':
681 parser.add_section('machine_%d' % (n))
682 for set in settingsList:
683 if set.isMachineSetting():
684 parser.set('machine_%d' % (n), set.getName(), set.getValue(n).encode('utf-8'))
686 parser.write(open(filename, 'w'))
688 def getPreference(name):
689 if name in tempOverride:
690 return tempOverride[name]
691 global settingsDictionary
692 if name in settingsDictionary and settingsDictionary[name].isPreference():
693 return settingsDictionary[name].getValue()
694 traceback.print_stack()
695 sys.stderr.write('Error: "%s" not found in preferences\n' % (name))
698 def putPreference(name, value):
699 #Check if we have a configuration file loaded, else load the default.
700 global settingsDictionary
701 if name in settingsDictionary and settingsDictionary[name].isPreference():
702 settingsDictionary[name].setValue(value)
703 savePreferences(getPreferencePath())
705 traceback.print_stack()
706 sys.stderr.write('Error: "%s" not found in preferences\n' % (name))
708 def isPreference(name):
709 global settingsDictionary
710 if name in settingsDictionary and settingsDictionary[name].isPreference():
714 def getMachineSettingFloat(name):
716 setting = getMachineSetting(name).replace(',', '.')
717 return float(eval(setting, {}, {}))
721 def getMachineSetting(name, index = None):
722 if name in tempOverride:
723 return tempOverride[name]
724 global settingsDictionary
725 if name in settingsDictionary and settingsDictionary[name].isMachineSetting():
726 return settingsDictionary[name].getValue(index)
727 traceback.print_stack()
728 sys.stderr.write('Error: "%s" not found in machine settings\n' % (name))
731 def putMachineSetting(name, value):
732 #Check if we have a configuration file loaded, else load the default.
733 global settingsDictionary
734 if name in settingsDictionary and settingsDictionary[name].isMachineSetting():
735 settingsDictionary[name].setValue(value)
736 savePreferences(getPreferencePath())
738 def isMachineSetting(name):
739 global settingsDictionary
740 if name in settingsDictionary and settingsDictionary[name].isMachineSetting():
744 def checkAndUpdateMachineName():
745 global _selectedMachineIndex
746 name = getMachineSetting('machine_name')
749 name = getMachineSetting('machine_type')
750 for n in xrange(0, getMachineCount()):
751 if n == _selectedMachineIndex:
754 if name == getMachineSetting('machine_name', n):
757 if '%s (%d)' % (name, index) == getMachineSetting('machine_name', n):
759 if index is not None:
760 name = '%s (%d)' % (name, index)
761 putMachineSetting('machine_name', name)
762 putPreference('active_machine', _selectedMachineIndex)
764 def getMachineCount():
766 while getMachineSetting('machine_name', n) != '':
772 def setActiveMachine(index):
773 global _selectedMachineIndex
774 _selectedMachineIndex = index
775 putPreference('active_machine', _selectedMachineIndex)
777 def removeMachine(index):
778 global _selectedMachineIndex
780 if getMachineCount() < 2:
782 for n in xrange(index, getMachineCount()):
783 for setting in settingsList:
784 if setting.isMachineSetting():
785 setting.setValue(setting.getValue(n+1), n)
787 if _selectedMachineIndex >= index:
788 setActiveMachine(getMachineCount() - 1)
790 ## Temp overrides for multi-extruder slicing and the project planner.
792 def setTempOverride(name, value):
793 tempOverride[name] = unicode(value).encode("utf-8")
794 def clearTempOverride(name):
795 del tempOverride[name]
796 def resetTempOverride():
799 #########################################################
800 ## Utility functions to calculate common profile values
801 #########################################################
802 def calculateEdgeWidth():
803 wallThickness = getProfileSettingFloat('wall_thickness')
804 nozzleSize = getProfileSettingFloat('nozzle_size')
806 if getProfileSetting('spiralize') == 'True':
809 if wallThickness < 0.01:
811 if wallThickness < nozzleSize:
814 lineCount = int(wallThickness / (nozzleSize - 0.0001))
817 lineWidth = wallThickness / lineCount
818 lineWidthAlt = wallThickness / (lineCount + 1)
819 if lineWidth > nozzleSize * 1.5:
823 def calculateLineCount():
824 wallThickness = getProfileSettingFloat('wall_thickness')
825 nozzleSize = getProfileSettingFloat('nozzle_size')
827 if wallThickness < 0.01:
829 if wallThickness < nozzleSize:
831 if getProfileSetting('spiralize') == 'True':
834 lineCount = int(wallThickness / (nozzleSize - 0.0001))
837 lineWidth = wallThickness / lineCount
838 lineWidthAlt = wallThickness / (lineCount + 1)
839 if lineWidth > nozzleSize * 1.5:
843 def calculateSolidLayerCount():
844 layerHeight = getProfileSettingFloat('layer_height')
845 solidThickness = getProfileSettingFloat('solid_layer_thickness')
846 if layerHeight == 0.0:
848 return int(math.ceil(solidThickness / (layerHeight - 0.0001)))
850 def calculateObjectSizeOffsets():
853 if getProfileSetting('platform_adhesion') == 'Brim':
854 size += getProfileSettingFloat('brim_line_count') * calculateEdgeWidth()
855 elif getProfileSetting('platform_adhesion') == 'Raft':
858 if getProfileSettingFloat('skirt_line_count') > 0:
859 size += getProfileSettingFloat('skirt_line_count') * calculateEdgeWidth() + getProfileSettingFloat('skirt_gap')
861 #if getProfileSetting('enable_raft') != 'False':
862 # size += profile.getProfileSettingFloat('raft_margin') * 2
863 #if getProfileSetting('support') != 'None':
864 # extraSizeMin = extraSizeMin + numpy.array([3.0, 0, 0])
865 # extraSizeMax = extraSizeMax + numpy.array([3.0, 0, 0])
868 def getMachineCenterCoords():
869 if getMachineSetting('machine_center_is_zero') == 'True':
871 return [getMachineSettingFloat('machine_width') / 2, getMachineSettingFloat('machine_depth') / 2]
873 #########################################################
874 ## Alteration file functions
875 #########################################################
876 def replaceTagMatch(m):
880 return pre + time.strftime('%H:%M:%S')
882 return pre + time.strftime('%d-%m-%Y')
884 return pre + ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][int(time.strftime('%w'))]
885 if tag == 'print_time':
886 return pre + '#P_TIME#'
887 if tag == 'filament_amount':
888 return pre + '#F_AMNT#'
889 if tag == 'filament_weight':
890 return pre + '#F_WGHT#'
891 if tag == 'filament_cost':
892 return pre + '#F_COST#'
893 if pre == 'F' and tag == 'max_z_speed':
894 f = getProfileSettingFloat('travel_speed') * 60
895 if pre == 'F' and tag in ['print_speed', 'retraction_speed', 'travel_speed', 'bottom_layer_speed', 'cool_min_feedrate']:
896 f = getProfileSettingFloat(tag) * 60
897 elif isProfileSetting(tag):
898 f = getProfileSettingFloat(tag)
899 elif isPreference(tag):
900 f = getProfileSettingFloat(tag)
902 return '%s?%s?' % (pre, tag)
904 return pre + str(int(f))
907 def replaceGCodeTags(filename, gcodeInt):
908 f = open(filename, 'r+')
910 data = data.replace('#P_TIME#', ('%5d:%02d' % (int(gcodeInt.totalMoveTimeMinute / 60), int(gcodeInt.totalMoveTimeMinute % 60)))[-8:])
911 data = data.replace('#F_AMNT#', ('%8.2f' % (gcodeInt.extrusionAmount / 1000))[-8:])
912 data = data.replace('#F_WGHT#', ('%8.2f' % (gcodeInt.calculateWeight() * 1000))[-8:])
913 cost = gcodeInt.calculateCost()
916 data = data.replace('#F_COST#', ('%8s' % (cost.split(' ')[0]))[-8:])
921 ### Get aleration raw contents. (Used internally in Cura)
922 def getAlterationFile(filename):
923 if filename in tempOverride:
924 return tempOverride[filename]
925 global settingsDictionary
926 if filename in settingsDictionary and settingsDictionary[filename].isAlteration():
927 return settingsDictionary[filename].getValue()
928 traceback.print_stack()
929 sys.stderr.write('Error: "%s" not found in alteration settings\n' % (filename))
932 def setAlterationFile(name, value):
933 #Check if we have a configuration file loaded, else load the default.
934 global settingsDictionary
935 if name in settingsDictionary and settingsDictionary[name].isAlteration():
936 settingsDictionary[name].setValue(value)
937 saveProfile(getDefaultProfilePath())
939 def isTagIn(tag, contents):
940 contents = re.sub(';[^\n]*\n', '', contents)
941 return tag in contents
943 ### Get the alteration file for output. (Used by Skeinforge)
944 def getAlterationFileContents(filename, extruderCount = 1):
947 alterationContents = getAlterationFile(filename)
948 if getMachineSetting('gcode_flavor') == 'UltiGCode':
949 if filename == 'end.gcode':
950 return 'M25 ;Stop reading from this point on.\n'
952 if filename == 'start.gcode':
953 if extruderCount > 1:
954 alterationContents = getAlterationFile("start%d.gcode" % (extruderCount))
955 #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.
956 #We also set our steps per E here, if configured.
957 eSteps = getMachineSettingFloat('steps_per_e')
959 prefix += 'M92 E%f\n' % (eSteps)
960 temp = getProfileSettingFloat('print_temperature')
962 if getMachineSetting('has_heated_bed') == 'True':
963 bedTemp = getProfileSettingFloat('print_bed_temperature')
965 if bedTemp > 0 and not isTagIn('{print_bed_temperature}', alterationContents):
966 prefix += 'M140 S%f\n' % (bedTemp)
967 if temp > 0 and not isTagIn('{print_temperature}', alterationContents):
968 if extruderCount > 0:
969 for n in xrange(1, extruderCount):
971 if n > 0 and getProfileSettingFloat('print_temperature%d' % (n+1)) > 0:
972 t = getProfileSettingFloat('print_temperature%d' % (n+1))
973 prefix += 'M104 T%d S%f\n' % (n, t)
974 for n in xrange(0, extruderCount):
976 if n > 0 and getProfileSettingFloat('print_temperature%d' % (n+1)) > 0:
977 t = getProfileSettingFloat('print_temperature%d' % (n+1))
978 prefix += 'M109 T%d S%f\n' % (n, t)
981 prefix += 'M109 S%f\n' % (temp)
982 if bedTemp > 0 and not isTagIn('{print_bed_temperature}', alterationContents):
983 prefix += 'M190 S%f\n' % (bedTemp)
984 elif filename == 'end.gcode':
985 if extruderCount > 1:
986 alterationContents = getAlterationFile("end%d.gcode" % (extruderCount))
987 #Append the profile string to the end of the GCode, so we can load it from the GCode file later.
988 postfix = ';CURA_PROFILE_STRING:%s\n' % (getProfileString())
989 return unicode(prefix + re.sub("(.)\{([^\}]*)\}", replaceTagMatch, alterationContents).rstrip() + '\n' + postfix).strip().encode('utf-8') + '\n'
993 def getPluginConfig():
995 return pickle.loads(str(getProfileSetting('plugin_config')))
999 def setPluginConfig(config):
1000 putProfileSetting('plugin_config', pickle.dumps(config))
1002 def getPluginBasePaths():
1004 if platform.system() != "Windows":
1005 ret.append(os.path.expanduser('~/.cura/plugins/'))
1006 if platform.system() == "Darwin" and hasattr(sys, 'frozen'):
1007 ret.append(os.path.normpath(os.path.join(resources.resourceBasePath, "Cura/plugins")))
1009 ret.append(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'plugins')))
1012 def getPluginList():
1014 for basePath in getPluginBasePaths():
1015 for filename in glob.glob(os.path.join(basePath, '*.py')):
1016 filename = os.path.basename(filename)
1017 if filename.startswith('_'):
1019 with open(os.path.join(basePath, filename), "r") as f:
1020 item = {'filename': filename, 'name': None, 'info': None, 'type': None, 'params': []}
1023 if not line.startswith('#'):
1025 line = line[1:].split(':', 1)
1028 if line[0].upper() == 'NAME':
1029 item['name'] = line[1].strip()
1030 elif line[0].upper() == 'INFO':
1031 item['info'] = line[1].strip()
1032 elif line[0].upper() == 'TYPE':
1033 item['type'] = line[1].strip()
1034 elif line[0].upper() == 'DEPEND':
1036 elif line[0].upper() == 'PARAM':
1037 m = re.match('([a-zA-Z][a-zA-Z0-9_]*)\(([a-zA-Z_]*)(?::([^\)]*))?\) +(.*)', line[1].strip())
1039 item['params'].append({'name': m.group(1), 'type': m.group(2), 'default': m.group(3), 'description': m.group(4)})
1041 print "Unknown item in effect meta data: %s %s" % (line[0], line[1])
1042 if item['name'] is not None and item['type'] == 'postprocess':
1046 def runPostProcessingPlugins(gcodefilename):
1047 pluginConfigList = getPluginConfig()
1048 pluginList = getPluginList()
1050 for pluginConfig in pluginConfigList:
1052 for pluginTest in pluginList:
1053 if pluginTest['filename'] == pluginConfig['filename']:
1059 for basePath in getPluginBasePaths():
1060 testFilename = os.path.join(basePath, pluginConfig['filename'])
1061 if os.path.isfile(testFilename):
1062 pythonFile = testFilename
1063 if pythonFile is None:
1066 locals = {'filename': gcodefilename}
1067 for param in plugin['params']:
1068 value = param['default']
1069 if param['name'] in pluginConfig['params']:
1070 value = pluginConfig['params'][param['name']]
1072 if param['type'] == 'float':
1074 value = float(value)
1076 value = float(param['default'])
1078 locals[param['name']] = value
1080 execfile(pythonFile, locals)
1082 locationInfo = traceback.extract_tb(sys.exc_info()[2])[-1]
1083 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])