chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / 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 #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.
37 import __init__
38
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
48 import math
49 import sys
50
51
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'
55
56
57 def getCraftedText( fileName, text, jitterRepository = None ):
58         'Jitter a gcode linear move text.'
59         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), jitterRepository )
60
61 def getCraftedTextFromText( gcodeText, jitterRepository = None ):
62         'Jitter a gcode linear move text.'
63         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'jitter'):
64                 return gcodeText
65         if jitterRepository == None:
66                 jitterRepository = settings.getReadRepository( JitterRepository() )
67         if not jitterRepository.activateJitter.value:
68                 return gcodeText
69         return JitterSkein().getCraftedGcode( jitterRepository, gcodeText )
70
71 def getJitteredLoop( jitterDistance, jitterLoop ):
72         'Get a jittered loop path.'
73         loopLength = euclidean.getLoopLength( jitterLoop )
74         lastLength = 0.0
75         pointIndex = 0
76         totalLength = 0.0
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 ) ]
81                 pointIndex += 1
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
96
97 def getNewRepository():
98         'Get new repository.'
99         return JitterRepository()
100
101 def isLoopNumberEqual( betweenX, betweenXIndex, loopNumber ):
102         'Determine if the loop number is equal.'
103         if betweenXIndex >= len( betweenX ):
104                 return False
105         return betweenX[ betweenXIndex ].index == loopNumber
106
107 def writeOutput(fileName, shouldAnalyze=True):
108         'Jitter a gcode linear move file.'
109         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'jitter', shouldAnalyze)
110
111
112 class JitterRepository:
113         'A class to handle the jitter settings.'
114         def __init__(self):
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'
122
123         def execute(self):
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)
128
129
130 class JitterSkein:
131         'A class to jitter a skein of extrusions.'
132         def __init__(self):
133                 'Initialize.'
134                 self.distanceFeedRate = gcodec.DistanceFeedRate()
135                 self.feedRateMinute = None
136                 self.isLoopPerimeter = False
137                 self.layerCount = settings.LayerCount()
138                 self.layerGolden = 0.0
139                 self.lineIndex = 0
140                 self.lines = None
141                 self.loopPath = None
142                 self.oldLocation = None
143                 self.operatingFeedRatePerMinute = None
144                 self.travelFeedRateMinute = None
145
146         def addGcodeFromThreadZ( self, thread, z ):
147                 'Add a gcode thread to the output.'
148                 if len(thread) > 0:
149                         self.addGcodeMovementZ( self.travelFeedRateMinute, thread[0], z )
150                 else:
151                         print('zero length vertex positions array which was skipped over, this should never happen.')
152                 if len(thread) < 2:
153                         return
154                 self.distanceFeedRate.addLine('M101')
155                 self.addGcodePathZ( self.feedRateMinute, thread[1 :], z )
156
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)
162
163         def addGcodePathZ( self, feedRateMinute, path, z ):
164                 'Add a gcode path, without modifying the extruder, to the output.'
165                 for point in path:
166                         self.addGcodeMovementZ(feedRateMinute, point, z)
167
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)
173                 self.loopPath = None
174
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.')
179                         return gcodeText
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()
185
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')
195                                 return
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)
204
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:
209                         return
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())
215                                 return
216                 elif firstWord == 'M101':
217                         if self.loopPath != None:
218                                 return
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)
230
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:
236                         return
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':
242                                 return
243                         elif firstWord == 'M101':
244                                 self.loopPath = euclidean.PathZ(self.oldLocation.z)
245                                 return
246
247
248 def main():
249         'Display the jitter dialog.'
250         if len(sys.argv) > 1:
251                 writeOutput(' '.join(sys.argv[1 :]))
252         else:
253                 settings.startMainLoopFromConstructor(getNewRepository())
254
255 if __name__ == '__main__':
256         main()