2 This page is in the table of contents.
3 Fillet rounds the corners slightly in a variety of ways. This is to reduce corner blobbing and sudden extruder acceleration.
5 The fillet manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fillet
9 The default 'Activate Fillet' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done.
12 ===Fillet Procedure Choice===
16 When selected, the corners will be filleted with an arc using the gcode point form.
19 When selected, the corners will be filleted with an arc using the gcode radius form.
22 When selected, the corners will be filleted with an arc composed of several segments.
25 When selected, the corners will be beveled.
27 ===Corner Feed Rate Multiplier===
30 Defines the ratio of the feed rate in corners over the original feed rate. With a high value the extruder will move quickly in corners, accelerating quickly and leaving a thin extrusion. With a low value, the extruder will move slowly in corners, accelerating gently and leaving a thick extrusion.
32 ===Fillet Radius over Perimeter Width===
35 Defines the width of the fillet.
37 ===Reversal Slowdown over Perimeter Width===
40 Defines how far before a path reversal the extruder will slow down. Some tools, like nozzle wipe, double back the path of the extruder and this option will add a slowdown point in that path so there won't be a sudden jerk at the end of the path. If the value is less than 0.1 a slowdown will not be added.
42 ===Use Intermediate Feed Rate in Corners===
45 When selected, the feed rate entering the corner will be the average of the old feed rate and the new feed rate.
48 The following examples fillet the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and fillet.py.
51 This brings up the fillet dialog.
53 > python fillet.py Screw Holder Bottom.stl
54 The fillet tool is parsing the file:
55 Screw Holder Bottom.stl
57 The fillet tool has created the file:
58 .. Screw Holder Bottom_fillet.gcode
62 from __future__ import absolute_import
63 #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.
66 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
67 from fabmetheus_utilities.vector3 import Vector3
68 from fabmetheus_utilities import archive
69 from fabmetheus_utilities import euclidean
70 from fabmetheus_utilities import gcodec
71 from fabmetheus_utilities import settings
72 from skeinforge_application.skeinforge_utilities import skeinforge_craft
73 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
74 from skeinforge_application.skeinforge_utilities import skeinforge_profile
79 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
80 __date__ = '$Date: 2008/21/04 $'
81 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
84 def getCraftedText( fileName, gcodeText, repository = None ):
85 "Fillet a gcode linear move file or text."
86 return getCraftedTextFromText( archive.getTextIfEmpty( fileName, gcodeText ), repository )
88 def getCraftedTextFromText( gcodeText, repository = None ):
89 "Fillet a gcode linear move text."
90 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'fillet'):
92 if repository == None:
93 repository = settings.getReadRepository( FilletRepository() )
94 if not repository.activateFillet.value:
96 if repository.arcPoint.value:
97 return ArcPointSkein().getCraftedGcode( repository, gcodeText )
98 elif repository.arcRadius.value:
99 return ArcRadiusSkein().getCraftedGcode( repository, gcodeText )
100 elif repository.arcSegment.value:
101 return ArcSegmentSkein().getCraftedGcode( repository, gcodeText )
102 elif repository.bevel.value:
103 return BevelSkein().getCraftedGcode( repository, gcodeText )
106 def getNewRepository():
107 'Get new repository.'
108 return FilletRepository()
110 def writeOutput(fileName, shouldAnalyze=True):
111 "Fillet a gcode linear move file. Depending on the settings, either arcPoint, arcRadius, arcSegment, bevel or do nothing."
112 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'fillet', shouldAnalyze)
116 "A class to bevel a skein of extrusions."
118 self.distanceFeedRate = gcodec.DistanceFeedRate()
119 self.extruderActive = False
120 self.feedRateMinute = 960.0
121 self.filletRadius = 0.2
124 self.oldFeedRateMinute = None
125 self.oldLocation = None
126 self.shouldAddLine = True
128 def addLinearMovePoint( self, feedRateMinute, point ):
129 "Add a gcode linear move, feedRate and newline to the output."
130 self.distanceFeedRate.addLine( self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( feedRateMinute, point.dropAxis(), point.z ) )
132 def getCornerFeedRate(self):
133 "Get the corner feed rate, which may be based on the intermediate feed rate."
134 feedRateMinute = self.feedRateMinute
135 if self.repository.useIntermediateFeedRateInCorners.value:
136 if self.oldFeedRateMinute != None:
137 feedRateMinute = 0.5 * ( self.oldFeedRateMinute + self.feedRateMinute )
138 return feedRateMinute * self.cornerFeedRateMultiplier
140 def getCraftedGcode( self, repository, gcodeText ):
141 "Parse gcode text and store the bevel gcode."
142 self.cornerFeedRateMultiplier = repository.cornerFeedRateMultiplier.value
143 self.lines = archive.getTextLines(gcodeText)
144 self.repository = repository
145 self.parseInitialization( repository )
146 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
147 line = self.lines[self.lineIndex]
149 return self.distanceFeedRate.output.getvalue()
151 def getExtruderOffReversalPoint( self, afterSegment, afterSegmentComplex, beforeSegment, beforeSegmentComplex, location ):
152 "If the extruder is off and the path is reversing, add intermediate slow points."
153 if self.repository.reversalSlowdownDistanceOverEdgeWidth.value < 0.1:
155 if self.extruderActive:
157 reversalBufferSlowdownDistance = self.reversalSlowdownDistance * 2.0
158 afterSegmentComplexLength = abs( afterSegmentComplex )
159 if afterSegmentComplexLength < reversalBufferSlowdownDistance:
161 beforeSegmentComplexLength = abs( beforeSegmentComplex )
162 if beforeSegmentComplexLength < reversalBufferSlowdownDistance:
164 afterSegmentComplexNormalized = afterSegmentComplex / afterSegmentComplexLength
165 beforeSegmentComplexNormalized = beforeSegmentComplex / beforeSegmentComplexLength
166 if euclidean.getDotProduct( afterSegmentComplexNormalized, beforeSegmentComplexNormalized ) < 0.95:
168 slowdownFeedRate = self.feedRateMinute * 0.5
169 self.shouldAddLine = False
170 beforePoint = euclidean.getPointPlusSegmentWithLength( self.reversalSlowdownDistance * abs( beforeSegment ) / beforeSegmentComplexLength, location, beforeSegment )
171 self.addLinearMovePoint( self.feedRateMinute, beforePoint )
172 self.addLinearMovePoint( slowdownFeedRate, location )
173 afterPoint = euclidean.getPointPlusSegmentWithLength( self.reversalSlowdownDistance * abs( afterSegment ) / afterSegmentComplexLength, location, afterSegment )
174 self.addLinearMovePoint( slowdownFeedRate, afterPoint )
177 def getNextLocation(self):
178 "Get the next linear move. Return none is none is found."
179 for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ):
180 line = self.lines[ afterIndex ]
181 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
182 if gcodec.getFirstWord(splitLine) == 'G1':
183 nextLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
187 def linearMove( self, splitLine ):
188 "Bevel a linear move."
189 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
190 self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine )
191 if self.oldLocation != None:
192 nextLocation = self.getNextLocation()
193 if nextLocation != None:
194 location = self.splitPointGetAfter( location, nextLocation )
195 self.oldLocation = location
196 self.oldFeedRateMinute = self.feedRateMinute
198 def parseInitialization( self, repository ):
199 'Parse gcode initialization and store the parameters.'
200 for self.lineIndex in xrange(len(self.lines)):
201 line = self.lines[self.lineIndex]
202 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
203 firstWord = gcodec.getFirstWord(splitLine)
204 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
205 if firstWord == '(</extruderInitialization>)':
206 self.distanceFeedRate.addTagBracketedProcedure('fillet')
208 elif firstWord == '(<edgeWidth>':
209 edgeWidth = abs(float(splitLine[1]))
210 self.curveSection = 0.7 * edgeWidth
211 self.filletRadius = edgeWidth * repository.filletRadiusOverEdgeWidth.value
212 self.minimumRadius = 0.1 * edgeWidth
213 self.reversalSlowdownDistance = edgeWidth * repository.reversalSlowdownDistanceOverEdgeWidth.value
214 self.distanceFeedRate.addLine(line)
216 def parseLine(self, line):
217 "Parse a gcode line and add it to the bevel gcode."
218 self.shouldAddLine = True
219 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
220 if len(splitLine) < 1:
222 firstWord = splitLine[0]
223 if firstWord == 'G1':
224 self.linearMove(splitLine)
225 elif firstWord == 'M101':
226 self.extruderActive = True
227 elif firstWord == 'M103':
228 self.extruderActive = False
229 if self.shouldAddLine:
230 self.distanceFeedRate.addLine(line)
232 def splitPointGetAfter( self, location, nextLocation ):
233 "Bevel a point and return the end of the bevel. should get complex for radius"
234 if self.filletRadius < 2.0 * self.minimumRadius:
236 afterSegment = nextLocation - location
237 afterSegmentComplex = afterSegment.dropAxis()
238 afterSegmentComplexLength = abs( afterSegmentComplex )
239 thirdAfterSegmentLength = 0.333 * afterSegmentComplexLength
240 if thirdAfterSegmentLength < self.minimumRadius:
242 beforeSegment = self.oldLocation - location
243 beforeSegmentComplex = beforeSegment.dropAxis()
244 beforeSegmentComplexLength = abs( beforeSegmentComplex )
245 thirdBeforeSegmentLength = 0.333 * beforeSegmentComplexLength
246 if thirdBeforeSegmentLength < self.minimumRadius:
248 extruderOffReversalPoint = self.getExtruderOffReversalPoint( afterSegment, afterSegmentComplex, beforeSegment, beforeSegmentComplex, location )
249 if extruderOffReversalPoint != None:
250 return extruderOffReversalPoint
251 bevelRadius = min( thirdAfterSegmentLength, self.filletRadius )
252 bevelRadius = min( thirdBeforeSegmentLength, bevelRadius )
253 self.shouldAddLine = False
254 beforePoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( beforeSegment ) / beforeSegmentComplexLength, location, beforeSegment )
255 self.addLinearMovePoint( self.feedRateMinute, beforePoint )
256 afterPoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( afterSegment ) / afterSegmentComplexLength, location, afterSegment )
257 self.addLinearMovePoint( self.getCornerFeedRate(), afterPoint )
261 class ArcSegmentSkein( BevelSkein ):
262 "A class to arc segment a skein of extrusions."
263 def addArc( self, afterCenterDifferenceAngle, afterPoint, beforeCenterSegment, beforePoint, center ):
264 "Add arc segments to the filleted skein."
265 absoluteDifferenceAngle = abs( afterCenterDifferenceAngle )
266 # steps = int( math.ceil( absoluteDifferenceAngle * 1.5 ) )
267 steps = int( math.ceil( min( absoluteDifferenceAngle * 1.5, absoluteDifferenceAngle * abs( beforeCenterSegment ) / self.curveSection ) ) )
268 stepPlaneAngle = euclidean.getWiddershinsUnitPolar( afterCenterDifferenceAngle / steps )
269 for step in xrange( 1, steps ):
270 beforeCenterSegment = euclidean.getRoundZAxisByPlaneAngle( stepPlaneAngle, beforeCenterSegment )
271 arcPoint = center + beforeCenterSegment
272 self.addLinearMovePoint( self.getCornerFeedRate(), arcPoint )
273 self.addLinearMovePoint( self.getCornerFeedRate(), afterPoint )
275 def splitPointGetAfter( self, location, nextLocation ):
276 "Fillet a point into arc segments and return the end of the last segment."
277 if self.filletRadius < 2.0 * self.minimumRadius:
279 afterSegment = nextLocation - location
280 afterSegmentComplex = afterSegment.dropAxis()
281 thirdAfterSegmentLength = 0.333 * abs( afterSegmentComplex )
282 if thirdAfterSegmentLength < self.minimumRadius:
284 beforeSegment = self.oldLocation - location
285 beforeSegmentComplex = beforeSegment.dropAxis()
286 thirdBeforeSegmentLength = 0.333 * abs( beforeSegmentComplex )
287 if thirdBeforeSegmentLength < self.minimumRadius:
289 extruderOffReversalPoint = self.getExtruderOffReversalPoint( afterSegment, afterSegmentComplex, beforeSegment, beforeSegmentComplex, location )
290 if extruderOffReversalPoint != None:
291 return extruderOffReversalPoint
292 bevelRadius = min( thirdAfterSegmentLength, self.filletRadius )
293 bevelRadius = min( thirdBeforeSegmentLength, bevelRadius )
294 self.shouldAddLine = False
295 beforePoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( beforeSegment ) / abs( beforeSegmentComplex ), location, beforeSegment )
296 self.addLinearMovePoint( self.feedRateMinute, beforePoint )
297 afterPoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( afterSegment ) / abs( afterSegmentComplex ), location, afterSegment )
298 afterPointComplex = afterPoint.dropAxis()
299 beforePointComplex = beforePoint.dropAxis()
300 locationComplex = location.dropAxis()
301 midpoint = 0.5 * ( afterPoint + beforePoint )
302 midpointComplex = midpoint.dropAxis()
303 midpointMinusLocationComplex = midpointComplex - locationComplex
304 midpointLocationLength = abs( midpointMinusLocationComplex )
305 if midpointLocationLength < 0.01 * self.filletRadius:
306 self.addLinearMovePoint( self.getCornerFeedRate(), afterPoint )
308 midpointAfterPointLength = abs( midpointComplex - afterPointComplex )
309 midpointCenterLength = midpointAfterPointLength * midpointAfterPointLength / midpointLocationLength
310 radius = math.sqrt( midpointCenterLength * midpointCenterLength + midpointAfterPointLength * midpointAfterPointLength )
311 centerComplex = midpointComplex + midpointMinusLocationComplex * midpointCenterLength / midpointLocationLength
312 center = Vector3( centerComplex.real, centerComplex.imag, midpoint.z )
313 afterCenterComplex = afterPointComplex - centerComplex
314 beforeCenter = beforePoint - center
315 angleDifference = euclidean.getAngleDifferenceByComplex( afterCenterComplex, beforeCenter.dropAxis() )
316 self.addArc( angleDifference, afterPoint, beforeCenter, beforePoint, center )
320 class ArcPointSkein( ArcSegmentSkein ):
321 "A class to arc point a skein of extrusions."
322 def addArc( self, afterCenterDifferenceAngle, afterPoint, beforeCenterSegment, beforePoint, center ):
323 "Add an arc point to the filleted skein."
324 if afterCenterDifferenceAngle == 0.0:
326 afterPointMinusBefore = afterPoint - beforePoint
327 centerMinusBefore = center - beforePoint
329 if afterCenterDifferenceAngle < 0.0:
331 centerMinusBeforeComplex = centerMinusBefore.dropAxis()
332 if abs( centerMinusBeforeComplex ) <= 0.0:
334 radius = abs( centerMinusBefore )
335 arcDistanceZ = complex( abs( afterCenterDifferenceAngle ) * radius, afterPointMinusBefore.z )
336 distance = abs( arcDistanceZ )
339 line = self.distanceFeedRate.getFirstWordMovement( firstWord, afterPointMinusBefore ) + self.getRelativeCenter( centerMinusBeforeComplex )
340 cornerFeedRate = self.getCornerFeedRate()
341 if cornerFeedRate != None:
342 line += ' F' + self.distanceFeedRate.getRounded(cornerFeedRate)
343 self.distanceFeedRate.addLine(line)
345 def getRelativeCenter( self, centerMinusBeforeComplex ):
346 "Get the relative center."
347 return ' I%s J%s' % ( self.distanceFeedRate.getRounded( centerMinusBeforeComplex.real ), self.distanceFeedRate.getRounded( centerMinusBeforeComplex.imag ) )
350 class ArcRadiusSkein( ArcPointSkein ):
351 "A class to arc radius a skein of extrusions."
352 def getRelativeCenter( self, centerMinusBeforeComplex ):
353 "Get the relative center."
354 radius = abs( centerMinusBeforeComplex )
355 return ' R' + ( self.distanceFeedRate.getRounded(radius) )
358 class FilletRepository:
359 "A class to handle the fillet settings."
361 "Set the default settings, execute title & settings fileName."
362 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.fillet.html', self )
363 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File to be Filleted', self, '')
364 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fillet')
365 self.activateFillet = settings.BooleanSetting().getFromValue('Activate Fillet', self, False )
366 self.filletProcedureChoiceLabel = settings.LabelDisplay().getFromName('Fillet Procedure Choice: ', self )
367 filletLatentStringVar = settings.LatentStringVar()
368 self.arcPoint = settings.Radio().getFromRadio( filletLatentStringVar, 'Arc Point', self, False )
369 self.arcRadius = settings.Radio().getFromRadio( filletLatentStringVar, 'Arc Radius', self, False )
370 self.arcSegment = settings.Radio().getFromRadio( filletLatentStringVar, 'Arc Segment', self, False )
371 self.bevel = settings.Radio().getFromRadio( filletLatentStringVar, 'Bevel', self, True )
372 self.cornerFeedRateMultiplier = settings.FloatSpin().getFromValue(0.8, 'Corner Feed Rate Multiplier (ratio):', self, 1.2, 1.0)
373 self.filletRadiusOverEdgeWidth = settings.FloatSpin().getFromValue( 0.25, 'Fillet Radius over Perimeter Width (ratio):', self, 0.65, 0.35 )
374 self.reversalSlowdownDistanceOverEdgeWidth = settings.FloatSpin().getFromValue( 0.3, 'Reversal Slowdown Distance over Perimeter Width (ratio):', self, 0.7, 0.5 )
375 self.useIntermediateFeedRateInCorners = settings.BooleanSetting().getFromValue('Use Intermediate Feed Rate in Corners', self, True )
376 self.executeTitle = 'Fillet'
379 "Fillet button has been clicked."
380 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
381 for fileName in fileNames:
382 writeOutput(fileName)
386 "Display the fillet dialog."
387 if len(sys.argv) > 1:
388 writeOutput(' '.join(sys.argv[1 :]))
390 settings.startMainLoopFromConstructor(getNewRepository())
392 if __name__ == "__main__":