2 This page is in the table of contents.
3 Coil is a script to coil wire or filament around an object.
6 The default 'Activate Coil' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called.
9 ===Minimum Tool Distance===
10 Default is twenty millimeters.
12 Defines the minimum distance between the wire dispenser and the object. The 'Minimum Tool Distance' should be set to the maximum radius of the wire dispenser, times at least 1.3 to get a reasonable safety margin.
15 The following examples coil the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and coil.py.
18 This brings up the coil dialog.
20 > python coil.py Screw Holder Bottom.stl
21 The coil tool is parsing the file:
22 Screw Holder Bottom.stl
24 The coil tool has created the file:
25 Screw Holder Bottom_coil.gcode
29 from __future__ import absolute_import
30 #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.
33 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
34 from fabmetheus_utilities.geometry.solids import triangle_mesh
35 from fabmetheus_utilities.vector3 import Vector3
36 from fabmetheus_utilities import archive
37 from fabmetheus_utilities import euclidean
38 from fabmetheus_utilities import gcodec
39 from fabmetheus_utilities import intercircle
40 from fabmetheus_utilities import settings
41 from skeinforge_application.skeinforge_utilities import skeinforge_craft
42 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
43 from skeinforge_application.skeinforge_utilities import skeinforge_profile
48 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
49 __date__ = '$Date: 2008/21/04 $'
50 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
53 def getCraftedText( fileName, gcodeText = '', repository=None):
54 "Coil the file or gcodeText."
55 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
57 def getCraftedTextFromText(gcodeText, repository=None):
58 "Coil a gcode linear move gcodeText."
59 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'coil'):
61 if repository == None:
62 repository = settings.getReadRepository( CoilRepository() )
63 if not repository.activateCoil.value:
65 return CoilSkein().getCraftedGcode(gcodeText, repository)
67 def getNewRepository():
69 return CoilRepository()
71 def writeOutput(fileName, shouldAnalyze=True):
72 "Coil a gcode linear move file."
73 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'coil', shouldAnalyze)
77 "A class to handle the coil settings."
79 "Set the default settings, execute title & settings fileName."
80 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.coil.html', self )
81 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Coil', self, '')
82 self.activateCoil = settings.BooleanSetting().getFromValue('Activate Coil', self, True )
83 self.minimumToolDistance = settings.FloatSpin().getFromValue( 10.0, 'Minimum Tool Distance (millimeters):', self, 50.0, 20.0 )
84 self.executeTitle = 'Coil'
87 "Coil button has been clicked."
88 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
89 for fileName in fileNames:
95 "A class to coil a skein of extrusions."
97 self.boundaryLayers = []
98 self.distanceFeedRate = gcodec.DistanceFeedRate()
102 self.oldLocationComplex = complex()
103 self.shutdownLines = []
105 def addCoilLayer( self, boundaryLayers, radius, z ):
107 self.distanceFeedRate.addLine('(<layer> %s )' % z ) # Indicate that a new layer is starting.
108 self.distanceFeedRate.addLine('(<nestedRing>)')
110 for boundaryLayerIndex in xrange(1, len(boundaryLayers) - 1):
111 boundaryLayer = boundaryLayers[boundaryLayerIndex]
112 boundaryLayerBegin = boundaryLayers[boundaryLayerIndex - 1]
113 boundaryLayerEnd = boundaryLayers[boundaryLayerIndex + 1]
114 beginLocation = Vector3(0.0, 0.0, 0.5 * (boundaryLayerBegin.z + boundaryLayer.z))
115 outsetLoop = intercircle.getLargestInsetLoopFromLoop(boundaryLayer.loops[0], - radius)
116 self.addCoilToThread(beginLocation, 0.5 * (boundaryLayer.z + boundaryLayerEnd.z), outsetLoop, thread)
117 self.addGcodeFromThread(thread)
118 self.distanceFeedRate.addLine('(</nestedRing>)')
119 self.distanceFeedRate.addLine('(</layer>)')
121 def addCoilLayers(self):
122 "Add the coil layers."
123 numberOfLayersFloat = round( self.edgeWidth / self.layerHeight )
124 numberOfLayers = int( numberOfLayersFloat )
125 halfLayerThickness = 0.5 * self.layerHeight
126 startOutset = self.repository.minimumToolDistance.value + halfLayerThickness
127 startZ = self.boundaryLayers[0].z + halfLayerThickness
128 zRange = self.boundaryLayers[-1].z - self.boundaryLayers[0].z
131 zIncrement = zRange / numberOfLayersFloat
132 for layerIndex in xrange( numberOfLayers ):
133 settings.printProgressByNumber(layerIndex, numberOfLayers, 'coil')
134 boundaryLayers = self.boundaryLayers
135 if layerIndex % 2 == 1:
136 boundaryLayers = self.boundaryReverseLayers
137 radius = startOutset + layerIndex * self.layerHeight
138 z = startZ + layerIndex * zIncrement
139 self.addCoilLayer( boundaryLayers, radius, z )
141 def addCoilToThread(self, beginLocation, endZ, loop, thread):
142 "Add a coil to the thread."
145 loop = euclidean.getLoopStartingClosest(self.halfEdgeWidth, self.oldLocationComplex, loop)
146 length = euclidean.getLoopLength(loop)
151 for point in loop[1 :]:
152 pathLength += abs(point - oldPoint)
153 along = pathLength / length
154 z = (1.0 - along) * beginLocation.z + along * endZ
155 location = Vector3(point.real, point.imag, z)
156 thread.append(location)
158 self.oldLocationComplex = loop[-1]
160 def addGcodeFromThread( self, thread ):
161 "Add a thread to the output."
163 firstLocation = thread[0]
164 self.distanceFeedRate.addGcodeMovementZ( firstLocation.dropAxis(), firstLocation.z )
166 print("zero length vertex positions array which was skipped over, this should never happen")
168 print("thread of only one point in addGcodeFromThread in coil, this should never happen")
171 self.distanceFeedRate.addLine('M101') # Turn extruder on.
172 for location in thread[1 :]:
173 self.distanceFeedRate.addGcodeMovementZ( location.dropAxis(), location.z )
174 self.distanceFeedRate.addLine('M103') # Turn extruder off.
176 def getCraftedGcode(self, gcodeText, repository):
177 "Parse gcode text and store the coil gcode."
178 self.repository = repository
179 self.lines = archive.getTextLines(gcodeText)
180 self.parseInitialization()
181 self.parseBoundaries()
182 self.parseUntilLayer()
184 self.distanceFeedRate.addLines( self.shutdownLines )
185 return self.distanceFeedRate.output.getvalue()
187 def parseBoundaries(self):
188 "Parse the boundaries and add them to the boundary layers."
191 for line in self.lines[self.lineIndex :]:
192 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
193 firstWord = gcodec.getFirstWord(splitLine)
194 if len( self.shutdownLines ) > 0:
195 self.shutdownLines.append(line)
196 if firstWord == '(</boundaryPerimeter>)':
198 elif firstWord == '(<boundaryPoint>':
199 location = gcodec.getLocationFromSplitLine(None, splitLine)
200 if boundaryLoop == None:
202 boundaryLayer.loops.append(boundaryLoop)
203 boundaryLoop.append(location.dropAxis())
204 elif firstWord == '(<layer>':
205 boundaryLayer = euclidean.LoopLayer(float(splitLine[1]))
206 self.boundaryLayers.append(boundaryLayer)
207 elif firstWord == '(</crafting>)':
208 self.shutdownLines = [ line ]
209 for boundaryLayer in self.boundaryLayers:
210 if not euclidean.isWiddershins( boundaryLayer.loops[0] ):
211 boundaryLayer.loops[0].reverse()
212 self.boundaryReverseLayers = self.boundaryLayers[:]
213 self.boundaryReverseLayers.reverse()
215 def parseInitialization(self):
216 'Parse gcode initialization and store the parameters.'
217 for self.lineIndex in xrange(len(self.lines)):
218 line = self.lines[self.lineIndex]
219 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
220 firstWord = gcodec.getFirstWord(splitLine)
221 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
222 if firstWord == '(</extruderInitialization>)':
223 self.distanceFeedRate.addTagBracketedProcedure('coil')
225 elif firstWord == '(<layerHeight>':
226 self.layerHeight = float(splitLine[1])
227 elif firstWord == '(<edgeWidth>':
228 self.edgeWidth = float(splitLine[1])
229 self.halfEdgeWidth = 0.5 * self.edgeWidth
230 self.distanceFeedRate.addLine(line)
232 def parseUntilLayer(self):
233 "Parse until the layer line and add it to the coil skein."
234 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
235 line = self.lines[self.lineIndex]
236 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
237 firstWord = gcodec.getFirstWord(splitLine)
238 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
239 if firstWord == '(<layer>':
241 self.distanceFeedRate.addLine(line)
245 "Display the coil dialog."
246 if len(sys.argv) > 1:
247 writeOutput(' '.join(sys.argv[1 :]))
249 settings.startMainLoopFromConstructor(getNewRepository())
251 if __name__ == "__main__":