chiark / gitweb /
Removed the bridge material amount setting, this did not only affect bridges, but...
[cura.git] / Cura / util / profile.py
1 from __future__ import absolute_import\r
2 from __future__ import division\r
3 #Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.\r
4 import __init__\r
5 \r
6 import ConfigParser, os, traceback, math, re, zlib, base64, time, sys\r
7 \r
8 #########################################################\r
9 ## Default settings when none are found.\r
10 #########################################################\r
11 \r
12 #Single place to store the defaults, so we have a consistent set of default settings.\r
13 profileDefaultSettings = {\r
14         'nozzle_size': '0.4',\r
15         'layer_height': '0.2',\r
16         'wall_thickness': '0.8',\r
17         'solid_layer_thickness': '0.6',\r
18         'fill_density': '20',\r
19         'skirt_line_count': '1',\r
20         'skirt_gap': '3.0',\r
21         'print_speed': '50',\r
22         'print_temperature': '0',\r
23         'support': 'None',\r
24         'filament_diameter': '2.89',\r
25         'filament_density': '1.00',\r
26         'machine_center_x': '100',\r
27         'machine_center_y': '100',\r
28         'retraction_min_travel': '5.0',\r
29         'retraction_speed': '40.0',\r
30         'retraction_amount': '0.0',\r
31         'retraction_extra': '0.0',\r
32         'travel_speed': '150',\r
33         'max_z_speed': '3.0',\r
34         'bottom_layer_speed': '20',\r
35         'cool_min_layer_time': '10',\r
36         'fan_enabled': 'True',\r
37         'fan_layer': '1',\r
38         'fan_speed': '100',\r
39         'model_scale': '1.0',\r
40         'flip_x': 'False',\r
41         'flip_y': 'False',\r
42         'flip_z': 'False',\r
43         'swap_xz': 'False',\r
44         'swap_yz': 'False',\r
45         'model_rotate_base': '0',\r
46         'model_multiply_x': '1',\r
47         'model_multiply_y': '1',\r
48         'extra_base_wall_thickness': '0.0',\r
49         'sequence': 'Loops > Perimeter > Infill',\r
50         'force_first_layer_sequence': 'True',\r
51         'infill_type': 'Line',\r
52         'solid_top': 'True',\r
53         'fill_overlap': '15',\r
54         'support_rate': '50',\r
55         'support_distance': '0.5',\r
56         'joris': 'False',\r
57         'enable_skin': 'False',\r
58         'enable_raft': 'False',\r
59         'cool_min_feedrate': '5',\r
60         'bridge_speed': '100',\r
61         'raft_margin': '5',\r
62         'raft_base_material_amount': '100',\r
63         'raft_interface_material_amount': '100',\r
64         'bottom_thickness': '0.3',\r
65         \r
66         'enable_dwindle': 'False',\r
67         'dwindle_pent_up_volume': '0.4',\r
68         'dwindle_slowdown_volume': '5.0',\r
69 \r
70         'add_start_end_gcode': 'True',\r
71         'gcode_extension': 'gcode',\r
72         'alternative_center': '',\r
73         'clear_z': '0.0',\r
74         'extruder': '0',\r
75 }\r
76 alterationDefault = {\r
77 #######################################################################################\r
78         'start.gcode': """;Sliced at: {day} {date} {time}\r
79 ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\r
80 G21        ;metric values\r
81 G90        ;absolute positioning\r
82 M107       ;start with the fan off\r
83 \r
84 G28 X0 Y0  ;move X/Y to min endstops\r
85 G28 Z0     ;move Z to min endstops\r
86 \r
87 ; if your prints start too high, try changing the Z0.0 below\r
88 ; to Z1.0 - the number after the Z is the actual, physical\r
89 ; height of the nozzle in mm. This can take some messing around\r
90 ; with to get just right...\r
91 G92 X0 Y0 Z0 E0         ;reset software position to front/left/z=0.0\r
92 G1 Z15.0 F{max_z_speed} ;move the platform down 15mm\r
93 G92 E0                  ;zero the extruded length\r
94 \r
95 G1 F200 E5              ;extrude 5mm of feed stock\r
96 G1 F200 E3.5            ;reverse feed stock by 1.5mm\r
97 G92 E0                  ;zero the extruded length again\r
98 \r
99 ;go to the middle of the platform, and move to Z=0 before starting the print.\r
100 G1 X{machine_center_x} Y{machine_center_y} F{travel_speed}\r
101 G1 Z0.0 F{max_z_speed}\r
102 """,\r
103 #######################################################################################\r
104         'end.gcode': """;End GCode\r
105 M104 S0                    ;extruder heat off\r
106 G91                        ;relative positioning\r
107 G1 Z+10 E-5 F{max_z_speed} ;move Z up a bit and retract filament by 5mm\r
108 G28 X0 Y0                  ;move X/Y to min endstops, so the head is out of the way\r
109 M84                        ;steppers off\r
110 G90                        ;absolute positioning\r
111 """,\r
112 #######################################################################################\r
113         'support_start.gcode': '',\r
114         'support_end.gcode': '',\r
115         'cool_start.gcode': '',\r
116         'cool_end.gcode': '',\r
117         'replace.csv': '',\r
118 #######################################################################################\r
119         'nextobject.gcode': """;Move to next object on the platform. clear_z is the minimal z height we need to make sure we do not hit any objects.\r
120 G92 E0\r
121 G1 Z{clear_z} E-5 F{max_z_speed}\r
122 G92 E0\r
123 G1 X{machine_center_x} Y{machine_center_y} F{travel_speed}\r
124 G1 F200 E5.5\r
125 G92 E0\r
126 G1 Z0 F{max_z_speed}\r
127 """,\r
128 #######################################################################################\r
129         'switchExtruder.gcode': """;Switch between the current extruder and the next extruder, when printing with multiple extruders.\r
130 G1 E-5 F5000\r
131 G92 E0\r
132 T{extruder}\r
133 G1 E5 F5000\r
134 G92 E0\r
135 """,\r
136 }\r
137 preferencesDefaultSettings = {\r
138         'wizardDone': 'False',\r
139         'startMode': 'Simple',\r
140         'lastFile': '',\r
141         'machine_width': '205',\r
142         'machine_depth': '205',\r
143         'machine_height': '200',\r
144         'extruder_amount': '1',\r
145         'extruder_offset_x1': '-22.0',\r
146         'extruder_offset_y1': '0.0',\r
147         'extruder_offset_x2': '0.0',\r
148         'extruder_offset_y2': '0.0',\r
149         'extruder_offset_x3': '0.0',\r
150         'extruder_offset_y3': '0.0',\r
151         'filament_density': '1300',\r
152         'steps_per_e': '0',\r
153         'serial_port': 'AUTO',\r
154         'serial_baud': '250000',\r
155         'slicer': 'Cura (Skeinforge based)',\r
156         'save_profile': 'False',\r
157         'filament_cost_kg': '0',\r
158         'filament_cost_meter': '0',\r
159         \r
160         'extruder_head_size_min_x': '70.0',\r
161         'extruder_head_size_min_y': '18.0',\r
162         'extruder_head_size_max_x': '18.0',\r
163         'extruder_head_size_max_y': '35.0',\r
164 }\r
165 \r
166 #########################################################\r
167 ## Profile and preferences functions\r
168 #########################################################\r
169 \r
170 ## Profile functions\r
171 def getDefaultProfilePath():\r
172         basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))\r
173         #If we have a frozen python install, we need to step out of the library.zip\r
174         if hasattr(sys, 'frozen'):\r
175                 basePath = os.path.normpath(os.path.join(basePath, ".."))\r
176         return os.path.normpath(os.path.join(basePath, "current_profile.ini"))\r
177 \r
178 def loadGlobalProfile(filename):\r
179         #Read a configuration file as global config\r
180         global globalProfileParser\r
181         globalProfileParser = ConfigParser.ConfigParser()\r
182         globalProfileParser.read(filename)\r
183 \r
184 def saveGlobalProfile(filename):\r
185         #Save the current profile to an ini file\r
186         globalProfileParser.write(open(filename, 'w'))\r
187 \r
188 def loadGlobalProfileFromString(options):\r
189         global globalProfileParser\r
190         globalProfileParser = ConfigParser.ConfigParser()\r
191         globalProfileParser.add_section('profile')\r
192         globalProfileParser.add_section('alterations')\r
193         options = base64.b64decode(options)\r
194         options = zlib.decompress(options)\r
195         (profileOpts, alt) = options.split('\f', 1)\r
196         for option in profileOpts.split('\b'):\r
197                 if len(option) > 0:\r
198                         (key, value) = option.split('=', 1)\r
199                         globalProfileParser.set('profile', key, value)\r
200         for option in alt.split('\b'):\r
201                 if len(option) > 0:\r
202                         (key, value) = option.split('=', 1)\r
203                         globalProfileParser.set('alterations', key, value)\r
204 \r
205 def getGlobalProfileString():\r
206         global globalProfileParser\r
207         if not globals().has_key('globalProfileParser'):\r
208                 loadGlobalProfile(getDefaultProfilePath())\r
209         \r
210         p = []\r
211         alt = []\r
212         tempDone = []\r
213         if globalProfileParser.has_section('profile'):\r
214                 for key in globalProfileParser.options('profile'):\r
215                         if key in tempOverride:\r
216                                 p.append(key + "=" + unicode(tempOverride[key]))\r
217                                 tempDone.append(key)\r
218                         else:\r
219                                 p.append(key + "=" + globalProfileParser.get('profile', key))\r
220         if globalProfileParser.has_section('alterations'):\r
221                 for key in globalProfileParser.options('alterations'):\r
222                         if key in tempOverride:\r
223                                 p.append(key + "=" + tempOverride[key])\r
224                                 tempDone.append(key)\r
225                         else:\r
226                                 alt.append(key + "=" + globalProfileParser.get('alterations', key))\r
227         for key in tempOverride:\r
228                 if key not in tempDone:\r
229                         p.append(key + "=" + unicode(tempOverride[key]))\r
230         ret = '\b'.join(p) + '\f' + '\b'.join(alt)\r
231         ret = base64.b64encode(zlib.compress(ret, 9))\r
232         return ret\r
233 \r
234 def getProfileSetting(name):\r
235         if name in tempOverride:\r
236                 return unicode(tempOverride[name])\r
237         #Check if we have a configuration file loaded, else load the default.\r
238         if not globals().has_key('globalProfileParser'):\r
239                 loadGlobalProfile(getDefaultProfilePath())\r
240         if not globalProfileParser.has_option('profile', name):\r
241                 if name in profileDefaultSettings:\r
242                         default = profileDefaultSettings[name]\r
243                 else:\r
244                         print "Missing default setting for: '" + name + "'"\r
245                         profileDefaultSettings[name] = ''\r
246                         default = ''\r
247                 if not globalProfileParser.has_section('profile'):\r
248                         globalProfileParser.add_section('profile')\r
249                 globalProfileParser.set('profile', name, str(default))\r
250                 #print name + " not found in profile, so using default: " + str(default)\r
251                 return default\r
252         return globalProfileParser.get('profile', name)\r
253 \r
254 def getProfileSettingFloat(name):\r
255         try:\r
256                 return float(eval(getProfileSetting(name), {}, {}))\r
257         except (ValueError, SyntaxError):\r
258                 return 0.0\r
259 \r
260 def putProfileSetting(name, value):\r
261         #Check if we have a configuration file loaded, else load the default.\r
262         if not globals().has_key('globalProfileParser'):\r
263                 loadGlobalProfile(getDefaultProfilePath())\r
264         if not globalProfileParser.has_section('profile'):\r
265                 globalProfileParser.add_section('profile')\r
266         globalProfileParser.set('profile', name, str(value))\r
267 \r
268 def isProfileSetting(name):\r
269         if name in profileDefaultSettings:\r
270                 return True\r
271         return False\r
272 \r
273 ## Preferences functions\r
274 global globalPreferenceParser\r
275 globalPreferenceParser = None\r
276 \r
277 def getPreferencePath():\r
278         basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))\r
279         #If we have a frozen python install, we need to step out of the library.zip\r
280         if hasattr(sys, 'frozen'):\r
281                 basePath = os.path.normpath(os.path.join(basePath, ".."))\r
282         return os.path.normpath(os.path.join(basePath, "preferences.ini"))\r
283 \r
284 def getPreferenceFloat(name):\r
285         try:\r
286                 return float(eval(getPreference(name), {}, {}))\r
287         except (ValueError, SyntaxError):\r
288                 return 0.0\r
289 \r
290 def getPreference(name):\r
291         if name in tempOverride:\r
292                 return unicode(tempOverride[name])\r
293         global globalPreferenceParser\r
294         if globalPreferenceParser == None:\r
295                 globalPreferenceParser = ConfigParser.ConfigParser()\r
296                 globalPreferenceParser.read(getPreferencePath())\r
297         if not globalPreferenceParser.has_option('preference', name):\r
298                 if name in preferencesDefaultSettings:\r
299                         default = preferencesDefaultSettings[name]\r
300                 else:\r
301                         print "Missing default setting for: '" + name + "'"\r
302                         preferencesDefaultSettings[name] = ''\r
303                         default = ''\r
304                 if not globalPreferenceParser.has_section('preference'):\r
305                         globalPreferenceParser.add_section('preference')\r
306                 globalPreferenceParser.set('preference', name, str(default))\r
307                 #print name + " not found in preferences, so using default: " + str(default)\r
308                 return default\r
309         return unicode(globalPreferenceParser.get('preference', name), "utf-8")\r
310 \r
311 def putPreference(name, value):\r
312         #Check if we have a configuration file loaded, else load the default.\r
313         global globalPreferenceParser\r
314         if globalPreferenceParser == None:\r
315                 globalPreferenceParser = ConfigParser.ConfigParser()\r
316                 globalPreferenceParser.read(getPreferencePath())\r
317         if not globalPreferenceParser.has_section('preference'):\r
318                 globalPreferenceParser.add_section('preference')\r
319         globalPreferenceParser.set('preference', name, unicode(value).encode("utf-8"))\r
320         globalPreferenceParser.write(open(getPreferencePath(), 'w'))\r
321 \r
322 def isPreference(name):\r
323         if name in preferencesDefaultSettings:\r
324                 return True\r
325         return False\r
326 \r
327 ## Temp overrides for multi-extruder slicing and the project planner.\r
328 tempOverride = {}\r
329 def setTempOverride(name, value):\r
330         tempOverride[name] = value\r
331 def resetTempOverride():\r
332         tempOverride.clear()\r
333 \r
334 #########################################################\r
335 ## Utility functions to calculate common profile values\r
336 #########################################################\r
337 def calculateEdgeWidth():\r
338         wallThickness = getProfileSettingFloat('wall_thickness')\r
339         nozzleSize = getProfileSettingFloat('nozzle_size')\r
340         \r
341         if wallThickness < nozzleSize:\r
342                 return wallThickness\r
343 \r
344         lineCount = int(wallThickness / nozzleSize)\r
345         lineWidth = wallThickness / lineCount\r
346         lineWidthAlt = wallThickness / (lineCount + 1)\r
347         if lineWidth > nozzleSize * 1.5:\r
348                 return lineWidthAlt\r
349         return lineWidth\r
350 \r
351 def calculateLineCount():\r
352         wallThickness = getProfileSettingFloat('wall_thickness')\r
353         nozzleSize = getProfileSettingFloat('nozzle_size')\r
354         \r
355         if wallThickness < nozzleSize:\r
356                 return 1\r
357 \r
358         lineCount = int(wallThickness / nozzleSize + 0.0001)\r
359         lineWidth = wallThickness / lineCount\r
360         lineWidthAlt = wallThickness / (lineCount + 1)\r
361         if lineWidth > nozzleSize * 1.5:\r
362                 return lineCount + 1\r
363         return lineCount\r
364 \r
365 def calculateSolidLayerCount():\r
366         layerHeight = getProfileSettingFloat('layer_height')\r
367         solidThickness = getProfileSettingFloat('solid_layer_thickness')\r
368         return int(math.ceil(solidThickness / layerHeight - 0.0001))\r
369 \r
370 #########################################################\r
371 ## Alteration file functions\r
372 #########################################################\r
373 def replaceTagMatch(m):\r
374         tag = m.group(0)[1:-1]\r
375         if tag == 'time':\r
376                 return time.strftime('%H:%M:%S')\r
377         if tag == 'date':\r
378                 return time.strftime('%d %b %Y')\r
379         if tag == 'day':\r
380                 return time.strftime('%a')\r
381         if tag in ['print_speed', 'retraction_speed', 'travel_speed', 'max_z_speed', 'bottom_layer_speed', 'cool_min_feedrate']:\r
382                 f = getProfileSettingFloat(tag) * 60\r
383         elif isProfileSetting(tag):\r
384                 f = getProfileSettingFloat(tag)\r
385         elif isPreference(tag):\r
386                 f = getProfileSettingFloat(tag)\r
387         else:\r
388                 return tag\r
389         if (f % 1) == 0:\r
390                 return str(int(f))\r
391         return str(f)\r
392 \r
393 ### Get aleration raw contents. (Used internally in Cura)\r
394 def getAlterationFile(filename):\r
395         #Check if we have a configuration file loaded, else load the default.\r
396         if not globals().has_key('globalProfileParser'):\r
397                 loadGlobalProfile(getDefaultProfilePath())\r
398         \r
399         if not globalProfileParser.has_option('alterations', filename):\r
400                 if filename in alterationDefault:\r
401                         default = alterationDefault[filename]\r
402                 else:\r
403                         print "Missing default alteration for: '" + filename + "'"\r
404                         alterationDefault[filename] = ''\r
405                         default = ''\r
406                 if not globalProfileParser.has_section('alterations'):\r
407                         globalProfileParser.add_section('alterations')\r
408                 #print "Using default for: %s" % (filename)\r
409                 globalProfileParser.set('alterations', filename, default)\r
410         return unicode(globalProfileParser.get('alterations', filename), "utf-8")\r
411 \r
412 def setAlterationFile(filename, value):\r
413         #Check if we have a configuration file loaded, else load the default.\r
414         if not globals().has_key('globalProfileParser'):\r
415                 loadGlobalProfile(getDefaultProfilePath())\r
416         if not globalProfileParser.has_section('alterations'):\r
417                 globalProfileParser.add_section('alterations')\r
418         globalProfileParser.set('alterations', filename, value.encode("utf-8"))\r
419         saveGlobalProfile(getDefaultProfilePath())\r
420 \r
421 ### Get the alteration file for output. (Used by Skeinforge)\r
422 def getAlterationFileContents(filename):\r
423         prefix = ''\r
424         alterationContents = getAlterationFile(filename)\r
425         if filename == 'start.gcode':\r
426                 #For the start code, hack the temperature and the steps per E value into it. So the temperature is reached before the start code extrusion.\r
427                 #We also set our steps per E here, if configured.\r
428                 eSteps = getPreferenceFloat('steps_per_e')\r
429                 if eSteps > 0:\r
430                         prefix += 'M92 E%f\n' % (eSteps)\r
431                 temp = getProfileSettingFloat('print_temperature')\r
432                 if temp > 0 and not '{print_temperature}' in alterationContents:\r
433                         prefix += 'M109 S%f\n' % (temp)\r
434         elif filename == 'replace.csv':\r
435                 #Always remove the extruder on/off M codes. These are no longer needed in 5D printing.\r
436                 prefix = 'M101\nM103\n'\r
437         \r
438         return unicode(prefix + re.sub("\{[^\}]*\}", replaceTagMatch, alterationContents).rstrip() + '\n').encode('utf-8')\r
439 \r