2 This page is in the table of contents.
3 Skirt is a plugin to give the extruder some extra time to begin extruding properly before beginning the object, and to put a baffle around the model in order to keep the extrusion warm.
5 The skirt manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Skirt
8 It is loosely based on Lenbook's outline plugin:
10 http://www.thingiverse.com/thing:4918
12 it is also loosely based on the outline that Nophead sometimes uses:
14 http://hydraraptor.blogspot.com/2010/01/hot-metal-and-serendipity.html
16 and also loosely based on the baffles that Nophead made to keep corners warm:
18 http://hydraraptor.blogspot.com/2010/09/some-corners-like-it-hot.html
20 If you want only an outline, set 'Layers To' to one. This gives the extruder some extra time to begin extruding properly before beginning your object, and gives you an early verification of where your object will be extruded.
22 If you also want an insulating skirt around the entire object, set 'Layers To' to a huge number, like 912345678. This will additionally make an insulating baffle around the object; to prevent moving air from cooling the object, which increases warping, especially in corners.
25 The default 'Activate Skirt' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done.
31 When selected, the skirt will be convex, going around the model with only convex angles. If convex is not selected, the skirt will hug the model, going into every nook and cranny.
33 ===Gap over Perimeter Width===
36 Defines the ratio of the gap between the object and the skirt over the edge width. If the ratio is too low, the skirt will connect to the object, if the ratio is too high, the skirt willl not provide much insulation for the object.
41 Defines the number of layers of the skirt. If you want only an outline, set 'Layers To' to one. If you want an insulating skirt around the entire object, set 'Layers To' to a huge number, like 912345678.
44 The following examples skirt the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and skirt.py.
47 This brings up the skirt dialog.
49 > python skirt.py Screw Holder Bottom.stl
50 The skirt tool is parsing the file:
51 Screw Holder Bottom.stl
53 The skirt tool has created the file:
54 .. Screw Holder Bottom_skirt.gcode
59 from __future__ import absolute_import
60 #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.
63 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
64 from fabmetheus_utilities.geometry.solids import triangle_mesh
65 from fabmetheus_utilities.vector3 import Vector3
66 from fabmetheus_utilities import archive
67 from fabmetheus_utilities import euclidean
68 from fabmetheus_utilities import gcodec
69 from fabmetheus_utilities import intercircle
70 from fabmetheus_utilities import settings
71 from skeinforge_application.skeinforge_utilities import skeinforge_craft
72 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
73 from skeinforge_application.skeinforge_utilities import skeinforge_profile
78 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
79 __date__ = '$Date: 2008/21/04 $'
80 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
83 def getCraftedText(fileName, text='', repository=None):
84 'Skirt the fill file or text.'
85 return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository)
87 def getCraftedTextFromText(gcodeText, repository=None):
88 'Skirt the fill text.'
89 if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'skirt'):
91 if repository == None:
92 repository = settings.getReadRepository(SkirtRepository())
93 if repository.skirtLineCount.value < 1:
95 return SkirtSkein().getCraftedGcode(gcodeText, repository)
97 def getNewRepository():
99 return SkirtRepository()
101 def getOuterLoops(loops):
102 'Get widdershins outer loops.'
105 if not euclidean.isPathInsideLoops(outerLoops, loop):
106 outerLoops.append(loop)
107 intercircle.directLoops(True, outerLoops)
110 def writeOutput(fileName, shouldAnalyze=True):
111 'Skirt a gcode linear move file.'
112 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'skirt', shouldAnalyze)
115 class LoopCrossDictionary:
116 'Loop with a horizontal and vertical dictionary.'
118 'Initialize LoopCrossDictionary.'
122 'Get the string representation of this LoopCrossDictionary.'
123 return str(self.loop)
126 class SkirtRepository:
127 'A class to handle the skirt settings.'
129 'Set the default settings, execute title & settings fileName.'
130 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.skirt.html', self)
131 self.fileNameInput = settings.FileNameInput().getFromFileName(
132 fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Skirt', self, '')
133 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Skirt')
134 self.skirtLineCount = settings.IntSpin().getSingleIncrementFromValue(0, 'Skirt line count', self, 20, 1)
135 self.convex = settings.BooleanSetting().getFromValue('Convex:', self, True)
136 self.gapWidth = settings.FloatSpin().getFromValue(1.0, 'Gap Width (mm):', self, 5.0, 3.0)
137 self.layersTo = settings.IntSpin().getSingleIncrementFromValue(0, 'Layers To (index):', self, 912345678, 1)
138 self.executeTitle = 'Skirt'
141 'Skirt button has been clicked.'
142 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(
143 self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
144 for fileName in fileNames:
145 writeOutput(fileName)
149 'A class to skirt a skein of extrusions.'
151 'Initialize variables.'
152 self.distanceFeedRate = gcodec.DistanceFeedRate()
153 self.feedRateMinute = 961.0
154 self.isExtruderActive = False
155 self.isSupportLayer = False
159 self.oldFlowRate = None
160 self.oldLocation = None
161 self.oldTemperatureInput = None
162 self.skirtFlowRate = None
163 self.skirtTemperature = None
164 self.travelFeedRateMinute = 957.0
165 self.unifiedLoop = LoopCrossDictionary()
167 def addFlowRate(self, flowRate):
168 'Add a line of temperature if different.'
170 self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate))
172 def addSkirt(self, z):
173 'At skirt at z to gcode output.'
174 self.setSkirtFeedFlowTemperature()
175 self.distanceFeedRate.addLine('(<skirt>)')
176 oldTemperature = self.oldTemperatureInput
177 self.addTemperatureLineIfDifferent(self.skirtTemperature)
178 self.addFlowRate(self.skirtFlowRate)
179 for outsetLoop in self.outsetLoops:
180 closedLoop = outsetLoop + [outsetLoop[0]]
181 self.distanceFeedRate.addGcodeFromFeedRateThreadZ(self.feedRateMinute, closedLoop, self.travelFeedRateMinute, z)
182 self.addFlowRate(self.oldFlowRate)
183 self.addTemperatureLineIfDifferent(oldTemperature)
184 self.distanceFeedRate.addLine('(</skirt>)')
186 def addTemperatureLineIfDifferent(self, temperature):
187 'Add a line of temperature if different.'
188 if temperature == None or temperature == self.oldTemperatureInput:
190 self.distanceFeedRate.addLine('M104 S' + euclidean.getRoundedToThreePlaces(temperature))
191 self.oldTemperatureInput = temperature
193 def createSegmentDictionaries(self, loopCrossDictionary):
194 'Create horizontal and vertical segment dictionaries.'
195 loopCrossDictionary.horizontalDictionary = self.getHorizontalXIntersectionsTable(loopCrossDictionary.loop)
196 flippedLoop = euclidean.getDiagonalFlippedLoop(loopCrossDictionary.loop)
197 loopCrossDictionary.verticalDictionary = self.getHorizontalXIntersectionsTable(flippedLoop)
199 def createSkirtLoops(self):
200 'Create the skirt loops.'
201 points = euclidean.getPointsByHorizontalDictionary(self.edgeWidth, self.unifiedLoop.horizontalDictionary)
202 points += euclidean.getPointsByVerticalDictionary(self.edgeWidth, self.unifiedLoop.verticalDictionary)
203 loops = triangle_mesh.getDescendingAreaOrientedLoops(points, points, 2.5 * self.edgeWidth)
204 outerLoops = getOuterLoops(loops)
205 self.outsetLoops = []
206 for i in xrange(self.repository.skirtLineCount.value, 0, -1):
207 outsetLoops = intercircle.getInsetSeparateLoopsFromLoops(outerLoops, -self.skirtOutset - i * self.edgeWidth)
208 outsetLoops = getOuterLoops(outsetLoops)
209 if self.repository.convex.value:
210 outsetLoops = [euclidean.getLoopConvex(euclidean.getConcatenatedList(outsetLoops))]
211 self.outsetLoops.extend(outsetLoops)
213 def getCraftedGcode(self, gcodeText, repository):
214 'Parse gcode text and store the skirt gcode.'
215 self.repository = repository
216 self.lines = archive.getTextLines(gcodeText)
217 self.parseInitialization()
218 self.parseBoundaries()
219 self.createSkirtLoops()
220 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
221 line = self.lines[self.lineIndex]
223 return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue())
225 def getHorizontalXIntersectionsTable(self, loop):
226 'Get the horizontal x intersections table from the loop.'
227 horizontalXIntersectionsTable = {}
228 euclidean.addXIntersectionsFromLoopForTable(loop, horizontalXIntersectionsTable, self.edgeWidth)
229 return horizontalXIntersectionsTable
231 def parseBoundaries(self):
232 'Parse the boundaries and union them.'
233 self.createSegmentDictionaries(self.unifiedLoop)
234 if self.repository.layersTo.value < 1:
236 loopCrossDictionary = None
238 for line in self.lines[self.lineIndex :]:
239 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
240 firstWord = gcodec.getFirstWord(splitLine)
241 if firstWord == '(</boundaryPerimeter>)' or firstWord == '(</raftPerimeter>)':
242 self.createSegmentDictionaries(loopCrossDictionary)
243 self.unifyLayer(loopCrossDictionary)
244 loopCrossDictionary = None
245 elif firstWord == '(<boundaryPoint>' or firstWord == '(<raftPoint>':
246 location = gcodec.getLocationFromSplitLine(None, splitLine)
247 if loopCrossDictionary == None:
248 loopCrossDictionary = LoopCrossDictionary()
249 loopCrossDictionary.loop.append(location.dropAxis())
250 elif firstWord == '(<layer>':
252 if layerIndex > self.repository.layersTo.value:
254 settings.printProgress(layerIndex, 'skirt')
256 def parseInitialization(self):
257 'Parse gcode initialization and store the parameters.'
258 for self.lineIndex in xrange(len(self.lines)):
259 line = self.lines[self.lineIndex]
260 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
261 firstWord = gcodec.getFirstWord(splitLine)
262 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
263 if firstWord == '(</extruderInitialization>)':
264 self.distanceFeedRate.addTagBracketedProcedure('skirt')
266 elif firstWord == '(<objectNextLayersTemperature>':
267 self.oldTemperatureInput = float(splitLine[1])
268 self.skirtTemperature = self.oldTemperatureInput
269 elif firstWord == '(<operatingFeedRatePerSecond>':
270 self.feedRateMinute = 60.0 * float(splitLine[1])
271 elif firstWord == '(<operatingFlowRate>':
272 self.oldFlowRate = float(splitLine[1])
273 self.skirtFlowRate = self.oldFlowRate
274 elif firstWord == '(<edgeWidth>':
275 self.edgeWidth = float(splitLine[1])
276 self.skirtOutset = self.repository.gapWidth.value + 0.5 * self.edgeWidth
277 self.distanceFeedRate.addTagRoundedLine('skirtOutset', self.skirtOutset)
278 elif firstWord == '(<travelFeedRatePerSecond>':
279 self.travelFeedRateMinute = 60.0 * float(splitLine[1])
280 self.distanceFeedRate.addLine(line)
282 def parseLine(self, line):
283 'Parse a gcode line and add it to the skirt skein.'
284 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
285 if len(splitLine) < 1:
287 firstWord = splitLine[0]
288 if firstWord == '(<raftPerimeter>)' or firstWord == '(</raftPerimeter>)' or firstWord == '(<raftPoint>':
290 self.distanceFeedRate.addLine(line)
291 if firstWord == 'G1':
292 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
293 elif firstWord == '(<layer>':
295 if self.layerIndex < self.repository.layersTo.value:
296 self.addSkirt(float(splitLine[1]))
297 elif firstWord == 'M101':
298 self.isExtruderActive = True
299 elif firstWord == 'M103':
300 self.isExtruderActive = False
301 elif firstWord == 'M104':
302 self.oldTemperatureInput = gcodec.getDoubleAfterFirstLetter(splitLine[1])
303 self.skirtTemperature = self.oldTemperatureInput
304 elif firstWord == 'M108':
305 self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1])
306 self.skirtFlowRate = self.oldFlowRate
307 elif firstWord == '(<supportLayer>)':
308 self.isSupportLayer = True
309 elif firstWord == '(</supportLayer>)':
310 self.isSupportLayer = False
312 def setSkirtFeedFlowTemperature(self):
313 'Set the skirt feed rate, flow rate and temperature to that of the next extrusion.'
314 isExtruderActive = self.isExtruderActive
315 isSupportLayer = self.isSupportLayer
316 for lineIndex in xrange(self.lineIndex, len(self.lines)):
317 line = self.lines[lineIndex]
318 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
319 firstWord = gcodec.getFirstWord(splitLine)
320 if firstWord == 'G1':
321 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
323 if not isSupportLayer:
325 elif firstWord == 'M101':
326 isExtruderActive = True
327 elif firstWord == 'M103':
328 isExtruderActive = False
329 elif firstWord == 'M104':
330 self.skirtTemperature = gcodec.getDoubleAfterFirstLetter(splitLine[1])
331 elif firstWord == 'M108':
332 self.skirtFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1])
333 elif firstWord == '(<supportLayer>)':
334 isSupportLayer = True
335 elif firstWord == '(</supportLayer>)':
336 isSupportLayer = False
338 def unifyLayer(self, loopCrossDictionary):
339 'Union the loopCrossDictionary with the unifiedLoop.'
340 euclidean.joinXIntersectionsTables(loopCrossDictionary.horizontalDictionary, self.unifiedLoop.horizontalDictionary)
341 euclidean.joinXIntersectionsTables(loopCrossDictionary.verticalDictionary, self.unifiedLoop.verticalDictionary)
345 'Display the skirt dialog.'
346 if len(sys.argv) > 1:
347 writeOutput(' '.join(sys.argv[1 :]))
349 settings.startMainLoopFromConstructor(getNewRepository())
351 if __name__ == '__main__':