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
6 import os, traceback, math, re, zlib, base64, time, sys, platform, glob, string
\r
7 import cPickle as pickle
\r
8 if sys.version_info[0] < 3:
\r
11 import configparser as ConfigParser
\r
13 from util import version
15 #########################################################
\r
16 ## Default settings when none are found.
\r
17 #########################################################
\r
19 #Single place to store the defaults, so we have a consistent set of default settings.
\r
20 profileDefaultSettings = {
\r
21 'nozzle_size': '0.4',
\r
22 'layer_height': '0.2',
\r
23 'wall_thickness': '0.8',
\r
24 'solid_layer_thickness': '0.6',
\r
25 'fill_density': '20',
\r
26 'skirt_line_count': '1',
\r
28 'print_speed': '50',
\r
29 'print_temperature': '220',
\r
30 'print_bed_temperature': '70',
\r
32 'filament_diameter': '2.89',
\r
33 'filament_density': '1.00',
\r
34 'machine_center_x': '100',
\r
35 'machine_center_y': '100',
\r
36 'retraction_min_travel': '5.0',
\r
37 'retraction_enable': 'False',
\r
38 'retraction_speed': '40.0',
\r
39 'retraction_amount': '4.5',
\r
40 'retraction_extra': '0.0',
\r
41 'retract_on_jumps_only': 'True',
\r
42 'travel_speed': '150',
\r
43 'max_z_speed': '3.0',
\r
44 'bottom_layer_speed': '20',
\r
45 'cool_min_layer_time': '10',
\r
46 'fan_enabled': 'True',
\r
49 'fan_speed_max': '100',
\r
50 'model_scale': '1.0',
\r
56 'model_rotate_base': '0',
\r
57 'model_multiply_x': '1',
\r
58 'model_multiply_y': '1',
\r
59 'extra_base_wall_thickness': '0.0',
\r
60 'sequence': 'Loops > Perimeter > Infill',
\r
61 'force_first_layer_sequence': 'True',
\r
62 'infill_type': 'Line',
\r
63 'solid_top': 'True',
\r
64 'fill_overlap': '15',
\r
65 'support_rate': '50',
\r
66 'support_distance': '0.5',
\r
67 'support_dual_extrusion': 'False',
\r
69 'enable_skin': 'False',
\r
70 'enable_raft': 'False',
\r
71 'cool_min_feedrate': '10',
\r
72 'bridge_speed': '100',
\r
74 'raft_base_material_amount': '100',
\r
75 'raft_interface_material_amount': '100',
\r
76 'bottom_thickness': '0.3',
\r
77 'plugin_config': '',
\r
79 'add_start_end_gcode': 'True',
\r
80 'gcode_extension': 'gcode',
\r
81 'alternative_center': '',
\r
85 alterationDefault = {
\r
86 #######################################################################################
\r
87 'start.gcode': """;Sliced {filename} at: {day} {date} {time}
\r
88 ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
\r
89 ;Print time: {print_time}
\r
90 ;Filament used: {filament_amount}m {filament_weight}g
\r
91 ;Filament cost: {filament_cost}
\r
93 G90 ;absolute positioning
\r
94 M107 ;start with the fan off
\r
96 G28 X0 Y0 ;move X/Y to min endstops
\r
97 G28 Z0 ;move Z to min endstops
\r
98 G92 X0 Y0 Z0 E0 ;reset software position to front/left/z=0.0
\r
100 G1 Z15.0 F{max_z_speed} ;move the platform down 15mm
\r
102 G92 E0 ;zero the extruded length
\r
103 G1 F200 E3 ;extrude 3mm of feed stock
\r
104 G92 E0 ;zero the extruded length again
\r
106 ;go to the middle of the platform (disabled, as there is no need to go to the center)
\r
107 ;G1 X{machine_center_x} Y{machine_center_y} F{travel_speed}
\r
110 #######################################################################################
\r
111 'end.gcode': """;End GCode
\r
112 M104 S0 ;extruder heater off
\r
113 M140 S0 ;heated bed heater off (if you have it)
\r
115 G91 ;relative positioning
\r
116 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
\r
117 G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
\r
118 G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way
\r
121 G90 ;absolute positioning
\r
123 #######################################################################################
\r
124 'support_start.gcode': '',
\r
125 'support_end.gcode': '',
\r
126 'cool_start.gcode': '',
\r
127 'cool_end.gcode': '',
\r
129 #######################################################################################
\r
130 '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
133 G91 ;relative positioning
\r
134 G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure
\r
135 G1 Z+0.5 E-5 F{travel_speed} ;move Z up a bit and retract filament even more
\r
136 G90 ;absolute positioning
\r
138 G1 Z{clear_z} F{max_z_speed}
\r
140 G1 X{machine_center_x} Y{machine_center_y} F{travel_speed}
\r
144 #######################################################################################
\r
145 'switchExtruder.gcode': """;Switch between the current extruder and the next extruder, when printing with multiple extruders.
\r
154 preferencesDefaultSettings = {
\r
155 'startMode': 'Simple',
\r
156 'lastFile': os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'example', 'UltimakerRobot_support.stl')),
\r
157 'machine_width': '205',
\r
158 'machine_depth': '205',
\r
159 'machine_height': '200',
\r
160 'machine_type': 'unknown',
\r
161 'has_heated_bed': 'False',
\r
162 'extruder_amount': '1',
\r
163 'extruder_offset_x1': '-22.0',
\r
164 'extruder_offset_y1': '0.0',
\r
165 'extruder_offset_x2': '0.0',
\r
166 'extruder_offset_y2': '0.0',
\r
167 'extruder_offset_x3': '0.0',
\r
168 'extruder_offset_y3': '0.0',
\r
169 'filament_density': '1300',
\r
170 'steps_per_e': '0',
\r
171 'serial_port': 'AUTO',
\r
172 'serial_port_auto': '',
\r
173 'serial_baud': 'AUTO',
\r
174 'serial_baud_auto': '',
\r
175 'slicer': 'Cura (Skeinforge based)',
\r
176 'save_profile': 'False',
\r
177 'filament_cost_kg': '0',
\r
178 'filament_cost_meter': '0',
\r
180 'sdshortnames': 'True',
\r
182 'extruder_head_size_min_x': '70.0',
\r
183 'extruder_head_size_min_y': '18.0',
\r
184 'extruder_head_size_max_x': '18.0',
\r
185 'extruder_head_size_max_y': '35.0',
\r
186 'extruder_head_size_height': '80.0',
\r
188 'model_colour': '#FFCC99',
\r
189 'model_colour2': '#33FF1A',
\r
190 'model_colour3': '#FF331A',
\r
191 'model_colour4': '#1A33FF',
\r
194 #########################################################
\r
195 ## Profile and preferences functions
\r
196 #########################################################
\r
198 ## Profile functions
\r
199 def getDefaultProfilePath():
\r
200 if platform.system() == "Windows":
201 basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
\r
202 #If we have a frozen python install, we need to step out of the library.zip
\r
203 if hasattr(sys, 'frozen'):
\r
204 basePath = os.path.normpath(os.path.join(basePath, ".."))
206 basePath = os.path.expanduser('~/.cura/%s' % version.getVersion(False))
207 if not os.path.isdir(basePath):
208 os.makedirs(basePath)
\r
209 return os.path.join(basePath, 'current_profile.ini')
\r
211 def loadGlobalProfile(filename):
\r
212 #Read a configuration file as global config
\r
213 global globalProfileParser
\r
214 globalProfileParser = ConfigParser.ConfigParser()
\r
215 globalProfileParser.read(filename)
\r
217 def resetGlobalProfile():
\r
218 #Read a configuration file as global config
\r
219 global globalProfileParser
\r
220 globalProfileParser = ConfigParser.ConfigParser()
\r
222 def saveGlobalProfile(filename):
\r
223 #Save the current profile to an ini file
\r
224 globalProfileParser.write(open(filename, 'w'))
\r
226 def loadGlobalProfileFromString(options):
\r
227 global globalProfileParser
\r
228 globalProfileParser = ConfigParser.ConfigParser()
\r
229 globalProfileParser.add_section('profile')
\r
230 globalProfileParser.add_section('alterations')
\r
231 options = base64.b64decode(options)
\r
232 options = zlib.decompress(options)
\r
233 (profileOpts, alt) = options.split('\f', 1)
\r
234 for option in profileOpts.split('\b'):
\r
235 if len(option) > 0:
\r
236 (key, value) = option.split('=', 1)
\r
237 globalProfileParser.set('profile', key, value)
\r
238 for option in alt.split('\b'):
\r
239 if len(option) > 0:
\r
240 (key, value) = option.split('=', 1)
\r
241 globalProfileParser.set('alterations', key, value)
\r
243 def getGlobalProfileString():
\r
244 global globalProfileParser
\r
245 if not globals().has_key('globalProfileParser'):
\r
246 loadGlobalProfile(getDefaultProfilePath())
\r
251 if globalProfileParser.has_section('profile'):
\r
252 for key in globalProfileParser.options('profile'):
\r
253 if key in tempOverride:
\r
254 p.append(key + "=" + tempOverride[key])
\r
255 tempDone.append(key)
\r
257 p.append(key + "=" + globalProfileParser.get('profile', key))
\r
258 if globalProfileParser.has_section('alterations'):
\r
259 for key in globalProfileParser.options('alterations'):
\r
260 if key in tempOverride:
\r
261 p.append(key + "=" + tempOverride[key])
\r
262 tempDone.append(key)
\r
264 alt.append(key + "=" + globalProfileParser.get('alterations', key))
\r
265 for key in tempOverride:
\r
266 if key not in tempDone:
\r
267 p.append(key + "=" + tempOverride[key])
\r
268 ret = '\b'.join(p) + '\f' + '\b'.join(alt)
\r
269 ret = base64.b64encode(zlib.compress(ret, 9))
\r
272 def getProfileSetting(name):
\r
273 if name in tempOverride:
\r
274 return unicode(tempOverride[name], "utf-8")
\r
275 #Check if we have a configuration file loaded, else load the default.
\r
276 if not globals().has_key('globalProfileParser'):
\r
277 loadGlobalProfile(getDefaultProfilePath())
\r
278 if not globalProfileParser.has_option('profile', name):
\r
279 if name in profileDefaultSettings:
\r
280 default = profileDefaultSettings[name]
\r
282 print("Missing default setting for: '" + name + "'")
\r
283 profileDefaultSettings[name] = ''
\r
285 if not globalProfileParser.has_section('profile'):
\r
286 globalProfileParser.add_section('profile')
\r
287 globalProfileParser.set('profile', name, str(default))
\r
288 #print(name + " not found in profile, so using default: " + str(default))
\r
290 return globalProfileParser.get('profile', name)
\r
292 def getProfileSettingFloat(name):
\r
294 setting = getProfileSetting(name).replace(',', '.')
\r
295 return float(eval(setting, {}, {}))
\r
296 except (ValueError, SyntaxError, TypeError):
\r
299 def putProfileSetting(name, value):
\r
300 #Check if we have a configuration file loaded, else load the default.
\r
301 if not globals().has_key('globalProfileParser'):
\r
302 loadGlobalProfile(getDefaultProfilePath())
\r
303 if not globalProfileParser.has_section('profile'):
\r
304 globalProfileParser.add_section('profile')
\r
305 globalProfileParser.set('profile', name, str(value))
\r
307 def isProfileSetting(name):
\r
308 if name in profileDefaultSettings:
\r
312 ## Preferences functions
\r
313 global globalPreferenceParser
\r
314 globalPreferenceParser = None
\r
316 def getPreferencePath():
\r
317 if platform.system() == "Windows":
318 basePath = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
\r
319 #If we have a frozen python install, we need to step out of the library.zip
\r
320 if hasattr(sys, 'frozen'):
\r
321 basePath = os.path.normpath(os.path.join(basePath, ".."))
323 basePath = os.path.expanduser('~/.cura/%s' % version.getVersion(False))
324 if not os.path.isdir(basePath):
325 os.makedirs(basePath)
\r
326 return os.path.join(basePath, 'preferences.ini')
\r
328 def getPreferenceFloat(name):
\r
330 setting = getPreference(name).replace(',', '.')
\r
331 return float(eval(setting, {}, {}))
\r
332 except (ValueError, SyntaxError, TypeError):
\r
335 def getPreferenceColour(name):
\r
336 colorString = getPreference(name)
\r
337 return [float(int(colorString[1:3], 16)) / 255, float(int(colorString[3:5], 16)) / 255, float(int(colorString[5:7], 16)) / 255, 1.0]
\r
339 def getPreference(name):
\r
340 if name in tempOverride:
\r
341 return unicode(tempOverride[name])
\r
342 global globalPreferenceParser
\r
343 if globalPreferenceParser == None:
\r
344 globalPreferenceParser = ConfigParser.ConfigParser()
\r
345 globalPreferenceParser.read(getPreferencePath())
\r
346 if not globalPreferenceParser.has_option('preference', name):
\r
347 if name in preferencesDefaultSettings:
\r
348 default = preferencesDefaultSettings[name]
\r
350 print("Missing default setting for: '" + name + "'")
\r
351 preferencesDefaultSettings[name] = ''
\r
353 if not globalPreferenceParser.has_section('preference'):
\r
354 globalPreferenceParser.add_section('preference')
\r
355 globalPreferenceParser.set('preference', name, str(default))
\r
356 #print(name + " not found in preferences, so using default: " + str(default))
\r
358 return unicode(globalPreferenceParser.get('preference', name), "utf-8")
\r
360 def putPreference(name, value):
\r
361 #Check if we have a configuration file loaded, else load the default.
\r
362 global globalPreferenceParser
\r
363 if globalPreferenceParser == None:
\r
364 globalPreferenceParser = ConfigParser.ConfigParser()
\r
365 globalPreferenceParser.read(getPreferencePath())
\r
366 if not globalPreferenceParser.has_section('preference'):
\r
367 globalPreferenceParser.add_section('preference')
\r
368 globalPreferenceParser.set('preference', name, unicode(value).encode("utf-8"))
\r
369 globalPreferenceParser.write(open(getPreferencePath(), 'w'))
\r
371 def isPreference(name):
\r
372 if name in preferencesDefaultSettings:
\r
376 ## Temp overrides for multi-extruder slicing and the project planner.
\r
378 def setTempOverride(name, value):
\r
379 tempOverride[name] = unicode(value).encode("utf-8")
\r
380 def clearTempOverride(name):
\r
381 del tempOverride[name]
\r
382 def resetTempOverride():
\r
383 tempOverride.clear()
\r
385 #########################################################
\r
386 ## Utility functions to calculate common profile values
\r
387 #########################################################
\r
388 def calculateEdgeWidth():
\r
389 wallThickness = getProfileSettingFloat('wall_thickness')
\r
390 nozzleSize = getProfileSettingFloat('nozzle_size')
\r
392 if wallThickness < nozzleSize:
\r
393 return wallThickness
\r
395 lineCount = int(wallThickness / nozzleSize)
\r
396 lineWidth = wallThickness / lineCount
\r
397 lineWidthAlt = wallThickness / (lineCount + 1)
\r
398 if lineWidth > nozzleSize * 1.5:
\r
399 return lineWidthAlt
\r
402 def calculateLineCount():
\r
403 wallThickness = getProfileSettingFloat('wall_thickness')
\r
404 nozzleSize = getProfileSettingFloat('nozzle_size')
\r
406 if wallThickness < nozzleSize:
\r
409 lineCount = int(wallThickness / nozzleSize + 0.0001)
\r
410 lineWidth = wallThickness / lineCount
\r
411 lineWidthAlt = wallThickness / (lineCount + 1)
\r
412 if lineWidth > nozzleSize * 1.5:
\r
413 return lineCount + 1
\r
416 def calculateSolidLayerCount():
\r
417 layerHeight = getProfileSettingFloat('layer_height')
\r
418 solidThickness = getProfileSettingFloat('solid_layer_thickness')
\r
419 return int(math.ceil(solidThickness / layerHeight - 0.0001))
\r
421 #########################################################
\r
422 ## Alteration file functions
\r
423 #########################################################
\r
424 def replaceTagMatch(m):
\r
428 return pre + time.strftime('%H:%M:%S')
\r
430 return pre + time.strftime('%d %b %Y')
\r
432 return pre + time.strftime('%a')
\r
433 if tag == 'print_time':
\r
434 return pre + '#P_TIME#'
\r
435 if tag == 'filament_amount':
\r
436 return pre + '#F_AMNT#'
\r
437 if tag == 'filament_weight':
\r
438 return pre + '#F_WGHT#'
\r
439 if tag == 'filament_cost':
\r
440 return pre + '#F_COST#'
\r
441 if pre == 'F' and tag in ['print_speed', 'retraction_speed', 'travel_speed', 'max_z_speed', 'bottom_layer_speed', 'cool_min_feedrate']:
\r
442 f = getProfileSettingFloat(tag) * 60
\r
443 elif isProfileSetting(tag):
\r
444 f = getProfileSettingFloat(tag)
\r
445 elif isPreference(tag):
\r
446 f = getProfileSettingFloat(tag)
\r
448 return '%s?%s?' % (pre, tag)
\r
450 return pre + str(int(f))
\r
451 return pre + str(f)
\r
453 def replaceGCodeTags(filename, gcodeInt):
\r
454 f = open(filename, 'r+')
\r
455 data = f.read(2048)
\r
456 data = data.replace('#P_TIME#', ('%5d:%02d' % (int(gcodeInt.totalMoveTimeMinute / 60), int(gcodeInt.totalMoveTimeMinute % 60)))[-8:])
\r
457 data = data.replace('#F_AMNT#', ('%8.2f' % (gcodeInt.extrusionAmount / 1000))[-8:])
\r
458 data = data.replace('#F_WGHT#', ('%8.2f' % (gcodeInt.calculateWeight() * 1000))[-8:])
\r
459 cost = gcodeInt.calculateCost()
\r
462 data = data.replace('#F_COST#', ('%8s' % (cost.split(' ')[0]))[-8:])
\r
467 ### Get aleration raw contents. (Used internally in Cura)
\r
468 def getAlterationFile(filename):
\r
469 #Check if we have a configuration file loaded, else load the default.
\r
470 if not globals().has_key('globalProfileParser'):
\r
471 loadGlobalProfile(getDefaultProfilePath())
\r
473 if not globalProfileParser.has_option('alterations', filename):
\r
474 if filename in alterationDefault:
\r
475 default = alterationDefault[filename]
\r
477 print("Missing default alteration for: '" + filename + "'")
\r
478 alterationDefault[filename] = ''
\r
480 if not globalProfileParser.has_section('alterations'):
\r
481 globalProfileParser.add_section('alterations')
\r
482 #print("Using default for: %s" % (filename))
\r
483 globalProfileParser.set('alterations', filename, default)
\r
484 return unicode(globalProfileParser.get('alterations', filename), "utf-8")
\r
486 def setAlterationFile(filename, value):
\r
487 #Check if we have a configuration file loaded, else load the default.
\r
488 if not globals().has_key('globalProfileParser'):
\r
489 loadGlobalProfile(getDefaultProfilePath())
\r
490 if not globalProfileParser.has_section('alterations'):
\r
491 globalProfileParser.add_section('alterations')
\r
492 globalProfileParser.set('alterations', filename, value.encode("utf-8"))
\r
493 saveGlobalProfile(getDefaultProfilePath())
\r
495 ### Get the alteration file for output. (Used by Skeinforge)
\r
496 def getAlterationFileContents(filename):
\r
499 alterationContents = getAlterationFile(filename)
\r
500 if filename == 'start.gcode':
\r
501 #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
502 #We also set our steps per E here, if configured.
\r
503 eSteps = getPreferenceFloat('steps_per_e')
\r
505 prefix += 'M92 E%f\n' % (eSteps)
\r
506 temp = getProfileSettingFloat('print_temperature')
\r
508 if getPreference('has_heated_bed') == 'True':
\r
509 bedTemp = getProfileSettingFloat('print_bed_temperature')
\r
511 if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
\r
512 prefix += 'M140 S%f\n' % (bedTemp)
\r
513 if temp > 0 and not '{print_temperature}' in alterationContents:
\r
514 prefix += 'M109 S%f\n' % (temp)
\r
515 if bedTemp > 0 and not '{print_bed_temperature}' in alterationContents:
\r
516 prefix += 'M190 S%f\n' % (bedTemp)
\r
517 elif filename == 'end.gcode':
\r
518 #Append the profile string to the end of the GCode, so we can load it from the GCode file later.
\r
519 postfix = ';CURA_PROFILE_STRING:%s\n' % (getGlobalProfileString())
\r
520 elif filename == 'replace.csv':
\r
521 #Always remove the extruder on/off M codes. These are no longer needed in 5D printing.
\r
522 prefix = 'M101\nM103\n'
\r
523 elif filename == 'support_start.gcode' or filename == 'support_end.gcode':
\r
524 #Add support start/end code
\r
525 if getProfileSetting('support_dual_extrusion') == 'True' and int(getPreference('extruder_amount')) > 1:
\r
526 if filename == 'support_start.gcode':
\r
527 setTempOverride('extruder', '1')
\r
529 setTempOverride('extruder', '0')
\r
530 alterationContents = getAlterationFileContents('switchExtruder.gcode')
\r
531 clearTempOverride('extruder')
\r
533 alterationContents = ''
\r
534 return unicode(prefix + re.sub("(.)\{([^\}]*)\}", replaceTagMatch, alterationContents).rstrip() + '\n' + postfix).strip().encode('utf-8')
\r
536 ###### PLUGIN #####
\r
538 def getPluginConfig():
\r
540 return pickle.loads(getProfileSetting('plugin_config'))
\r
544 def setPluginConfig(config):
\r
545 putProfileSetting('plugin_config', pickle.dumps(config))
\r
547 def getPluginBasePaths():
\r
548 ret = [os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'plugins'))]
\r
549 if platform.system() != "Windows":
\r
550 ret.append(os.path.expanduser('~/.cura/plugins/'))
\r
553 def getPluginList():
\r
555 for basePath in getPluginBasePaths():
\r
556 for filename in glob.glob(os.path.join(basePath, '*.py')):
\r
557 filename = os.path.basename(filename)
\r
558 if filename.startswith('_'):
\r
560 with open(os.path.join(basePath, filename), "r") as f:
\r
561 item = {'filename': filename, 'name': None, 'info': None, 'type': None, 'params': []}
\r
563 line = line.strip()
\r
564 if not line.startswith('#'):
\r
566 line = line[1:].split(':', 1)
\r
569 if line[0].upper() == 'NAME':
\r
570 item['name'] = line[1].strip()
\r
571 elif line[0].upper() == 'INFO':
\r
572 item['info'] = line[1].strip()
\r
573 elif line[0].upper() == 'TYPE':
\r
574 item['type'] = line[1].strip()
\r
575 elif line[0].upper() == 'DEPEND':
\r
577 elif line[0].upper() == 'PARAM':
\r
578 m = re.match('([a-zA-Z]*)\(([a-zA-Z_]*)(?:\:([^\)]*))?\) +(.*)', line[1].strip())
\r
580 item['params'].append({'name': m.group(1), 'type': m.group(2), 'default': m.group(3), 'description': m.group(4)})
\r
582 print "Unknown item in effect meta data: %s %s" % (line[0], line[1])
\r
583 if item['name'] != None and item['type'] == 'postprocess':
\r
587 def runPostProcessingPlugins(gcodefilename):
\r
588 pluginConfigList = getPluginConfig()
\r
589 pluginList = getPluginList()
\r
591 for pluginConfig in pluginConfigList:
\r
593 for pluginTest in pluginList:
\r
594 if pluginTest['filename'] == pluginConfig['filename']:
\r
595 plugin = pluginTest
\r
600 for basePath in getPluginBasePaths():
\r
601 testFilename = os.path.join(basePath, pluginConfig['filename'])
\r
602 if os.path.isfile(testFilename):
\r
603 pythonFile = testFilename
\r
604 if pythonFile == None:
\r
607 locals = {'filename': gcodefilename}
\r
608 for param in plugin['params']:
\r
609 value = param['default']
\r
610 if param['name'] in pluginConfig['params']:
\r
611 value = pluginConfig['params'][param['name']]
\r
613 if param['type'] == 'float':
\r
615 value = float(value)
\r
617 value = float(param['default'])
\r
619 locals[param['name']] = value
\r
621 execfile(pythonFile, locals)
\r
623 locationInfo = traceback.extract_tb(sys.exc_info()[2])[-1]
\r
624 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])
\r
627 def getSDcardDrives():
\r
629 if platform.system() == "Windows":
\r
630 from ctypes import windll
\r
631 bitmask = windll.kernel32.GetLogicalDrives()
\r
632 for letter in string.uppercase:
\r
634 drives.append(letter + ':/')
\r
636 if platform.system() == "Darwin":
\r
638 for volume in glob.glob('/Volumes/*'):
\r
639 if stat.S_ISLNK(os.lstat(volume).st_mode):
\r
641 drives.append(volume)
\r