chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / cura_sf / skeinforge_application / skeinforge_plugins / craft_plugins / alteration.py
1 #! /usr/bin/env python
2 """
3 This page is in the table of contents.
4 The alteration plugin adds the start and end files to the gcode.
5
6 This plugin also removes the alteration prefix tokens from the alteration lines.  Alteration lines have a prefix token so they can go through the craft plugins without being modified.  However, the tokens are not recognized by the firmware so they have to be removed before export. The alteration token is:
7 (<alterationDeleteThisPrefix/>)
8
9 The alteration manual page is at:
10 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Alteration
11
12 ==Operation==
13 The default 'Activate Alteration' checkbox is on.  When it is on, the functions described below will work, when it is off, nothing will be done.
14
15 ==Settings==
16 Alteration looks for alteration files in the alterations folder in the .skeinforge folder in the home directory.  Alteration 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.
17
18 ===Name of End File===
19 Default is 'end.gcode'.
20
21 If there is a file with the name of the "Name of End File" setting, it will be added to the very end of the gcode.
22
23 ===Name of Start File===
24 Default is 'start.gcode'.
25
26 If there is a file with the name of the "Name of Start File" setting, it will be added to the very beginning of the gcode.
27
28 ===Remove Redundant Mcode===
29 Default: True
30
31 If 'Remove Redundant Mcode' is selected then M104 and M108 lines which are followed by a different value before there is a movement will be removed.  For example, if there is something like:
32 M113 S1.0
33 M104 S60.0
34 (<layer> 0.72 )
35 M104 S200.0
36 (<skirt>)
37
38 with Remove Redundant Mcode selected, that snippet would become:
39 M113 S1.0
40 M104 S200.0
41 (<layer> 0.72 )
42 (<skirt>)
43
44 This is a relatively safe procedure, the only reason it is optional is because someone might make an alteration file which, for some unknown reason, requires the redundant mcode.
45
46 ===Replace Variable with Setting===
47 Default: True
48
49 If 'Replace Variable with Setting' is selected and there is an alteration line with a setting token, the token will be replaced by the value.
50
51 For example, if there is an alteration line like:
52
53 M140 S<setting.chamber.BedTemperature>
54
55 the token would be replaced with the value and assuming the bed chamber was 60.0, the output would be:
56
57 M140 S60.0
58
59 ==Examples==
60 The following examples add the alteration information to the file Screw Holder Bottom.stl.  The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and alteration.py.
61
62 > python alteration.py
63 This brings up the alteration dialog.
64
65 > python alteration.py Screw Holder Bottom.stl
66 The alteration tool is parsing the file:
67 Screw Holder Bottom.stl
68 ..
69 The alteration tool has created the file:
70 .. Screw Holder Bottom_alteration.gcode
71
72 """
73
74 from __future__ import absolute_import
75 #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.
76 import __init__
77
78 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
79 from fabmetheus_utilities import archive
80 from fabmetheus_utilities import gcodec
81 from fabmetheus_utilities import settings
82 from skeinforge_application.skeinforge_utilities import skeinforge_craft
83 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
84 from skeinforge_application.skeinforge_utilities import skeinforge_profile
85 import cStringIO
86 import sys
87
88
89 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
90 __date__ = '$Date: 2008/02/05 $'
91 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
92
93
94 def getCraftedText(fileName, text='', repository=None):
95         'Alteration a gcode linear move text.'
96         return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository)
97
98 def getCraftedTextFromText(gcodeText, repository=None):
99         'Alteration a gcode linear move text.'
100         if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'alteration'):
101                 return gcodeText
102         if repository == None:
103                 repository = settings.getReadRepository(AlterationRepository())
104         if not repository.activateAlteration.value:
105                 return gcodeText
106         return AlterationSkein().getCraftedGcode(gcodeText, repository)
107
108 def getGcodeTextWithoutRedundantMcode(gcodeText):
109         'Get gcode text without redundant M104 and M108.'
110         lines = archive.getTextLines(gcodeText)
111         lines = getLinesWithoutRedundancy('M104', lines)
112         lines = getLinesWithoutRedundancy('M108', lines)
113         output = cStringIO.StringIO()
114         gcodec.addLinesToCString(output, lines)
115         return output.getvalue()
116
117 def getLinesWithoutRedundancy(duplicateWord, lines):
118         'Get gcode lines without redundant first words.'
119         oldDuplicationIndex = None
120         for lineIndex, line in enumerate(lines):
121                 firstWord = gcodec.getFirstWordFromLine(line)
122                 if firstWord == duplicateWord:
123                         if oldDuplicationIndex == None:
124                                 oldDuplicationIndex = lineIndex
125                         else:
126                                 lines[oldDuplicationIndex] = line
127                                 lines[lineIndex] = ''
128                 elif firstWord.startswith('G') or firstWord == 'M101' or firstWord == 'M103':
129                         oldDuplicationIndex = None
130         return lines
131
132 def getNewRepository():
133         'Get new repository.'
134         return AlterationRepository()
135
136 def writeOutput(fileName, shouldAnalyze=True):
137         'Alteration a gcode linear move file.  Chain alteration the gcode if the alteration procedure has not been done.'
138         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'alteration', shouldAnalyze)
139
140
141 class AlterationRepository:
142         "A class to handle the alteration settings."
143         def __init__(self):
144                 "Set the default settings, execute title & settings fileName."
145                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.alteration.html', self )
146                 self.baseNameSynonym = 'bookend.csv'
147                 self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Alteration', self, '')
148                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Alteration')
149                 self.activateAlteration = settings.BooleanSetting().getFromValue('Activate Alteration', self, True)
150                 self.nameOfEndFile = settings.StringSetting().getFromValue('Name of End File:', self, 'end.gcode')
151                 self.nameOfStartFile = settings.StringSetting().getFromValue('Name of Start File:', self, 'start.gcode')
152                 self.removeRedundantMcode = settings.BooleanSetting().getFromValue('Remove Redundant Mcode', self, True)
153                 self.replaceVariableWithSetting = settings.BooleanSetting().getFromValue('Replace Variable with Setting', self, True)
154                 self.executeTitle = 'Alteration'
155
156         def execute(self):
157                 'Alteration button has been clicked.'
158                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
159                 for fileName in fileNames:
160                         writeOutput(fileName)
161
162
163 class AlterationSkein:
164         "A class to alteration a skein of extrusions."
165         def __init__(self):
166                 'Initialize.'
167                 self.distanceFeedRate = gcodec.DistanceFeedRate()
168                 self.lineIndex = 0
169                 self.settingDictionary = None
170
171         def addFromUpperLowerFile(self, fileName):
172                 "Add lines of text from the fileName or the lowercase fileName, if there is no file by the original fileName in the directory."
173                 alterationFileLines = settings.getAlterationFileLines(fileName)
174                 self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(alterationFileLines)
175
176         def getCraftedGcode(self, gcodeText, repository):
177                 "Parse gcode text and store the bevel gcode."
178                 self.lines = archive.getTextLines(gcodeText)
179                 if repository.replaceVariableWithSetting.value:
180                         self.setSettingDictionary()
181                 self.addFromUpperLowerFile(repository.nameOfStartFile.value) # Add a start file if it exists.
182                 self.parseInitialization()
183                 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
184                         line = self.lines[self.lineIndex]
185                         self.distanceFeedRate.addLine(line)
186                 self.addFromUpperLowerFile(repository.nameOfEndFile.value) # Add an end file if it exists.
187                 gcodeText = self.getReplacedAlterationText()
188                 if repository.removeRedundantMcode.value:
189                         gcodeText = getGcodeTextWithoutRedundantMcode(gcodeText)
190                 return gcodeText
191
192         def getReplacedAlterationLine(self, alterationFileLine, searchIndex=0):
193                 'Get the alteration file line with variables replaced with the settings.'
194                 settingIndex = alterationFileLine.find('setting.', searchIndex)
195                 beginIndex = settingIndex - 1
196                 if beginIndex < 0:
197                         return alterationFileLine
198                 endBracketIndex = alterationFileLine.find('>', settingIndex)
199                 if alterationFileLine[beginIndex] != '<' or endBracketIndex == -1:
200                         return alterationFileLine
201                 endIndex = endBracketIndex + 1
202                 innerToken = alterationFileLine[settingIndex + len('setting.'): endIndex].replace('>', '').replace(' ', '').replace('_', '').lower()
203                 if innerToken in self.settingDictionary:
204                         replacedSetting = self.settingDictionary[innerToken]
205                         replacedAlterationLine = alterationFileLine[: beginIndex] + replacedSetting + alterationFileLine[endIndex :]
206                         return self.getReplacedAlterationLine(replacedAlterationLine, beginIndex + len(replacedSetting))
207                 return alterationFileLine
208
209         def getReplacedAlterationText(self):
210                 'Replace the alteration lines if there are settings.'
211                 if self.settingDictionary == None:
212                         return self.distanceFeedRate.output.getvalue().replace('(<alterationDeleteThisPrefix/>)', '')
213                 lines = archive.getTextLines(self.distanceFeedRate.output.getvalue())
214                 distanceFeedRate = gcodec.DistanceFeedRate()
215                 for line in lines:
216                         if line.startswith('(<alterationDeleteThisPrefix/>)'):
217                                 line = self.getReplacedAlterationLine(line[len('(<alterationDeleteThisPrefix/>)') :])
218                         distanceFeedRate.addLine(line)
219                 return distanceFeedRate.output.getvalue()
220
221         def parseInitialization(self):
222                 'Parse gcode initialization and store the parameters.'
223                 for self.lineIndex in xrange(len(self.lines)):
224                         line = self.lines[self.lineIndex]
225                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
226                         firstWord = gcodec.getFirstWord(splitLine)
227                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
228                         if firstWord == '(</extruderInitialization>)':
229                                 self.distanceFeedRate.addTagBracketedProcedure('alteration')
230                                 return
231                         self.distanceFeedRate.addLine(line)
232
233         def setSettingDictionary(self):
234                 'Set the setting dictionary from the gcode text.'
235                 for line in self.lines:
236                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
237                         firstWord = gcodec.getFirstWord(splitLine)
238                         if firstWord == '(<setting>' and self.settingDictionary != None:
239                                 if len(splitLine) > 4:
240                                         procedure = splitLine[1]
241                                         name = splitLine[2].replace('_', ' ').replace(' ', '')
242                                         if '(' in name:
243                                                 name = name[: name.find('(')]
244                                         value = ' '.join(splitLine[3 : -1])
245                                         self.settingDictionary[(procedure + '.' + name).lower()] = value
246                         elif firstWord == '(<settings>)':
247                                 self.settingDictionary = {}
248                         elif firstWord == '(</settings>)':
249                                 return
250
251
252 def main():
253         "Display the alteration dialog."
254         if len(sys.argv) > 1:
255                 writeOutput(' '.join(sys.argv[1 :]))
256         else:
257                 settings.startMainLoopFromConstructor(getNewRepository())
258
259 if __name__ == "__main__":
260         main()