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
103 #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.
106 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
107 from fabmetheus_utilities import archive
108 from fabmetheus_utilities import euclidean
109 from fabmetheus_utilities import gcodec
110 from fabmetheus_utilities import intercircle
111 from fabmetheus_utilities import settings
112 from skeinforge_application.skeinforge_utilities import skeinforge_analyze
113 from skeinforge_application.skeinforge_utilities import skeinforge_craft
114 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
115 from skeinforge_application.skeinforge_utilities import skeinforge_profile
122 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
123 __credits__ = 'Gary Hodgson <http://garyhodgson.com/reprap/2011/06/hacking-skeinforge-export-module/>'
124 __date__ = '$Date: 2008/21/04 $'
125 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
128 def getCraftedTextFromText(gcodeText, repository=None):
129 'Export a gcode linear move text.'
130 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'export'):
132 if repository == None:
133 repository = settings.getReadRepository(ExportRepository())
134 if not repository.activateExport.value:
136 return ExportSkein().getCraftedGcode(repository, gcodeText)
138 def getDescriptionCarve(lines):
139 'Get the description for carve.'
140 descriptionCarve = ''
141 layerThicknessString = getSettingString(lines, 'carve', 'Layer Height')
142 if layerThicknessString != None:
143 descriptionCarve += layerThicknessString.replace('.', '') + 'h'
144 edgeWidthString = getSettingString(lines, 'carve', 'Edge Width over Height')
145 if edgeWidthString != None:
146 descriptionCarve += 'x%sw' % str(float(edgeWidthString) * float(layerThicknessString)).replace('.', '')
147 return descriptionCarve
149 def getDescriptionFill(lines):
150 'Get the description for fill.'
151 activateFillString = getSettingString(lines, 'fill', 'Activate Fill')
152 if activateFillString == None or activateFillString == 'False':
154 infillSolidityString = getSettingString(lines, 'fill', 'Infill Solidity')
155 return '_' + infillSolidityString.replace('.', '') + 'fill'
157 def getDescriptionMultiply(lines):
158 'Get the description for multiply.'
159 activateMultiplyString = getSettingString(lines, 'multiply', 'Activate Multiply')
160 if activateMultiplyString == None or activateMultiplyString == 'False':
162 columnsString = getSettingString(lines, 'multiply', 'Number of Columns')
163 rowsString = getSettingString(lines, 'multiply', 'Number of Rows')
164 if columnsString == '1' and rowsString == '1':
166 return '_%scx%sr' % (columnsString, rowsString)
168 def getDescriptionSpeed(lines):
169 'Get the description for speed.'
170 activateSpeedString = getSettingString(lines, 'speed', 'Activate Speed')
171 if activateSpeedString == None or activateSpeedString == 'False':
173 feedRateString = getSettingString(lines, 'speed', 'Feed Rate')
174 flowRateString = getSettingString(lines, 'speed', 'Flow Rate')
175 if feedRateString == flowRateString:
176 return '_%sEL' % feedRateString.replace('.0', '')
177 return '_%sE%sL' % (feedRateString.replace('.0', ''), flowRateString.replace('.0', ''))
179 def getDescriptiveExtension(gcodeText):
180 'Get the descriptive extension.'
181 lines = archive.getTextLines(gcodeText)
182 return '.' + getDescriptionCarve(lines) + getDescriptionFill(lines) + getDescriptionMultiply(lines) + getDescriptionSpeed(lines)
184 def getDistanceGcode(exportText):
185 'Get gcode lines with distance variable added, this is for if ever there is distance code.'
186 lines = archive.getTextLines(exportText)
189 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
191 if len(splitLine) > 0:
192 firstWord = splitLine[0]
193 if firstWord == 'G1':
194 location = gcodec.getLocationFromSplitLine(oldLocation, splitLine)
195 if oldLocation != None:
196 distance = location.distance(oldLocation)
197 oldLocation = location
200 def getFirstValue(gcodeText, word):
201 'Get the value from the first line which starts with the given word.'
202 for line in archive.getTextLines(gcodeText):
203 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
204 if gcodec.getFirstWord(splitLine) == word:
208 def getNewRepository():
209 'Get new repository.'
210 return ExportRepository()
212 def getReplaceableExportGcode(nameOfReplaceFile, replaceableExportGcode):
213 'Get text with strings replaced according to replace.csv file.'
214 replaceLines = settings.getAlterationLines(nameOfReplaceFile)
215 if len(replaceLines) < 1:
216 return replaceableExportGcode
217 for replaceLine in replaceLines:
218 splitLine = replaceLine.replace('\\n', '\t').split('\t')
219 if len(splitLine) > 0:
220 replaceableExportGcode = replaceableExportGcode.replace(splitLine[0], '\n'.join(splitLine[1 :]))
221 output = cStringIO.StringIO()
222 gcodec.addLinesToCString(output, archive.getTextLines(replaceableExportGcode))
223 return output.getvalue()
225 def getSelectedPluginModule( plugins ):
226 'Get the selected plugin module.'
227 for plugin in plugins:
229 return archive.getModuleWithDirectoryPath( plugin.directoryPath, plugin.name )
232 def getSettingString(lines, procedureName, settingNameStart):
233 'Get the setting value from the lines, return None if there is no setting starting with that name.'
234 settingNameStart = settingNameStart.replace(' ', '_')
236 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
238 if len(splitLine) > 0:
239 firstWord = splitLine[0]
240 if firstWord == '(<setting>':
241 if len(splitLine) > 4:
242 if splitLine[1] == procedureName and splitLine[2].startswith(settingNameStart):
244 elif firstWord == '(</settings>)':
248 def sendOutputTo(outputTo, text):
249 'Send output to a file or a standard output.'
250 if outputTo.endswith('stderr'):
251 sys.stderr.write(text)
252 sys.stderr.write('\n')
255 if outputTo.endswith('stdout'):
256 sys.stdout.write(text)
257 sys.stdout.write('\n')
260 archive.writeFileText(outputTo, text)
262 def writeOutput(fileName, shouldAnalyze=True):
263 'Export a gcode linear move file.'
266 repository = ExportRepository()
267 settings.getReadRepository(repository)
268 startTime = time.time()
269 print('File ' + archive.getSummarizedFileName(fileName) + ' is being chain exported.')
270 fileNameSuffix = fileName[: fileName.rfind('.')]
271 if repository.addExportSuffix.value:
272 fileNameSuffix += '_export'
273 gcodeText = gcodec.getGcodeFileText(fileName, '')
274 procedures = skeinforge_craft.getProcedures('export', gcodeText)
275 gcodeText = skeinforge_craft.getChainTextFromProcedures(fileName, procedures[: -1], gcodeText)
278 if repository.addProfileExtension.value:
279 fileNameSuffix += '.' + getFirstValue(gcodeText, '(<profileName>')
280 if repository.addDescriptiveExtension.value:
281 fileNameSuffix += getDescriptiveExtension(gcodeText)
282 if repository.addTimestampExtension.value:
283 fileNameSuffix += '.' + getFirstValue(gcodeText, '(<timeStampPreface>')
284 fileNameSuffix += '.' + repository.fileExtension.value
285 fileNamePenultimate = fileName[: fileName.rfind('.')] + '_penultimate.gcode'
286 filePenultimateWritten = False
287 if repository.savePenultimateGcode.value:
288 archive.writeFileText(fileNamePenultimate, gcodeText)
289 filePenultimateWritten = True
290 print('The penultimate file is saved as ' + archive.getSummarizedFileName(fileNamePenultimate))
291 exportGcode = getCraftedTextFromText(gcodeText, repository)
293 if shouldAnalyze and repository.analyzeGcode.value:
294 window = skeinforge_analyze.writeOutput(fileName, fileNamePenultimate, fileNameSuffix, filePenultimateWritten, gcodeText)
295 replaceableExportGcode = None
296 selectedPluginModule = getSelectedPluginModule(repository.exportPlugins)
297 if selectedPluginModule == None:
298 replaceableExportGcode = exportGcode
300 if selectedPluginModule.globalIsReplaceable:
301 replaceableExportGcode = selectedPluginModule.getOutput(exportGcode)
303 selectedPluginModule.writeOutput(fileNameSuffix, exportGcode)
304 if replaceableExportGcode != None:
305 replaceableExportGcode = getReplaceableExportGcode(repository.nameOfReplaceFile.value, replaceableExportGcode)
306 archive.writeFileText( fileNameSuffix, replaceableExportGcode )
307 print('The exported file is saved as ' + archive.getSummarizedFileName(fileNameSuffix))
308 if repository.alsoSendOutputTo.value != '':
309 if replaceableExportGcode == None:
310 replaceableExportGcode = selectedPluginModule.getOutput(exportGcode)
311 sendOutputTo(repository.alsoSendOutputTo.value, replaceableExportGcode)
312 print('It took %s to export the file.' % euclidean.getDurationString(time.time() - startTime))
316 class ExportRepository:
317 'A class to handle the export settings.'
319 'Set the default settings, execute title & settings fileName.'
320 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.export.html', self)
321 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Export', self, '')
322 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Export')
323 self.activateExport = settings.BooleanSetting().getFromValue('Activate Export', self, True)
324 self.addDescriptiveExtension = settings.BooleanSetting().getFromValue('Add Descriptive Extension', self, False)
325 self.addExportSuffix = settings.BooleanSetting().getFromValue('Add Export Suffix', self, True)
326 self.addProfileExtension = settings.BooleanSetting().getFromValue('Add Profile Extension', self, False)
327 self.addTimestampExtension = settings.BooleanSetting().getFromValue('Add Timestamp Extension', self, False)
328 self.alsoSendOutputTo = settings.StringSetting().getFromValue('Also Send Output To:', self, '')
329 self.analyzeGcode = settings.BooleanSetting().getFromValue('Analyze Gcode', self, True)
330 self.commentChoice = settings.MenuButtonDisplay().getFromName('Comment Choice:', self)
331 self.doNotDeleteComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Do Not Delete Comments', self, True)
332 self.deleteCraftingComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Delete Crafting Comments', self, False)
333 self.deleteAllComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Delete All Comments', self, False)
334 exportPluginsFolderPath = archive.getAbsoluteFrozenFolderPath(archive.getCraftPluginsDirectoryPath('export.py'), 'export_plugins')
335 exportStaticDirectoryPath = os.path.join(exportPluginsFolderPath, 'static_plugins')
336 exportPluginFileNames = archive.getPluginFileNamesFromDirectoryPath(exportPluginsFolderPath)
337 exportStaticPluginFileNames = archive.getPluginFileNamesFromDirectoryPath(exportStaticDirectoryPath)
338 self.exportLabel = settings.LabelDisplay().getFromName('Export Operations: ', self)
339 self.exportPlugins = []
340 exportLatentStringVar = settings.LatentStringVar()
341 self.doNotChangeOutput = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, 'Do Not Change Output', self, False)
342 self.doNotChangeOutput.directoryPath = None
343 allExportPluginFileNames = exportPluginFileNames + exportStaticPluginFileNames
344 for exportPluginFileName in allExportPluginFileNames:
347 if exportPluginFileName == "gcode_small":
349 if exportPluginFileName in exportPluginFileNames:
350 path = os.path.join(exportPluginsFolderPath, exportPluginFileName)
351 exportPlugin = settings.RadioCapitalizedButton().getFromPath(exportLatentStringVar, exportPluginFileName, path, self, default)
352 exportPlugin.directoryPath = exportPluginsFolderPath
354 exportPlugin = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, exportPluginFileName, self, default)
355 exportPlugin.directoryPath = exportStaticDirectoryPath
356 self.exportPlugins.append(exportPlugin)
357 self.fileExtension = settings.StringSetting().getFromValue('File Extension:', self, 'gcode')
358 self.nameOfReplaceFile = settings.StringSetting().getFromValue('Name of Replace File:', self, 'replace.csv')
359 self.savePenultimateGcode = settings.BooleanSetting().getFromValue('Save Penultimate Gcode', self, False)
360 self.executeTitle = 'Export'
363 'Export button has been clicked.'
364 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
365 for fileName in fileNames:
366 writeOutput(fileName)
370 'A class to export a skein of extrusions.'
372 self.crafting = False
373 self.decimalPlacesExported = 2
374 self.output = cStringIO.StringIO()
376 def addLine(self, line):
377 'Add a line of text and a newline to the output.'
379 self.output.write(line + '\n')
381 def getCraftedGcode( self, repository, gcodeText ):
382 'Parse gcode text and store the export gcode.'
383 self.repository = repository
384 lines = archive.getTextLines(gcodeText)
387 return self.output.getvalue()
389 def getLineWithTruncatedNumber(self, character, line, splitLine):
390 'Get a line with the number after the character truncated.'
391 numberString = gcodec.getStringFromCharacterSplitLine(character, splitLine)
392 if numberString == None:
394 roundedNumberString = euclidean.getRoundedToPlacesString(self.decimalPlacesExported, float(numberString))
395 return gcodec.getLineWithValueString(character, line, splitLine, roundedNumberString)
397 def parseLine(self, line):
398 'Parse a gcode line.'
399 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
400 if len(splitLine) < 1:
403 firstWord = splitLine[0]
404 if firstWord == '(</crafting>)':
405 self.crafting = False
406 elif firstWord == '(<decimalPlacesCarried>':
407 self.decimalPlacesExported = int(splitLine[1]) - 1
408 if self.repository.deleteAllComments.value or (self.repository.deleteCraftingComments.value and self.crafting):
409 if firstWord[0] == '(':
412 line = line.split(';')[0].split('(')[0].strip()
413 if firstWord == '(<crafting>)':
415 if firstWord == '(</extruderInitialization>)':
416 self.addLine(gcodec.getTagBracketedProcedure('export'))
417 if firstWord != 'G1' and firstWord != 'G2' and firstWord != 'G3' :
420 line = self.getLineWithTruncatedNumber('X', line, splitLine)
421 line = self.getLineWithTruncatedNumber('Y', line, splitLine)
422 line = self.getLineWithTruncatedNumber('Z', line, splitLine)
423 line = self.getLineWithTruncatedNumber('I', line, splitLine)
424 line = self.getLineWithTruncatedNumber('J', line, splitLine)
425 line = self.getLineWithTruncatedNumber('R', line, splitLine)
430 'Display the export dialog.'
431 if len(sys.argv) > 1:
432 writeOutput(' '.join(sys.argv[1 :]))
434 settings.startMainLoopFromConstructor(getNewRepository())
436 if __name__ == '__main__':