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
36 #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.
39 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
40 from fabmetheus_utilities import archive
41 from fabmetheus_utilities import euclidean
42 from fabmetheus_utilities import gcodec
43 from fabmetheus_utilities import intercircle
44 from fabmetheus_utilities import settings
45 from skeinforge_application.skeinforge_utilities import skeinforge_craft
46 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
47 from skeinforge_application.skeinforge_utilities import skeinforge_profile
52 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
53 __date__ = '$Date: 2008/21/04 $'
54 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
57 def getCraftedText( fileName, text, jitterRepository = None ):
58 'Jitter a gcode linear move text.'
59 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), jitterRepository )
61 def getCraftedTextFromText( gcodeText, jitterRepository = None ):
62 'Jitter a gcode linear move text.'
63 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'jitter'):
65 if jitterRepository == None:
66 jitterRepository = settings.getReadRepository( JitterRepository() )
67 if not jitterRepository.activateJitter.value:
69 return JitterSkein().getCraftedGcode( jitterRepository, gcodeText )
71 def getJitteredLoop( jitterDistance, jitterLoop ):
72 'Get a jittered loop path.'
73 loopLength = euclidean.getLoopLength( jitterLoop )
77 jitterPosition = ( jitterDistance + 256.0 * loopLength ) % loopLength
78 while totalLength < jitterPosition and pointIndex < len( jitterLoop ):
79 firstPoint = jitterLoop[pointIndex]
80 secondPoint = jitterLoop[ (pointIndex + 1) % len( jitterLoop ) ]
82 lastLength = totalLength
83 totalLength += abs(firstPoint - secondPoint)
84 remainingLength = jitterPosition - lastLength
85 pointIndex = pointIndex % len( jitterLoop )
86 ultimateJitteredPoint = jitterLoop[pointIndex]
87 penultimateJitteredPointIndex = ( pointIndex + len( jitterLoop ) - 1 ) % len( jitterLoop )
88 penultimateJitteredPoint = jitterLoop[ penultimateJitteredPointIndex ]
89 segment = ultimateJitteredPoint - penultimateJitteredPoint
90 segmentLength = abs(segment)
91 originalOffsetLoop = euclidean.getAroundLoop( pointIndex, pointIndex, jitterLoop )
92 if segmentLength <= 0.0:
93 return originalOffsetLoop
94 newUltimatePoint = penultimateJitteredPoint + segment * remainingLength / segmentLength
95 return [newUltimatePoint] + originalOffsetLoop
97 def getNewRepository():
99 return JitterRepository()
101 def isLoopNumberEqual( betweenX, betweenXIndex, loopNumber ):
102 'Determine if the loop number is equal.'
103 if betweenXIndex >= len( betweenX ):
105 return betweenX[ betweenXIndex ].index == loopNumber
107 def writeOutput(fileName, shouldAnalyze=True):
108 'Jitter a gcode linear move file.'
109 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'jitter', shouldAnalyze)
112 class JitterRepository:
113 'A class to handle the jitter settings.'
115 'Set the default settings, execute title & settings fileName.'
116 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.jitter.html', self)
117 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Jitter', self, '')
118 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Jitter')
119 self.activateJitter = settings.BooleanSetting().getFromValue('Activate Jitter', self, False)
120 self.jitterOverEdgeWidth = settings.FloatSpin().getFromValue(1.0, 'Jitter Over Perimeter Width (ratio):', self, 3.0, 2.0)
121 self.executeTitle = 'Jitter'
124 'Jitter button has been clicked.'
125 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
126 for fileName in fileNames:
127 writeOutput(fileName)
131 'A class to jitter a skein of extrusions.'
134 self.distanceFeedRate = gcodec.DistanceFeedRate()
135 self.feedRateMinute = None
136 self.isLoopPerimeter = False
137 self.layerCount = settings.LayerCount()
138 self.layerGolden = 0.0
142 self.oldLocation = None
143 self.operatingFeedRatePerMinute = None
144 self.travelFeedRateMinute = None
146 def addGcodeFromThreadZ( self, thread, z ):
147 'Add a gcode thread to the output.'
149 self.addGcodeMovementZ( self.travelFeedRateMinute, thread[0], z )
151 print('zero length vertex positions array which was skipped over, this should never happen.')
154 self.distanceFeedRate.addLine('M101')
155 self.addGcodePathZ( self.feedRateMinute, thread[1 :], z )
157 def addGcodeMovementZ(self, feedRateMinute, point, z):
158 'Add a movement to the output.'
159 if feedRateMinute == None:
160 feedRateMinute = self.operatingFeedRatePerMinute
161 self.distanceFeedRate.addGcodeMovementZWithFeedRate(feedRateMinute, point, z)
163 def addGcodePathZ( self, feedRateMinute, path, z ):
164 'Add a gcode path, without modifying the extruder, to the output.'
166 self.addGcodeMovementZ(feedRateMinute, point, z)
168 def addTailoredLoopPath(self):
169 'Add a clipped and jittered loop path.'
170 loop = getJitteredLoop(self.layerJitter, self.loopPath.path[: -1])
171 loop = euclidean.getAwayPoints(loop, 0.2 * self.edgeWidth)
172 self.addGcodeFromThreadZ(loop + [loop[0]], self.loopPath.z)
175 def getCraftedGcode(self, jitterRepository, gcodeText):
176 'Parse gcode text and store the jitter gcode.'
177 if jitterRepository.jitterOverEdgeWidth.value == 0.0:
178 print('Warning, Jitter Over Perimeter Width is zero so thing will be done.')
180 self.lines = archive.getTextLines(gcodeText)
181 self.parseInitialization(jitterRepository)
182 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
183 self.parseLine(self.lines[self.lineIndex])
184 return self.distanceFeedRate.output.getvalue()
186 def parseInitialization( self, jitterRepository ):
187 'Parse gcode initialization and store the parameters.'
188 for self.lineIndex in xrange(len(self.lines)):
189 line = self.lines[self.lineIndex]
190 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
191 firstWord = gcodec.getFirstWord(splitLine)
192 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
193 if firstWord == '(</extruderInitialization>)':
194 self.distanceFeedRate.addTagBracketedProcedure('jitter')
196 elif firstWord == '(<operatingFeedRatePerSecond>':
197 self.operatingFeedRatePerMinute = 60.0 * float(splitLine[1])
198 elif firstWord == '(<edgeWidth>':
199 self.edgeWidth = float(splitLine[1])
200 self.jitter = jitterRepository.jitterOverEdgeWidth.value * self.edgeWidth
201 elif firstWord == '(<travelFeedRatePerSecond>':
202 self.travelFeedRateMinute = 60.0 * float(splitLine[1])
203 self.distanceFeedRate.addLine(line)
205 def parseLine(self, line):
206 'Parse a gcode line, jitter it and add it to the jitter skein.'
207 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
208 if len(splitLine) < 1:
210 firstWord = splitLine[0]
211 if firstWord == 'G1':
212 self.setFeedRateLocationLoopPath(line, splitLine)
213 if self.loopPath != None:
214 self.loopPath.path.append(self.oldLocation.dropAxis())
216 elif firstWord == 'M101':
217 if self.loopPath != None:
219 elif firstWord == 'M103':
220 self.isLoopPerimeter = False
221 if self.loopPath != None:
222 self.addTailoredLoopPath()
223 elif firstWord == '(<layer>':
224 self.layerCount.printProgressIncrement('jitter')
225 self.layerGolden = math.fmod(self.layerGolden + 0.61803398874989479, 1.0)
226 self.layerJitter = self.jitter * self.layerGolden - 0.5
227 elif firstWord == '(<loop>' or firstWord == '(<edge>':
228 self.isLoopPerimeter = True
229 self.distanceFeedRate.addLine(line)
231 def setFeedRateLocationLoopPath(self, line, splitLine):
232 'Set the feedRateMinute, oldLocation and loopPath.'
233 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
234 self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
235 if not self.isLoopPerimeter or self.loopPath != None:
237 for afterIndex in xrange(self.lineIndex + 1, len(self.lines)):
238 line = self.lines[afterIndex]
239 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
240 firstWord = gcodec.getFirstWord(splitLine)
241 if firstWord == 'G1' or firstWord == 'M103':
243 elif firstWord == 'M101':
244 self.loopPath = euclidean.PathZ(self.oldLocation.z)
249 'Display the jitter dialog.'
250 if len(sys.argv) > 1:
251 writeOutput(' '.join(sys.argv[1 :]))
253 settings.startMainLoopFromConstructor(getNewRepository())
255 if __name__ == '__main__':