2 This page is in the table of contents.
3 Export is a craft tool to pick an export plugin, add information to the file name, and delete comments.
5 The export manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Export
9 The default 'Activate Export' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called.
12 ===Add Descriptive Extension===
15 When selected, key profile values will be added as an extension to the gcode file. For example:
16 test.04hx06w_03fill_2cx2r_33EL.gcode
21 * 04h = 'Layer Height (mm):' 0.4
23 * 06w = 0.6 width i.e. 0.4 times 'Edge Width over Height (ratio):' 1.5
25 * 03fill = 'Infill Solidity (ratio):' 0.3
26 * _ (Multiply section; if there is one column and one row then this section is not shown.)
27 * 2c = 'Number of Columns (integer):' 2
29 * 2r = 'Number of Rows (integer):' 2.
31 * 33EL = 'Feed Rate (mm/s):' 33.0 and 'Flow Rate Setting (float):' 33.0. If either value has a positive value after the decimal place then this is also shown, but if it is zero it is hidden. Also, if the values differ (which they shouldn't with 5D volumetrics) then each should be displayed separately. For example, 35.2E30L = 'Feed Rate (mm/s):' 35.2 and 'Flow Rate Setting (float):' 30.0.
33 ===Add Profile Extension===
36 When selected, the current profile will be added to the file extension. For example:
37 test.my_profile_name.gcode
39 ===Add Timestamp Extension===
42 When selected, the current date and time is added as an extension in format YYYYmmdd_HHMMSS (so it is sortable if one has many files). For example:
43 test.my_profile_name.20110613_220113.gcode
45 ===Also Send Output To===
48 Defines the output name for sending to a file or pipe. A common choice is stdout to print the output in the shell screen. Another common choice is stderr. With the empty default, nothing will be done. If the value is anything else, the output will be written to that file name.
53 When selected, the penultimate gcode will be sent to the analyze plugins to be analyzed and viewed.
56 Default is 'Delete All Comments'.
58 ====Do Not Delete Comments====
59 When selected, export will not delete comments. Crafting comments slow down the processing in many firmware types, which leads to pauses and therefore a lower quality print.
61 ====Delete Crafting Comments====
62 When selected, export will delete the time consuming crafting comments, but leave the initialization comments. Since the crafting comments are deleted, there are no pauses during extrusion. The remaining initialization comments provide some useful information for the analyze tools.
64 ====Delete All Comments====
65 When selected, export will delete all comments. The comments are not necessary to run a fabricator. Some printers do not support comments at all so the safest way is choose this option.
67 ===Export Operations===
68 Export presents the user with a choice of the export plugins in the export_plugins folder. The chosen plugin will then modify the gcode or translate it into another format. There is also the "Do Not Change Output" choice, which will not change the output. An export plugin is a script in the export_plugins folder which has the getOutput function, the globalIsReplaceable variable and if it's output is not replaceable, the writeOutput function.
73 Defines the file extension added to the name of the output file. The output file will be named as originalname_export.extension so if you are processing XYZ.stl the output will by default be XYZ_export.gcode
75 ===Name of Replace File===
76 Default is replace.csv.
78 When export is exporting the code, if there is a tab separated file with the name of the "Name of Replace File" setting, it will replace the string in the first column by its replacement in the second column. If there is nothing in the second column, the first column string will be deleted, if this leads to an empty line, the line will be deleted. If there are replacement columns after the second, they will be added as extra lines of text. There is an example file replace_example.csv to demonstrate the tab separated format, which can be edited in a text editor or a spreadsheet.
80 Export looks for the alteration file in the alterations folder in the .skeinforge folder in the home directory. Export does not care if the text file names are capitalized, but some file systems do not handle file name cases properly, so to be on the safe side you should give them lower case names. If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder.
82 ===Save Penultimate Gcode===
85 When selected, export will save the gcode file with the suffix '_penultimate.gcode' just before it is exported. This is useful because the code after it is exported could be in a form which the viewers can not display well.
88 The following examples export the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and export.py.
91 This brings up the export dialog.
93 > python export.py Screw Holder Bottom.stl
94 The export tool is parsing the file:
95 Screw Holder Bottom.stl
97 The export tool has created the file:
98 .. Screw Holder Bottom_export.gcode
102 from __future__ import absolute_import
104 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
105 from fabmetheus_utilities import archive
106 from fabmetheus_utilities import euclidean
107 from fabmetheus_utilities import gcodec
108 from fabmetheus_utilities import settings
109 from skeinforge_application.skeinforge_utilities import skeinforge_analyze
110 from skeinforge_application.skeinforge_utilities import skeinforge_craft
111 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
112 from skeinforge_application.skeinforge_utilities import skeinforge_profile
119 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
120 __credits__ = 'Gary Hodgson <http://garyhodgson.com/reprap/2011/06/hacking-skeinforge-export-module/>'
121 __date__ = '$Date: 2008/21/04 $'
122 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
125 def getCraftedTextFromText(gcodeText, repository=None):
126 'Export a gcode linear move text.'
127 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'export'):
129 if repository == None:
130 repository = settings.getReadRepository(ExportRepository())
131 if not repository.activateExport.value:
133 return ExportSkein().getCraftedGcode(repository, gcodeText)
135 def getDescriptionCarve(lines):
136 'Get the description for carve.'
137 descriptionCarve = ''
138 layerThicknessString = getSettingString(lines, 'carve', 'Layer Height')
139 if layerThicknessString != None:
140 descriptionCarve += layerThicknessString.replace('.', '') + 'h'
141 edgeWidthString = getSettingString(lines, 'carve', 'Edge Width over Height')
142 if edgeWidthString != None:
143 descriptionCarve += 'x%sw' % str(float(edgeWidthString) * float(layerThicknessString)).replace('.', '')
144 return descriptionCarve
146 def getDescriptionFill(lines):
147 'Get the description for fill.'
148 activateFillString = getSettingString(lines, 'fill', 'Activate Fill')
149 if activateFillString == None or activateFillString == 'False':
151 infillSolidityString = getSettingString(lines, 'fill', 'Infill Solidity')
152 return '_' + infillSolidityString.replace('.', '') + 'fill'
154 def getDescriptionMultiply(lines):
155 'Get the description for multiply.'
156 activateMultiplyString = getSettingString(lines, 'multiply', 'Activate Multiply')
157 if activateMultiplyString == None or activateMultiplyString == 'False':
159 columnsString = getSettingString(lines, 'multiply', 'Number of Columns')
160 rowsString = getSettingString(lines, 'multiply', 'Number of Rows')
161 if columnsString == '1' and rowsString == '1':
163 return '_%scx%sr' % (columnsString, rowsString)
165 def getDescriptionSpeed(lines):
166 'Get the description for speed.'
167 activateSpeedString = getSettingString(lines, 'speed', 'Activate Speed')
168 if activateSpeedString == None or activateSpeedString == 'False':
170 feedRateString = getSettingString(lines, 'speed', 'Feed Rate')
171 flowRateString = getSettingString(lines, 'speed', 'Flow Rate')
172 if feedRateString == flowRateString:
173 return '_%sEL' % feedRateString.replace('.0', '')
174 return '_%sE%sL' % (feedRateString.replace('.0', ''), flowRateString.replace('.0', ''))
176 def getDescriptiveExtension(gcodeText):
177 'Get the descriptive extension.'
178 lines = archive.getTextLines(gcodeText)
179 return '.' + getDescriptionCarve(lines) + getDescriptionFill(lines) + getDescriptionMultiply(lines) + getDescriptionSpeed(lines)
181 def getDistanceGcode(exportText):
182 'Get gcode lines with distance variable added, this is for if ever there is distance code.'
183 lines = archive.getTextLines(exportText)
186 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
188 if len(splitLine) > 0:
189 firstWord = splitLine[0]
190 if firstWord == 'G1':
191 location = gcodec.getLocationFromSplitLine(oldLocation, splitLine)
192 if oldLocation != None:
193 distance = location.distance(oldLocation)
194 oldLocation = location
197 def getFirstValue(gcodeText, word):
198 'Get the value from the first line which starts with the given word.'
199 for line in archive.getTextLines(gcodeText):
200 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
201 if gcodec.getFirstWord(splitLine) == word:
205 def getNewRepository():
206 'Get new repository.'
207 return ExportRepository()
209 def getReplaceableExportGcode(nameOfReplaceFile, replaceableExportGcode):
210 'Get text with strings replaced according to replace.csv file.'
211 replaceLines = settings.getAlterationLines(nameOfReplaceFile)
212 if len(replaceLines) < 1:
213 return replaceableExportGcode
214 for replaceLine in replaceLines:
215 splitLine = replaceLine.replace('\\n', '\t').split('\t')
216 if len(splitLine) > 0:
217 replaceableExportGcode = replaceableExportGcode.replace(splitLine[0], '\n'.join(splitLine[1 :]))
218 output = cStringIO.StringIO()
219 gcodec.addLinesToCString(output, archive.getTextLines(replaceableExportGcode))
220 return output.getvalue()
222 def getSelectedPluginModule( plugins ):
223 'Get the selected plugin module.'
224 for plugin in plugins:
226 return archive.getModuleWithDirectoryPath( plugin.directoryPath, plugin.name )
229 def getSettingString(lines, procedureName, settingNameStart):
230 'Get the setting value from the lines, return None if there is no setting starting with that name.'
231 settingNameStart = settingNameStart.replace(' ', '_')
233 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
235 if len(splitLine) > 0:
236 firstWord = splitLine[0]
237 if firstWord == '(<setting>':
238 if len(splitLine) > 4:
239 if splitLine[1] == procedureName and splitLine[2].startswith(settingNameStart):
241 elif firstWord == '(</settings>)':
245 def sendOutputTo(outputTo, text):
246 'Send output to a file or a standard output.'
247 if outputTo.endswith('stderr'):
248 sys.stderr.write(text)
249 sys.stderr.write('\n')
252 if outputTo.endswith('stdout'):
253 sys.stdout.write(text)
254 sys.stdout.write('\n')
257 archive.writeFileText(outputTo, text)
259 def getOutput(fileName):
260 'Export a gcode linear move file.'
263 repository = ExportRepository()
264 settings.getReadRepository(repository)
265 startTime = time.time()
266 print('File ' + archive.getSummarizedFileName(fileName.encode('ascii', 'replace')) + ' is being chain exported.')
267 gcodeText = gcodec.getGcodeFileText(fileName, '')
268 procedures = skeinforge_craft.getProcedures('export', gcodeText)
269 gcodeText = skeinforge_craft.getChainTextFromProcedures(fileName, procedures[: -1], gcodeText)
272 fileNamePenultimate = fileName[: fileName.rfind('.')] + '_penultimate.gcode'
273 if repository.savePenultimateGcode.value:
274 archive.writeFileText(fileNamePenultimate, gcodeText)
275 print('The penultimate file is saved as ' + archive.getSummarizedFileName(fileNamePenultimate))
276 exportGcode = getCraftedTextFromText(gcodeText, repository)
277 replaceableExportGcode = None
278 selectedPluginModule = getSelectedPluginModule(repository.exportPlugins)
279 if selectedPluginModule is None:
280 replaceableExportGcode = exportGcode
282 if selectedPluginModule.globalIsReplaceable:
283 replaceableExportGcode = selectedPluginModule.getOutput(exportGcode)
285 # selectedPluginModule.writeOutput(outputFilename, exportGcode)
286 if replaceableExportGcode is not None:
287 replaceableExportGcode = getReplaceableExportGcode(repository.nameOfReplaceFile.value, replaceableExportGcode)
288 if repository.alsoSendOutputTo.value != '':
289 if replaceableExportGcode == None:
290 replaceableExportGcode = selectedPluginModule.getOutput(exportGcode)
291 sendOutputTo(repository.alsoSendOutputTo.value, replaceableExportGcode)
292 print('It took %s to export the file.' % euclidean.getDurationString(time.time() - startTime))
293 return replaceableExportGcode
295 class ExportRepository(object):
296 'A class to handle the export settings.'
298 'Set the default settings, execute title & settings fileName.'
299 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.export.html', self)
300 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Export', self, '')
301 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Export')
302 self.activateExport = settings.BooleanSetting().getFromValue('Activate Export', self, True)
303 self.alsoSendOutputTo = settings.StringSetting().getFromValue('Also Send Output To:', self, '')
304 self.analyzeGcode = settings.BooleanSetting().getFromValue('Analyze Gcode', self, True)
305 self.commentChoice = settings.MenuButtonDisplay().getFromName('Comment Choice:', self)
306 self.doNotDeleteComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Do Not Delete Comments', self, True)
307 self.deleteCraftingComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Delete Crafting Comments', self, False)
308 self.deleteAllComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Delete All Comments', self, False)
309 exportPluginsFolderPath = archive.getAbsoluteFrozenFolderPath(archive.getCraftPluginsDirectoryPath('export.py'), 'export_plugins')
310 exportStaticDirectoryPath = os.path.join(exportPluginsFolderPath, 'static_plugins')
311 exportPluginFileNames = archive.getPluginFileNamesFromDirectoryPath(exportPluginsFolderPath)
312 exportStaticPluginFileNames = archive.getPluginFileNamesFromDirectoryPath(exportStaticDirectoryPath)
313 self.exportLabel = settings.LabelDisplay().getFromName('Export Operations: ', self)
314 self.exportPlugins = []
315 exportLatentStringVar = settings.LatentStringVar()
316 self.doNotChangeOutput = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, 'Do Not Change Output', self, False)
317 self.doNotChangeOutput.directoryPath = None
318 allExportPluginFileNames = exportPluginFileNames + exportStaticPluginFileNames
319 for exportPluginFileName in allExportPluginFileNames:
322 if exportPluginFileName == "gcode_small":
324 if exportPluginFileName in exportPluginFileNames:
325 path = os.path.join(exportPluginsFolderPath, exportPluginFileName)
326 exportPlugin = settings.RadioCapitalizedButton().getFromPath(exportLatentStringVar, exportPluginFileName, path, self, default)
327 exportPlugin.directoryPath = exportPluginsFolderPath
329 exportPlugin = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, exportPluginFileName, self, default)
330 exportPlugin.directoryPath = exportStaticDirectoryPath
331 self.exportPlugins.append(exportPlugin)
332 self.fileExtension = settings.StringSetting().getFromValue('File Extension:', self, 'gcode')
333 self.nameOfReplaceFile = settings.StringSetting().getFromValue('Name of Replace File:', self, 'replace.csv')
334 self.savePenultimateGcode = settings.BooleanSetting().getFromValue('Save Penultimate Gcode', self, False)
335 self.executeTitle = 'Export'
338 'Export button has been clicked.'
339 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
340 for fileName in fileNames:
341 writeOutput(fileName)
344 class ExportSkein(object):
345 'A class to export a skein of extrusions.'
347 self.crafting = False
348 self.decimalPlacesExported = 2
349 self.output = cStringIO.StringIO()
351 def addLine(self, line):
352 'Add a line of text and a newline to the output.'
354 self.output.write(line + '\n')
356 def getCraftedGcode( self, repository, gcodeText ):
357 'Parse gcode text and store the export gcode.'
358 self.repository = repository
359 lines = archive.getTextLines(gcodeText)
362 return self.output.getvalue()
364 def getLineWithTruncatedNumber(self, character, line, splitLine):
365 'Get a line with the number after the character truncated.'
366 numberString = gcodec.getStringFromCharacterSplitLine(character, splitLine)
367 if numberString == None:
369 roundedNumberString = euclidean.getRoundedToPlacesString(self.decimalPlacesExported, float(numberString))
370 return gcodec.getLineWithValueString(character, line, splitLine, roundedNumberString)
372 def parseLine(self, line):
373 'Parse a gcode line.'
374 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
375 if len(splitLine) < 1:
378 firstWord = splitLine[0]
379 if firstWord == '(</crafting>)':
380 self.crafting = False
381 elif firstWord == '(<decimalPlacesCarried>':
382 self.decimalPlacesExported = int(splitLine[1]) - 1
383 if self.repository.deleteAllComments.value or (self.repository.deleteCraftingComments.value and self.crafting):
384 if firstWord[0] == '(':
387 line = line.split(';')[0].split('(')[0].strip()
388 if firstWord == '(<crafting>)':
390 if firstWord == '(</extruderInitialization>)':
391 self.addLine(gcodec.getTagBracketedProcedure('export'))
392 if firstWord != 'G1' and firstWord != 'G2' and firstWord != 'G3' :
395 line = self.getLineWithTruncatedNumber('X', line, splitLine)
396 line = self.getLineWithTruncatedNumber('Y', line, splitLine)
397 line = self.getLineWithTruncatedNumber('Z', line, splitLine)
398 line = self.getLineWithTruncatedNumber('I', line, splitLine)
399 line = self.getLineWithTruncatedNumber('J', line, splitLine)
400 line = self.getLineWithTruncatedNumber('R', line, splitLine)
405 'Display the export dialog.'
406 if len(sys.argv) > 1:
407 writeOutput(' '.join(sys.argv[1 :]))
409 settings.startMainLoopFromConstructor(getNewRepository())
411 if __name__ == '__main__':