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 / dwindle.py
1 """
2 This page is in the table of contents.
3 Dwindle is a plugin to reduce the feed rate and flow rate at the end of the thread, in order to reduce the ooze when traveling. It reduces the flow rate by a bit more than the feed rate, in order to use up the pent up plastic in the thread so that there is less remaining in the ooze.
4
5 The dwindle manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dwindle
7
8 ==Operation==
9 The default 'Activate Dwindle' checkbox is off.  When it is on, the functions described below will work, when it is off, nothing will be done.
10
11 ==Settings==
12 ===End Rate Multiplier===
13 Default: 0.5
14
15 Defines the ratio of the feed and flow rate at the end over the feed and flow rate of the rest of the thread. With reasonable values for the 'Pent Up Volume' and 'Slowdown Volume', the amount of ooze should be roughly proportional to the square of the 'End Rate Multiplier'. If the 'End Rate Multiplier' is too low, the printing will be very slow because the feed rate will be lower. If the 'End Rate Multiplier' is too high, there will still be a lot of ooze.
16
17 ===Pent Up Volume===
18 Default: 0.4 mm3
19
20 When the filament is stopped, there is a pent up volume of plastic that comes out afterwards. For best results, the 'Pent Up Volume' in dwindle should be set to that amount. If the 'Pent Up Volume' is too small, there will still be a lot of ooze. If the 'Pent Up Volume' is too large, the end of the thread will be thinner than the rest of the thread.
21
22 ===Slowdown Steps===
23 Default: 3
24
25 Dwindle reduces the feed rate and flow rate in steps so the thread will remain at roughly the same thickness until the end.  The "Slowdown Steps" setting is the number of steps, the more steps the smaller the variation in the thread thickness, but the larger the size of the resulting gcode file and the more time spent pausing between segments.
26
27 ===Slowdown Volume===
28 Default: 5 mm3
29
30 The 'Slowdown Volume' is the volume of the end of the thread where the feed and flow rates will be decreased. If the 'Slowdown Volume' is too small, there won't be enough time to get rid of the pent up plastic, so there will still be a lot of ooze. If the 'Slowdown Volume' is too large, a bit of time will be wasted because for a large portion of the thread, the feed rate will be slow. Overall, it is best to err on being too large, because too large would only waste machine time in production, rather than the more important string removal labor time.
31
32 ==Examples==
33 The following examples dwindle the file Screw Holder Bottom.stl.  The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and dwindle.py.
34
35 > python dwindle.py
36 This brings up the dwindle dialog.
37
38 > python dwindle.py Screw Holder Bottom.stl
39 The dwindle tool is parsing the file:
40 Screw Holder Bottom.stl
41 ..
42 The dwindle tool has created the file:
43 .. Screw Holder Bottom_dwindle.gcode
44
45 """
46
47 from __future__ import absolute_import
48 #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.
49 import __init__
50
51 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
52 from fabmetheus_utilities.vector3 import Vector3
53 from fabmetheus_utilities import archive
54 from fabmetheus_utilities import euclidean
55 from fabmetheus_utilities import gcodec
56 from fabmetheus_utilities import settings
57 from skeinforge_application.skeinforge_utilities import skeinforge_craft
58 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
59 from skeinforge_application.skeinforge_utilities import skeinforge_profile
60 import math
61 import sys
62
63
64 __author__ = 'Enrique Perez (perez_enrique aht yahoo.com)'
65 __date__ = '$Date: 2008/21/04 $'
66 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
67
68
69 def getCraftedText(fileName, gcodeText, repository=None):
70         'Dwindle a gcode linear move text.'
71         return getCraftedTextFromText(archive.getTextIfEmpty(fileName, gcodeText), repository)
72
73 def getCraftedTextFromText(gcodeText, repository=None):
74         'Dwindle a gcode linear move text.'
75         if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'dwindle'):
76                 return gcodeText
77         if repository == None:
78                 repository = settings.getReadRepository(DwindleRepository())
79         if not repository.activateDwindle.value:
80                 return gcodeText
81         return DwindleSkein().getCraftedGcode(gcodeText, repository)
82
83 def getNewRepository():
84         'Get new repository.'
85         return DwindleRepository()
86
87 def writeOutput(fileName, shouldAnalyze=True):
88         'Dwindle a gcode linear move file.  Chain dwindle the gcode if it is not already dwindle.'
89         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'dwindle', shouldAnalyze)
90
91
92 class DwindleRepository:
93         'A class to handle the dwindle settings.'
94         def __init__(self):
95                 'Set the default settings, execute title & settings fileName.'
96                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.dwindle.html', self)
97                 self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Dwindle', self, '')
98                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dwindle')
99                 self.activateDwindle = settings.BooleanSetting().getFromValue('Activate Dwindle', self, False)
100                 settings.LabelSeparator().getFromRepository(self)
101                 self.endRateMultiplier = settings.FloatSpin().getFromValue(0.4, 'End Rate Multiplier (ratio):', self, 0.8, 0.5)
102                 self.pentUpVolume = settings.FloatSpin().getFromValue(0.1, 'Pent Up Volume (cubic millimeters):', self, 1.0, 0.4)
103                 self.slowdownSteps = settings.IntSpin().getFromValue(2, 'Slowdown Steps (positive integer):', self, 10, 3)
104                 self.slowdownVolume = settings.FloatSpin().getFromValue(1.0, 'Slowdown Volume (cubic millimeters):', self, 10.0, 5.0)
105                 self.executeTitle = 'Dwindle'
106
107         def execute(self):
108                 'Dwindle button has been clicked.'
109                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
110                 for fileName in fileNames:
111                         writeOutput(fileName)
112
113
114 class DwindleSkein:
115         'A class to dwindle a skein of extrusions.'
116         def __init__(self):
117                 'Initialize.'
118                 self.distanceFeedRate = gcodec.DistanceFeedRate()
119                 self.feedRateMinute = 959.0
120                 self.isActive = False
121                 self.layerIndex = -1
122                 self.lineIndex = 0
123                 self.lines = None
124                 self.oldFlowRate = None
125                 self.oldLocation = None
126                 self.operatingFlowRate = None
127                 self.threadSections = []
128
129         def addThread(self):
130                 'Add the thread sections to the gcode.'
131                 if len(self.threadSections) == 0:
132                         return
133                 area = self.area
134                 dwindlePortion = 0.0
135                 endRateMultiplier = self.repository.endRateMultiplier.value
136                 halfOverSteps = self.halfOverSteps
137                 oneOverSteps = self.oneOverSteps
138                 currentPentUpVolume = self.repository.pentUpVolume.value * self.oldFlowRate / self.operatingFlowRate
139                 slowdownFlowRateMultiplier = 1.0 - (currentPentUpVolume / self.repository.slowdownVolume.value)
140                 operatingFeedRateMinute = self.operatingFeedRateMinute
141                 slowdownVolume = self.repository.slowdownVolume.value
142                 for threadSectionIndex in xrange(len(self.threadSections) - 1, -1, -1):
143                         threadSection = self.threadSections[threadSectionIndex]
144                         dwindlePortion = threadSection.getDwindlePortion(area, dwindlePortion, operatingFeedRateMinute, self.operatingFlowRate, slowdownVolume)
145                 for threadSection in self.threadSections:
146                         threadSection.addGcodeThreadSection(self.distanceFeedRate, endRateMultiplier, halfOverSteps, oneOverSteps, slowdownFlowRateMultiplier)
147                 self.distanceFeedRate.addFlowRateLine(self.oldFlowRate)
148                 self.threadSections = []
149
150         def getCraftedGcode(self, gcodeText, repository):
151                 'Parse gcode text and store the dwindle gcode.'
152                 self.lines = archive.getTextLines(gcodeText)
153                 self.repository = repository
154                 self.parseInitialization()
155                 if self.operatingFlowRate == None:
156                         print('Warning, there is no operatingFlowRate so dwindle will do nothing.')
157                         return gcodeText
158                 self.area = self.infillWidth * self.layerHeight * self.volumeFraction
159                 self.oneOverSteps = 1.0 / float(repository.slowdownSteps.value)
160                 self.halfOverSteps = 0.5 * self.oneOverSteps
161                 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
162                         line = self.lines[self.lineIndex]
163                         self.parseLine(line)
164                 return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue())
165
166         def parseInitialization(self):
167                 'Parse gcode initialization and store the parameters.'
168                 for self.lineIndex in xrange(len(self.lines)):
169                         line = self.lines[self.lineIndex]
170                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
171                         firstWord = gcodec.getFirstWord(splitLine)
172                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
173                         if firstWord == '(</extruderInitialization>)':
174                                 self.distanceFeedRate.addTagBracketedProcedure('dwindle')
175                                 return
176                         elif firstWord == '(<infillWidth>':
177                                 self.infillWidth = float(splitLine[1])
178                         elif firstWord == '(<layerHeight>':
179                                 self.layerHeight = float(splitLine[1])
180                         elif firstWord == '(<operatingFeedRatePerSecond>':
181                                 self.operatingFeedRateMinute = 60.0 * float(splitLine[1])
182                         elif firstWord == '(<operatingFlowRate>':
183                                 self.operatingFlowRate = float(splitLine[1])
184                                 self.oldFlowRate = self.operatingFlowRate
185                         elif firstWord == '(<volumeFraction>':
186                                 self.volumeFraction = float(splitLine[1])
187                         self.distanceFeedRate.addLine(line)
188
189         def parseLine(self, line):
190                 'Parse a gcode line and add it to the dwindle skein.'
191                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
192                 if len(splitLine) < 1:
193                         return
194                 firstWord = splitLine[0]
195                 if firstWord == 'G1':
196                         self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
197                         location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
198                         if self.isActive:
199                                 self.threadSections.append(ThreadSection(self.feedRateMinute, self.oldFlowRate, location, self.oldLocation))
200                         self.oldLocation = location
201                 elif firstWord == '(<layer>':
202                         self.layerIndex += 1
203                         settings.printProgress(self.layerIndex, 'dwindle')
204                 elif firstWord == 'M101':
205                         self.isActive = True
206                 elif firstWord == 'M103':
207                         self.isActive = False
208                         self.addThread()
209                 elif firstWord == 'M108':
210                         self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1])
211                 if len(self.threadSections) == 0:
212                         self.distanceFeedRate.addLine(line)
213
214
215 class ThreadSection:
216         'A class to handle a volumetric section of a thread.'
217         def __init__(self, feedRateMinute, flowRate, location, oldLocation):
218                 'Initialize.'
219                 self.feedRateMinute = feedRateMinute
220                 self.flowRate = flowRate
221                 self.location = location
222                 self.oldLocation = oldLocation
223
224         def addGcodeMovementByRate(self, distanceFeedRate, endRateMultiplier, location, rateMultiplier, slowdownFlowRateMultiplier):
225                 'Add gcode movement by rate multiplier.'
226                 flowRate = self.flowRate
227                 rateMultiplier = rateMultiplier + endRateMultiplier * (1.0 - rateMultiplier)
228                 if rateMultiplier < 1.0:
229                         flowRate *= slowdownFlowRateMultiplier
230                 distanceFeedRate.addFlowRateLine(flowRate * rateMultiplier)
231                 distanceFeedRate.addGcodeMovementZWithFeedRateVector3(self.feedRateMinute * rateMultiplier, location)
232
233         def addGcodeThreadSection(self, distanceFeedRate, endRateMultiplier, halfOverSteps, oneOverSteps, slowdownFlowRateMultiplier):
234                 'Add gcode thread section.'
235                 if self.dwindlePortionEnd > 1.0 - halfOverSteps:
236                         distanceFeedRate.addFlowRateLine(self.flowRate)
237                         distanceFeedRate.addGcodeMovementZWithFeedRateVector3(self.feedRateMinute, self.location)
238                         return
239                 dwindleDifference = self.dwindlePortionBegin - self.dwindlePortionEnd
240                 if self.dwindlePortionBegin < 1.0 and dwindleDifference > oneOverSteps:
241                         numberOfStepsFloat = math.ceil(dwindleDifference / oneOverSteps)
242                         numberOfSteps = int(numberOfStepsFloat)
243                         for stepIndex in xrange(numberOfSteps):
244                                 alongBetween = (float(stepIndex) + 0.5) / numberOfStepsFloat
245                                 location = self.getLocation(float(stepIndex + 1) / numberOfStepsFloat)
246                                 rateMultiplier = self.dwindlePortionEnd * alongBetween + self.dwindlePortionBegin * (1.0 - alongBetween)
247                                 self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, location, rateMultiplier, slowdownFlowRateMultiplier)
248                         return
249                 if self.dwindlePortionBegin > 1.0 and self.dwindlePortionEnd < 1.0:
250                         alongDwindle = 0.0
251                         if self.dwindlePortionBegin > 1.0 + halfOverSteps:
252                                 alongDwindle = (self.dwindlePortionBegin - 1.0) / dwindleDifference
253                                 self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, self.getLocation(alongDwindle), 1.0, slowdownFlowRateMultiplier)
254                         alongDwindlePortion = self.dwindlePortionEnd * alongDwindle + self.dwindlePortionBegin * (1.0 - alongDwindle)
255                         alongDwindleDifference = alongDwindlePortion - self.dwindlePortionEnd
256                         numberOfStepsFloat = math.ceil(alongDwindleDifference / oneOverSteps)
257                         numberOfSteps = int(numberOfStepsFloat)
258                         for stepIndex in xrange(numberOfSteps):
259                                 alongBetween = (float(stepIndex) + 0.5) / numberOfStepsFloat
260                                 alongDwindleLocation = float(stepIndex + 1) / numberOfStepsFloat
261                                 location = self.getLocation(alongDwindleLocation + alongDwindle * (1.0 - alongDwindleLocation))
262                                 rateMultiplier = self.dwindlePortionEnd * alongBetween + alongDwindlePortion * (1.0 - alongBetween)
263                                 self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, location, rateMultiplier, slowdownFlowRateMultiplier)
264                         return
265                 rateMultiplier = min(0.5 * (self.dwindlePortionBegin + self.dwindlePortionEnd), 1.0)
266                 self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, self.location, rateMultiplier, slowdownFlowRateMultiplier)
267
268         def getDwindlePortion(self, area, dwindlePortion, operatingFeedRateMinute, operatingFlowRate, slowdownVolume):
269                 'Get cumulative dwindle portion.'
270                 self.dwindlePortionEnd = dwindlePortion
271                 distance = abs(self.oldLocation - self.location)
272                 volume = area * distance
273                 self.dwindlePortionBegin = dwindlePortion + volume / slowdownVolume
274                 return self.dwindlePortionBegin
275
276         def getLocation(self, along):
277                 'Get location along way.'
278                 return self.location * along + self.oldLocation * (1.0 - along)
279
280
281 def main():
282         'Display the dwindle dialog.'
283         if len(sys.argv) > 1:
284                 writeOutput(' '.join(sys.argv[1 :]))
285         else:
286                 settings.startMainLoopFromConstructor(getNewRepository())
287
288 if __name__ == '__main__':
289         main()