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.
5 The dwindle manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dwindle
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.
12 ===End Rate Multiplier===
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.
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.
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.
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.
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.
36 This brings up the dwindle dialog.
38 > python dwindle.py Screw Holder Bottom.stl
39 The dwindle tool is parsing the file:
40 Screw Holder Bottom.stl
42 The dwindle tool has created the file:
43 .. Screw Holder Bottom_dwindle.gcode
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.
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
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'
69 def getCraftedText(fileName, gcodeText, repository=None):
70 'Dwindle a gcode linear move text.'
71 return getCraftedTextFromText(archive.getTextIfEmpty(fileName, gcodeText), repository)
73 def getCraftedTextFromText(gcodeText, repository=None):
74 'Dwindle a gcode linear move text.'
75 if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'dwindle'):
77 if repository == None:
78 repository = settings.getReadRepository(DwindleRepository())
79 if not repository.activateDwindle.value:
81 return DwindleSkein().getCraftedGcode(gcodeText, repository)
83 def getNewRepository():
85 return DwindleRepository()
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)
92 class DwindleRepository:
93 'A class to handle the dwindle settings.'
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'
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)
115 'A class to dwindle a skein of extrusions.'
118 self.distanceFeedRate = gcodec.DistanceFeedRate()
119 self.feedRateMinute = 959.0
120 self.isActive = False
124 self.oldFlowRate = None
125 self.oldLocation = None
126 self.operatingFlowRate = None
127 self.threadSections = []
130 'Add the thread sections to the gcode.'
131 if len(self.threadSections) == 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 = []
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.')
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]
164 return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue())
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')
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)
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:
194 firstWord = splitLine[0]
195 if firstWord == 'G1':
196 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
197 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
199 self.threadSections.append(ThreadSection(self.feedRateMinute, self.oldFlowRate, location, self.oldLocation))
200 self.oldLocation = location
201 elif firstWord == '(<layer>':
203 settings.printProgress(self.layerIndex, 'dwindle')
204 elif firstWord == 'M101':
206 elif firstWord == 'M103':
207 self.isActive = False
209 elif firstWord == 'M108':
210 self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1])
211 if len(self.threadSections) == 0:
212 self.distanceFeedRate.addLine(line)
216 'A class to handle a volumetric section of a thread.'
217 def __init__(self, feedRateMinute, flowRate, location, oldLocation):
219 self.feedRateMinute = feedRateMinute
220 self.flowRate = flowRate
221 self.location = location
222 self.oldLocation = oldLocation
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)
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)
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)
249 if self.dwindlePortionBegin > 1.0 and self.dwindlePortionEnd < 1.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)
265 rateMultiplier = min(0.5 * (self.dwindlePortionBegin + self.dwindlePortionEnd), 1.0)
266 self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, self.location, rateMultiplier, slowdownFlowRateMultiplier)
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
276 def getLocation(self, along):
277 'Get location along way.'
278 return self.location * along + self.oldLocation * (1.0 - along)
282 'Display the dwindle dialog.'
283 if len(sys.argv) > 1:
284 writeOutput(' '.join(sys.argv[1 :]))
286 settings.startMainLoopFromConstructor(getNewRepository())
288 if __name__ == '__main__':