chiark / gitweb /
f49760f6eb7cd1cf3396812240aac85adc09d610
[cura.git] / Cura / skeinforge_application / skeinforge_plugins / craft_plugins / coil.py
1 """
2 This page is in the table of contents.
3 Coil is a script to coil wire or filament around an object.
4
5 ==Operation==
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.
7
8 ==Settings==
9 ===Minimum Tool Distance===
10 Default is twenty millimeters.
11
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.
13
14 ==Examples==
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.
16
17 > python coil.py
18 This brings up the coil dialog.
19
20 > python coil.py Screw Holder Bottom.stl
21 The coil tool is parsing the file:
22 Screw Holder Bottom.stl
23 ..
24 The coil tool has created the file:
25 Screw Holder Bottom_coil.gcode
26
27 """
28
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.
31 import __init__
32
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
44 import os
45 import sys
46
47
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'
51
52
53 def getCraftedText( fileName, gcodeText = '', repository=None):
54         "Coil the file or gcodeText."
55         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
56
57 def getCraftedTextFromText(gcodeText, repository=None):
58         "Coil a gcode linear move gcodeText."
59         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'coil'):
60                 return gcodeText
61         if repository == None:
62                 repository = settings.getReadRepository( CoilRepository() )
63         if not repository.activateCoil.value:
64                 return gcodeText
65         return CoilSkein().getCraftedGcode(gcodeText, repository)
66
67 def getNewRepository():
68         'Get new repository.'
69         return CoilRepository()
70
71 def writeOutput(fileName, shouldAnalyze=True):
72         "Coil a gcode linear move file."
73         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'coil', shouldAnalyze)
74
75
76 class CoilRepository:
77         "A class to handle the coil settings."
78         def __init__(self):
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'
85
86         def execute(self):
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:
90                         writeOutput(fileName)
91
92
93
94 class CoilSkein:
95         "A class to coil a skein of extrusions."
96         def __init__(self):
97                 self.boundaryLayers = []
98                 self.distanceFeedRate = gcodec.DistanceFeedRate()
99                 self.edgeWidth = 0.6
100                 self.lineIndex = 0
101                 self.lines = None
102                 self.oldLocationComplex = complex()
103                 self.shutdownLines = []
104
105         def addCoilLayer( self, boundaryLayers, radius, z ):
106                 "Add a coil layer."
107                 self.distanceFeedRate.addLine('(<layer> %s )' % z ) # Indicate that a new layer is starting.
108                 self.distanceFeedRate.addLine('(<nestedRing>)')
109                 thread = []
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>)')
120
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
129                 zIncrement = 0.0
130                 if zRange >= 0.0:
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 )
140
141         def addCoilToThread(self, beginLocation, endZ, loop, thread):
142                 "Add a coil to the thread."
143                 if len(loop) < 1:
144                         return
145                 loop = euclidean.getLoopStartingClosest(self.halfEdgeWidth, self.oldLocationComplex, loop)
146                 length = euclidean.getLoopLength(loop)
147                 if length <= 0.0:
148                         return
149                 oldPoint = loop[0]
150                 pathLength = 0.0
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)
157                         oldPoint = point
158                 self.oldLocationComplex = loop[-1]
159
160         def addGcodeFromThread( self, thread ):
161                 "Add a thread to the output."
162                 if len(thread) > 0:
163                         firstLocation = thread[0]
164                         self.distanceFeedRate.addGcodeMovementZ( firstLocation.dropAxis(), firstLocation.z )
165                 else:
166                         print("zero length vertex positions array which was skipped over, this should never happen")
167                 if len(thread) < 2:
168                         print("thread of only one point in addGcodeFromThread in coil, this should never happen")
169                         print(thread)
170                         return
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.
175
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()
183                 self.addCoilLayers()
184                 self.distanceFeedRate.addLines( self.shutdownLines )
185                 return self.distanceFeedRate.output.getvalue()
186
187         def parseBoundaries(self):
188                 "Parse the boundaries and add them to the boundary layers."
189                 boundaryLoop = None
190                 boundaryLayer = None
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>)':
197                                 boundaryLoop = None
198                         elif firstWord == '(<boundaryPoint>':
199                                 location = gcodec.getLocationFromSplitLine(None, splitLine)
200                                 if boundaryLoop == None:
201                                         boundaryLoop = []
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()
214
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')
224                                 return
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)
231
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>':
240                                 return
241                         self.distanceFeedRate.addLine(line)
242
243
244 def main():
245         "Display the coil dialog."
246         if len(sys.argv) > 1:
247                 writeOutput(' '.join(sys.argv[1 :]))
248         else:
249                 settings.startMainLoopFromConstructor(getNewRepository())
250
251 if __name__ == "__main__":
252         main()