2 This page is in the table of contents.
3 This craft tool jitters the loop end position to a different place on each layer to prevent a ridge from being created on the side of the object.
5 The jitter manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Jitter
9 The default 'Activate Jitter' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done.
12 ===Jitter Over Perimeter Width===
15 Defines the amount the loop ends will be jittered over the edge width. A high value means the loops will start all over the place and a low value means loops will start at roughly the same place on each layer.
17 For example if you turn jitter off and print a cube every outside shell on the cube will start from exactly the same point so you will have a visible "mark/line/seam" on the side of the cube. Using the jitter tool you move that start point around hence you avoid that visible seam.
21 The following examples jitter the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and jitter.py.
24 This brings up the jitter dialog.
26 > python jitter.py Screw Holder Bottom.stl
27 The jitter tool is parsing the file:
28 Screw Holder Bottom.stl
30 The jitter tool has created the file:
31 .. Screw Holder Bottom_jitter.gcode
35 from __future__ import absolute_import
37 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
38 from fabmetheus_utilities import archive
39 from fabmetheus_utilities import euclidean
40 from fabmetheus_utilities import gcodec
41 from fabmetheus_utilities import settings
42 from skeinforge_application.skeinforge_utilities import skeinforge_craft
43 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
44 from skeinforge_application.skeinforge_utilities import skeinforge_profile
49 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
50 __date__ = '$Date: 2008/21/04 $'
51 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
54 def getCraftedText( fileName, text, jitterRepository = None ):
55 'Jitter a gcode linear move text.'
56 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), jitterRepository )
58 def getCraftedTextFromText( gcodeText, jitterRepository = None ):
59 'Jitter a gcode linear move text.'
60 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'jitter'):
62 if jitterRepository == None:
63 jitterRepository = settings.getReadRepository( JitterRepository() )
64 if not jitterRepository.activateJitter.value:
66 return JitterSkein().getCraftedGcode( jitterRepository, gcodeText )
68 def getJitteredLoop( jitterDistance, jitterLoop ):
69 'Get a jittered loop path.'
70 loopLength = euclidean.getLoopLength( jitterLoop )
74 jitterPosition = ( jitterDistance + 256.0 * loopLength ) % loopLength
75 while totalLength < jitterPosition and pointIndex < len( jitterLoop ):
76 firstPoint = jitterLoop[pointIndex]
77 secondPoint = jitterLoop[ (pointIndex + 1) % len( jitterLoop ) ]
79 lastLength = totalLength
80 totalLength += abs(firstPoint - secondPoint)
81 remainingLength = jitterPosition - lastLength
82 pointIndex = pointIndex % len( jitterLoop )
83 ultimateJitteredPoint = jitterLoop[pointIndex]
84 penultimateJitteredPointIndex = ( pointIndex + len( jitterLoop ) - 1 ) % len( jitterLoop )
85 penultimateJitteredPoint = jitterLoop[ penultimateJitteredPointIndex ]
86 segment = ultimateJitteredPoint - penultimateJitteredPoint
87 segmentLength = abs(segment)
88 originalOffsetLoop = euclidean.getAroundLoop( pointIndex, pointIndex, jitterLoop )
89 if segmentLength <= 0.0:
90 return originalOffsetLoop
91 newUltimatePoint = penultimateJitteredPoint + segment * remainingLength / segmentLength
92 return [newUltimatePoint] + originalOffsetLoop
94 def getNewRepository():
96 return JitterRepository()
98 def isLoopNumberEqual( betweenX, betweenXIndex, loopNumber ):
99 'Determine if the loop number is equal.'
100 if betweenXIndex >= len( betweenX ):
102 return betweenX[ betweenXIndex ].index == loopNumber
104 def writeOutput(fileName, shouldAnalyze=True):
105 'Jitter a gcode linear move file.'
106 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'jitter', shouldAnalyze)
109 class JitterRepository(object):
110 'A class to handle the jitter settings.'
112 'Set the default settings, execute title & settings fileName.'
113 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.jitter.html', self)
114 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Jitter', self, '')
115 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Jitter')
116 self.activateJitter = settings.BooleanSetting().getFromValue('Activate Jitter', self, False)
117 self.jitterOverEdgeWidth = settings.FloatSpin().getFromValue(1.0, 'Jitter Over Perimeter Width (ratio):', self, 3.0, 2.0)
118 self.executeTitle = 'Jitter'
121 'Jitter button has been clicked.'
122 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
123 for fileName in fileNames:
124 writeOutput(fileName)
127 class JitterSkein(object):
128 'A class to jitter a skein of extrusions.'
131 self.distanceFeedRate = gcodec.DistanceFeedRate()
132 self.feedRateMinute = None
133 self.isLoopPerimeter = False
134 self.layerCount = settings.LayerCount()
135 self.layerGolden = 0.0
139 self.oldLocation = None
140 self.operatingFeedRatePerMinute = None
141 self.travelFeedRateMinute = None
143 def addGcodeFromThreadZ( self, thread, z ):
144 'Add a gcode thread to the output.'
146 self.addGcodeMovementZ( self.travelFeedRateMinute, thread[0], z )
148 print('zero length vertex positions array which was skipped over, this should never happen.')
151 self.distanceFeedRate.addLine('M101')
152 self.addGcodePathZ( self.feedRateMinute, thread[1 :], z )
154 def addGcodeMovementZ(self, feedRateMinute, point, z):
155 'Add a movement to the output.'
156 if feedRateMinute == None:
157 feedRateMinute = self.operatingFeedRatePerMinute
158 self.distanceFeedRate.addGcodeMovementZWithFeedRate(feedRateMinute, point, z)
160 def addGcodePathZ( self, feedRateMinute, path, z ):
161 'Add a gcode path, without modifying the extruder, to the output.'
163 self.addGcodeMovementZ(feedRateMinute, point, z)
165 def addTailoredLoopPath(self):
166 'Add a clipped and jittered loop path.'
167 loop = getJitteredLoop(self.layerJitter, self.loopPath.path[: -1])
168 loop = euclidean.getAwayPoints(loop, 0.2 * self.edgeWidth)
169 self.addGcodeFromThreadZ(loop + [loop[0]], self.loopPath.z)
172 def getCraftedGcode(self, jitterRepository, gcodeText):
173 'Parse gcode text and store the jitter gcode.'
174 if jitterRepository.jitterOverEdgeWidth.value == 0.0:
175 print('Warning, Jitter Over Perimeter Width is zero so thing will be done.')
177 self.lines = archive.getTextLines(gcodeText)
178 self.parseInitialization(jitterRepository)
179 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
180 self.parseLine(self.lines[self.lineIndex])
181 return self.distanceFeedRate.output.getvalue()
183 def parseInitialization( self, jitterRepository ):
184 'Parse gcode initialization and store the parameters.'
185 for self.lineIndex in xrange(len(self.lines)):
186 line = self.lines[self.lineIndex]
187 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
188 firstWord = gcodec.getFirstWord(splitLine)
189 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
190 if firstWord == '(</extruderInitialization>)':
191 self.distanceFeedRate.addTagBracketedProcedure('jitter')
193 elif firstWord == '(<operatingFeedRatePerSecond>':
194 self.operatingFeedRatePerMinute = 60.0 * float(splitLine[1])
195 elif firstWord == '(<edgeWidth>':
196 self.edgeWidth = float(splitLine[1])
197 self.jitter = jitterRepository.jitterOverEdgeWidth.value * self.edgeWidth
198 elif firstWord == '(<travelFeedRatePerSecond>':
199 self.travelFeedRateMinute = 60.0 * float(splitLine[1])
200 self.distanceFeedRate.addLine(line)
202 def parseLine(self, line):
203 'Parse a gcode line, jitter it and add it to the jitter skein.'
204 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
205 if len(splitLine) < 1:
207 firstWord = splitLine[0]
208 if firstWord == 'G1':
209 self.setFeedRateLocationLoopPath(line, splitLine)
210 if self.loopPath != None:
211 self.loopPath.path.append(self.oldLocation.dropAxis())
213 elif firstWord == 'M101':
214 if self.loopPath != None:
216 elif firstWord == 'M103':
217 self.isLoopPerimeter = False
218 if self.loopPath != None:
219 self.addTailoredLoopPath()
220 elif firstWord == '(<layer>':
221 self.layerCount.printProgressIncrement('jitter')
222 self.layerGolden = math.fmod(self.layerGolden + 0.61803398874989479, 1.0)
223 self.layerJitter = self.jitter * self.layerGolden - 0.5
224 elif firstWord == '(<loop>' or firstWord == '(<edge>':
225 self.isLoopPerimeter = True
226 self.distanceFeedRate.addLine(line)
228 def setFeedRateLocationLoopPath(self, line, splitLine):
229 'Set the feedRateMinute, oldLocation and loopPath.'
230 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
231 self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
232 if not self.isLoopPerimeter or self.loopPath != None:
234 for afterIndex in xrange(self.lineIndex + 1, len(self.lines)):
235 line = self.lines[afterIndex]
236 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
237 firstWord = gcodec.getFirstWord(splitLine)
238 if firstWord == 'G1' or firstWord == 'M103':
240 elif firstWord == 'M101':
241 self.loopPath = euclidean.PathZ(self.oldLocation.z)
246 'Display the jitter dialog.'
247 if len(sys.argv) > 1:
248 writeOutput(' '.join(sys.argv[1 :]))
250 settings.startMainLoopFromConstructor(getNewRepository())
252 if __name__ == '__main__':