chiark / gitweb /
Add back the ultimaker platform, and made the platform mesh simpler.
[cura.git] / Cura / slice / cura_sf / skeinforge_application / skeinforge_plugins / craft_plugins / jitter.py
1 """
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.
4
5 The jitter manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Jitter
7
8 ==Operation==
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.
10
11 ==Settings==
12 ===Jitter Over Perimeter Width===
13 Default: 2
14
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.
16
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. 
18
19
20 ==Examples==
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.
22
23 > python jitter.py
24 This brings up the jitter dialog.
25
26 > python jitter.py Screw Holder Bottom.stl
27 The jitter tool is parsing the file:
28 Screw Holder Bottom.stl
29 ..
30 The jitter tool has created the file:
31 .. Screw Holder Bottom_jitter.gcode
32
33 """
34
35 from __future__ import absolute_import
36
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
45 import math
46 import sys
47
48
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'
52
53
54 def getCraftedText( fileName, text, jitterRepository = None ):
55         'Jitter a gcode linear move text.'
56         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), jitterRepository )
57
58 def getCraftedTextFromText( gcodeText, jitterRepository = None ):
59         'Jitter a gcode linear move text.'
60         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'jitter'):
61                 return gcodeText
62         if jitterRepository == None:
63                 jitterRepository = settings.getReadRepository( JitterRepository() )
64         if not jitterRepository.activateJitter.value:
65                 return gcodeText
66         return JitterSkein().getCraftedGcode( jitterRepository, gcodeText )
67
68 def getJitteredLoop( jitterDistance, jitterLoop ):
69         'Get a jittered loop path.'
70         loopLength = euclidean.getLoopLength( jitterLoop )
71         lastLength = 0.0
72         pointIndex = 0
73         totalLength = 0.0
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 ) ]
78                 pointIndex += 1
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
93
94 def getNewRepository():
95         'Get new repository.'
96         return JitterRepository()
97
98 def isLoopNumberEqual( betweenX, betweenXIndex, loopNumber ):
99         'Determine if the loop number is equal.'
100         if betweenXIndex >= len( betweenX ):
101                 return False
102         return betweenX[ betweenXIndex ].index == loopNumber
103
104 def writeOutput(fileName, shouldAnalyze=True):
105         'Jitter a gcode linear move file.'
106         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'jitter', shouldAnalyze)
107
108
109 class JitterRepository(object):
110         'A class to handle the jitter settings.'
111         def __init__(self):
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'
119
120         def execute(self):
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)
125
126
127 class JitterSkein(object):
128         'A class to jitter a skein of extrusions.'
129         def __init__(self):
130                 'Initialize.'
131                 self.distanceFeedRate = gcodec.DistanceFeedRate()
132                 self.feedRateMinute = None
133                 self.isLoopPerimeter = False
134                 self.layerCount = settings.LayerCount()
135                 self.layerGolden = 0.0
136                 self.lineIndex = 0
137                 self.lines = None
138                 self.loopPath = None
139                 self.oldLocation = None
140                 self.operatingFeedRatePerMinute = None
141                 self.travelFeedRateMinute = None
142
143         def addGcodeFromThreadZ( self, thread, z ):
144                 'Add a gcode thread to the output.'
145                 if len(thread) > 0:
146                         self.addGcodeMovementZ( self.travelFeedRateMinute, thread[0], z )
147                 else:
148                         print('zero length vertex positions array which was skipped over, this should never happen.')
149                 if len(thread) < 2:
150                         return
151                 self.distanceFeedRate.addLine('M101')
152                 self.addGcodePathZ( self.feedRateMinute, thread[1 :], z )
153
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)
159
160         def addGcodePathZ( self, feedRateMinute, path, z ):
161                 'Add a gcode path, without modifying the extruder, to the output.'
162                 for point in path:
163                         self.addGcodeMovementZ(feedRateMinute, point, z)
164
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)
170                 self.loopPath = None
171
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.')
176                         return gcodeText
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()
182
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')
192                                 return
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)
201
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:
206                         return
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())
212                                 return
213                 elif firstWord == 'M101':
214                         if self.loopPath != None:
215                                 return
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)
227
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:
233                         return
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':
239                                 return
240                         elif firstWord == 'M101':
241                                 self.loopPath = euclidean.PathZ(self.oldLocation.z)
242                                 return
243
244
245 def main():
246         'Display the jitter dialog.'
247         if len(sys.argv) > 1:
248                 writeOutput(' '.join(sys.argv[1 :]))
249         else:
250                 settings.startMainLoopFromConstructor(getNewRepository())
251
252 if __name__ == '__main__':
253         main()