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 if len(self.outsetLoops) < 1 or len(self.outsetLoops[0]) < 1:
176 self.setSkirtFeedFlowTemperature()
177 self.distanceFeedRate.addLine('(<skirt>)')
178 oldTemperature = self.oldTemperatureInput
179 self.addTemperatureLineIfDifferent(self.skirtTemperature)
180 self.addFlowRate(self.skirtFlowRate)
181 for outsetLoop in self.outsetLoops:
182 closedLoop = outsetLoop + [outsetLoop[0]]
183 self.distanceFeedRate.addGcodeFromFeedRateThreadZ(self.feedRateMinute, closedLoop, self.travelFeedRateMinute, z)
184 self.addFlowRate(self.oldFlowRate)
185 self.addTemperatureLineIfDifferent(oldTemperature)
186 self.distanceFeedRate.addLine('(</skirt>)')
188 def addTemperatureLineIfDifferent(self, temperature):
189 'Add a line of temperature if different.'
190 if temperature == None or temperature == self.oldTemperatureInput:
192 self.distanceFeedRate.addLine('M104 S' + euclidean.getRoundedToThreePlaces(temperature))
193 self.oldTemperatureInput = temperature
195 def createSegmentDictionaries(self, loopCrossDictionary):
196 'Create horizontal and vertical segment dictionaries.'
197 loopCrossDictionary.horizontalDictionary = self.getHorizontalXIntersectionsTable(loopCrossDictionary.loop)
198 flippedLoop = euclidean.getDiagonalFlippedLoop(loopCrossDictionary.loop)
199 loopCrossDictionary.verticalDictionary = self.getHorizontalXIntersectionsTable(flippedLoop)
201 def createSkirtLoops(self):
202 'Create the skirt loops.'
203 points = euclidean.getPointsByHorizontalDictionary(self.edgeWidth, self.unifiedLoop.horizontalDictionary)
204 points += euclidean.getPointsByVerticalDictionary(self.edgeWidth, self.unifiedLoop.verticalDictionary)
205 loops = triangle_mesh.getDescendingAreaOrientedLoops(points, points, 2.5 * self.edgeWidth)
206 outerLoops = getOuterLoops(loops)
207 self.outsetLoops = []
208 for i in xrange(self.repository.skirtLineCount.value, 0, -1):
209 outsetLoops = intercircle.getInsetSeparateLoopsFromLoops(outerLoops, -self.skirtOutset - i * self.edgeWidth)
210 outsetLoops = getOuterLoops(outsetLoops)
211 if self.repository.convex.value:
212 outsetLoops = [euclidean.getLoopConvex(euclidean.getConcatenatedList(outsetLoops))]
213 self.outsetLoops.extend(outsetLoops)
215 def getCraftedGcode(self, gcodeText, repository):
216 'Parse gcode text and store the skirt gcode.'
217 self.repository = repository
218 self.lines = archive.getTextLines(gcodeText)
219 self.parseInitialization()
220 self.parseBoundaries()
221 self.createSkirtLoops()
222 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
223 line = self.lines[self.lineIndex]
225 return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue())
227 def getHorizontalXIntersectionsTable(self, loop):
228 'Get the horizontal x intersections table from the loop.'
229 horizontalXIntersectionsTable = {}
230 euclidean.addXIntersectionsFromLoopForTable(loop, horizontalXIntersectionsTable, self.edgeWidth)
231 return horizontalXIntersectionsTable
233 def parseBoundaries(self):
234 'Parse the boundaries and union them.'
235 self.createSegmentDictionaries(self.unifiedLoop)
236 if self.repository.layersTo.value < 1:
238 loopCrossDictionary = None
240 for line in self.lines[self.lineIndex :]:
241 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
242 firstWord = gcodec.getFirstWord(splitLine)
243 if firstWord == '(</boundaryPerimeter>)' or firstWord == '(</raftPerimeter>)':
244 self.createSegmentDictionaries(loopCrossDictionary)
245 self.unifyLayer(loopCrossDictionary)
246 loopCrossDictionary = None
247 elif firstWord == '(<boundaryPoint>' or firstWord == '(<raftPoint>':
248 location = gcodec.getLocationFromSplitLine(None, splitLine)
249 if loopCrossDictionary == None:
250 loopCrossDictionary = LoopCrossDictionary()
251 loopCrossDictionary.loop.append(location.dropAxis())
252 elif firstWord == '(<layer>':
254 if layerIndex > self.repository.layersTo.value:
256 settings.printProgress(layerIndex, 'skirt')
258 def parseInitialization(self):
259 'Parse gcode initialization and store the parameters.'
260 for self.lineIndex in xrange(len(self.lines)):
261 line = self.lines[self.lineIndex]
262 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
263 firstWord = gcodec.getFirstWord(splitLine)
264 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
265 if firstWord == '(</extruderInitialization>)':
266 self.distanceFeedRate.addTagBracketedProcedure('skirt')
268 elif firstWord == '(<objectNextLayersTemperature>':
269 self.oldTemperatureInput = float(splitLine[1])
270 self.skirtTemperature = self.oldTemperatureInput
271 elif firstWord == '(<operatingFeedRatePerSecond>':
272 self.feedRateMinute = 60.0 * float(splitLine[1])
273 elif firstWord == '(<operatingFlowRate>':
274 self.oldFlowRate = float(splitLine[1])
275 self.skirtFlowRate = self.oldFlowRate
276 elif firstWord == '(<edgeWidth>':
277 self.edgeWidth = float(splitLine[1])
278 self.skirtOutset = self.repository.gapWidth.value + 0.5 * self.edgeWidth
279 self.distanceFeedRate.addTagRoundedLine('skirtOutset', self.skirtOutset)
280 elif firstWord == '(<travelFeedRatePerSecond>':
281 self.travelFeedRateMinute = 60.0 * float(splitLine[1])
282 self.distanceFeedRate.addLine(line)
284 def parseLine(self, line):
285 'Parse a gcode line and add it to the skirt skein.'
286 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
287 if len(splitLine) < 1:
289 firstWord = splitLine[0]
290 if firstWord == '(<raftPerimeter>)' or firstWord == '(</raftPerimeter>)' or firstWord == '(<raftPoint>':
292 self.distanceFeedRate.addLine(line)
293 if firstWord == 'G1':
294 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
295 elif firstWord == '(<layer>':
297 if self.layerIndex < self.repository.layersTo.value:
298 self.addSkirt(float(splitLine[1]))
299 elif firstWord == 'M101':
300 self.isExtruderActive = True
301 elif firstWord == 'M103':
302 self.isExtruderActive = False
303 elif firstWord == 'M104':
304 self.oldTemperatureInput = gcodec.getDoubleAfterFirstLetter(splitLine[1])
305 self.skirtTemperature = self.oldTemperatureInput
306 elif firstWord == 'M108':
307 self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1])
308 self.skirtFlowRate = self.oldFlowRate
309 elif firstWord == '(<supportLayer>)':
310 self.isSupportLayer = True
311 elif firstWord == '(</supportLayer>)':
312 self.isSupportLayer = False
314 def setSkirtFeedFlowTemperature(self):
315 'Set the skirt feed rate, flow rate and temperature to that of the next extrusion.'
316 isExtruderActive = self.isExtruderActive
317 isSupportLayer = self.isSupportLayer
318 for lineIndex in xrange(self.lineIndex, len(self.lines)):
319 line = self.lines[lineIndex]
320 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
321 firstWord = gcodec.getFirstWord(splitLine)
322 if firstWord == 'G1':
323 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
325 if not isSupportLayer:
327 elif firstWord == 'M101':
328 isExtruderActive = True
329 elif firstWord == 'M103':
330 isExtruderActive = False
331 elif firstWord == 'M104':
332 self.skirtTemperature = gcodec.getDoubleAfterFirstLetter(splitLine[1])
333 elif firstWord == 'M108':
334 self.skirtFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1])
335 elif firstWord == '(<supportLayer>)':
336 isSupportLayer = True
337 elif firstWord == '(</supportLayer>)':
338 isSupportLayer = False
340 def unifyLayer(self, loopCrossDictionary):
341 'Union the loopCrossDictionary with the unifiedLoop.'
342 euclidean.joinXIntersectionsTables(loopCrossDictionary.horizontalDictionary, self.unifiedLoop.horizontalDictionary)
343 euclidean.joinXIntersectionsTables(loopCrossDictionary.verticalDictionary, self.unifiedLoop.verticalDictionary)
347 'Display the skirt dialog.'
348 if len(sys.argv) > 1:
349 writeOutput(' '.join(sys.argv[1 :]))
351 settings.startMainLoopFromConstructor(getNewRepository())
353 if __name__ == '__main__':