chiark / gitweb /
Move SF into its own directory, to seperate SF and Cura. Rename newui to gui.
[cura.git] / Cura / cura_sf / skeinforge_application / skeinforge_plugins / craft_plugins / splodge.py
1 """
2 This page is in the table of contents.
3 Splodge turns the extruder on just before the start of a thread.  This is to give the extrusion a bit anchoring at the beginning.
4
5 The splodge manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Splodge
7
8 ==Operation==
9 The default 'Activate Splodge' checkbox is on.  When it is on, the functions described below will work, when it is off, the functions will not be called.
10
11 ==Settings==
12 ===Initial===
13 ====Initial Lift over Extra Thickness====
14 Default is one.
15
16 Defines the amount the extruder will be lifted over the extra thickness of the initial splodge thread.  The higher the ratio, the more the extruder will be lifted over the splodge, if the ratio is too low the extruder might plow through the splodge extrusion.
17
18 ====Initial Splodge Feed Rate====
19 Default is one millimeter per second.
20
21 Defines the feed rate at which the initial extra extrusion will be added.  With the default feed rate, the splodge will be added slower so it will be thicker than the regular extrusion.
22
23 ====Initial Splodge Quantity Length====
24 Default is thirty millimeters.
25
26 Defines the quantity length of extra extrusion at the operating feed rate that will be added to the initial thread.  If a splodge quantity length is smaller than 0.1 times the edge width, no splodge of that type will be added.
27
28 ===Operating===
29 ====Operating Lift over Extra Thickness====
30 Default is one.
31
32 Defines the amount the extruder will be lifted over the extra thickness of the operating splodge thread.
33
34 ====Operating Splodge Feed Rate====
35 Default is one millimeter per second.
36
37 Defines the feed rate at which the next extra extrusions will be added.
38
39 ====Operating Splodge Quantity Length====
40 Default is thirty millimeters.
41
42 Defines the quantity length of extra extrusion at the operating feed rate that will be added for the next threads.
43
44 ==Examples==
45 The following examples splodge the file Screw Holder Bottom.stl.  The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and splodge.py.
46
47 > python splodge.py
48 This brings up the splodge dialog.
49
50 > python splodge.py Screw Holder Bottom.stl
51 The splodge tool is parsing the file:
52 Screw Holder Bottom.stl
53 ..
54 The splodge tool has created the file:
55 .. Screw Holder Bottom_splodge.gcode
56
57 """
58
59 from __future__ import absolute_import
60 #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.
61 import __init__
62
63 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
64 from fabmetheus_utilities import archive
65 from fabmetheus_utilities import euclidean
66 from fabmetheus_utilities import gcodec
67 from fabmetheus_utilities import settings
68 from skeinforge_application.skeinforge_utilities import skeinforge_craft
69 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
70 from skeinforge_application.skeinforge_utilities import skeinforge_profile
71 import math
72 import sys
73
74
75 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
76 __date__ = '$Date: 2008/21/04 $'
77 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
78
79
80 def getCraftedText( fileName, text, splodgeRepository = None ):
81         "Splodge a gcode linear move file or text."
82         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), splodgeRepository )
83
84 def getCraftedTextFromText( gcodeText, splodgeRepository = None ):
85         "Splodge a gcode linear move text."
86         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'splodge'):
87                 return gcodeText
88         if splodgeRepository == None:
89                 splodgeRepository = settings.getReadRepository( SplodgeRepository() )
90         if not splodgeRepository.activateSplodge.value:
91                 return gcodeText
92         return SplodgeSkein().getCraftedGcode( gcodeText, splodgeRepository )
93
94 def getNewRepository():
95         'Get new repository.'
96         return SplodgeRepository()
97
98 def writeOutput(fileName, shouldAnalyze=True):
99         "Splodge a gcode linear move file."
100         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'splodge', shouldAnalyze)
101
102
103 class SplodgeRepository:
104         "A class to handle the splodge settings."
105         def __init__(self):
106                 "Set the default settings, execute title & settings fileName."
107                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.splodge.html', self )
108                 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Splodge', self, '')
109                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Splodge')
110                 self.activateSplodge = settings.BooleanSetting().getFromValue('Activate Splodge', self, False )
111                 settings.LabelSeparator().getFromRepository(self)
112                 settings.LabelDisplay().getFromName('- Initial -', self )
113                 self.initialLiftOverExtraThickness = settings.FloatSpin().getFromValue( 0.5, 'Initial Lift over Extra Thickness (ratio):', self, 1.5, 1.0 )
114                 self.initialSplodgeFeedRate = settings.FloatSpin().getFromValue( 0.4, 'Initial Splodge Feed Rate (mm/s):', self, 2.4, 1.0 )
115                 self.initialSplodgeQuantityLength = settings.FloatSpin().getFromValue( 10.0, 'Initial Splodge Quantity Length (millimeters):', self, 90.0, 30.0 )
116                 settings.LabelSeparator().getFromRepository(self)
117                 settings.LabelDisplay().getFromName('- Operating -', self )
118                 self.operatingLiftOverExtraThickness = settings.FloatSpin().getFromValue( 0.5, 'Operating Lift over Extra Thickness (ratio):', self, 1.5, 1.0 )
119                 self.operatingSplodgeFeedRate = settings.FloatSpin().getFromValue( 0.4, 'Operating Splodge Feed Rate (mm/s):', self, 2.4, 1.0 )
120                 self.operatingSplodgeQuantityLength = settings.FloatSpin().getFromValue(0.4, 'Operating Splodge Quantity Length (millimeters):', self, 2.4, 1.0)
121                 settings.LabelSeparator().getFromRepository(self)
122                 self.executeTitle = 'Splodge'
123
124         def execute(self):
125                 "Splodge button has been clicked."
126                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
127                 for fileName in fileNames:
128                         writeOutput(fileName)
129
130
131 class SplodgeSkein:
132         "A class to splodge a skein of extrusions."
133         def __init__(self):
134                 self.distanceFeedRate = gcodec.DistanceFeedRate()
135                 self.feedRateMinute = 961.0
136                 self.isExtruderActive = False
137                 self.hasInitialSplodgeBeenAdded = False
138                 self.isLastExtruderCommandActivate = False
139                 self.lastLineOutput = None
140                 self.lineIndex = 0
141                 self.lines = None
142                 self.oldLocation = None
143                 self.operatingFeedRatePerSecond = 15.0
144
145         def addLineUnlessIdentical(self, line):
146                 "Add a line, unless it is identical to the last line."
147                 if line == self.lastLineOutput:
148                         return
149                 self.lastLineOutput = line
150                 self.distanceFeedRate.addLine(line)
151
152         def addLineUnlessIdenticalReactivate(self, line):
153                 "Add a line, unless it is identical to the last line or another M101."
154                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
155                 if len(splitLine) < 1:
156                         return
157                 firstWord = splitLine[0]
158                 if firstWord == 'M101':
159                         if not self.isLastExtruderCommandActivate:
160                                 self.addLineUnlessIdentical(line)
161                                 self.isLastExtruderCommandActivate = True
162                         return
163                 if firstWord == 'M103':
164                         self.isLastExtruderCommandActivate = False
165                 self.addLineUnlessIdentical(line)
166
167         def getCraftedGcode( self, gcodeText, splodgeRepository ):
168                 "Parse gcode text and store the splodge gcode."
169                 self.lines = archive.getTextLines(gcodeText)
170                 self.setRotations()
171                 self.splodgeRepository = splodgeRepository
172                 self.parseInitialization( splodgeRepository )
173                 self.boundingRectangle = gcodec.BoundingRectangle().getFromGcodeLines( self.lines[self.lineIndex :], 0.5 * self.edgeWidth )
174                 self.initialSplodgeFeedRateMinute = 60.0 * splodgeRepository.initialSplodgeFeedRate.value
175                 self.initialStartupDistance = splodgeRepository.initialSplodgeQuantityLength.value * splodgeRepository.initialSplodgeFeedRate.value / self.operatingFeedRatePerSecond
176                 self.operatingSplodgeFeedRateMinute = 60.0 * splodgeRepository.operatingSplodgeFeedRate.value
177                 self.operatingStartupDistance = splodgeRepository.operatingSplodgeQuantityLength.value * splodgeRepository.operatingSplodgeFeedRate.value / self.operatingFeedRatePerSecond
178                 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
179                         line = self.lines[self.lineIndex]
180                         self.parseLine(line)
181                 return self.distanceFeedRate.output.getvalue()
182
183         def getInitialSplodgeLine( self, line, location ):
184                 "Add the initial splodge line."
185                 if not self.isJustBeforeExtrusion():
186                         return line
187                 self.hasInitialSplodgeBeenAdded = True
188                 if self.splodgeRepository.initialSplodgeQuantityLength.value < self.minimumQuantityLength:
189                         return line
190                 return self.getSplodgeLineGivenDistance( self.initialSplodgeFeedRateMinute, line, self.splodgeRepository.initialLiftOverExtraThickness.value, location, self.initialStartupDistance )
191
192         def getNextActiveLocationComplex(self):
193                 "Get the next active line."
194                 isActive = False
195                 for lineIndex in xrange( self.lineIndex + 1, len(self.lines) ):
196                         line = self.lines[lineIndex]
197                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
198                         firstWord = gcodec.getFirstWord(splitLine)
199                         if firstWord == 'M101':
200                                 isActive = True
201                         if firstWord == 'G1' and isActive:
202                                 return gcodec.getLocationFromSplitLine(self.oldLocation, splitLine).dropAxis()
203                 return None
204
205         def getOperatingSplodgeLine( self, line, location ):
206                 "Get the operating splodge line."
207                 if not self.isJustBeforeExtrusion():
208                         return line
209                 if self.splodgeRepository.operatingSplodgeQuantityLength.value < self.minimumQuantityLength:
210                         return line
211                 return self.getSplodgeLineGivenDistance( self.operatingSplodgeFeedRateMinute, line, self.splodgeRepository.operatingLiftOverExtraThickness.value, location, self.operatingStartupDistance )
212
213         def getSplodgeLine(self, line, location, splitLine):
214                 "Get splodged gcode line."
215                 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
216                 if self.hasInitialSplodgeBeenAdded:
217                         return self.getOperatingSplodgeLine(line, location)
218                 return self.getInitialSplodgeLine(line, location)
219
220         def getSplodgeLineGivenDistance( self, feedRateMinute, line, liftOverExtraThickness, location, startupDistance ):
221                 "Add the splodge line."
222                 locationComplex = location.dropAxis()
223                 relativeStartComplex = None
224                 nextLocationComplex = self.getNextActiveLocationComplex()
225                 if nextLocationComplex != None:
226                         if nextLocationComplex != locationComplex:
227                                 relativeStartComplex = locationComplex - nextLocationComplex
228                 if relativeStartComplex == None:
229                         relativeStartComplex = complex( 19.9, 9.9 )
230                         if self.oldLocation != None:
231                                 oldLocationComplex = self.oldLocation.dropAxis()
232                                 if oldLocationComplex != locationComplex:
233                                         relativeStartComplex = oldLocationComplex - locationComplex
234                 relativeStartComplex *= startupDistance / abs( relativeStartComplex )
235                 startComplex = self.getStartInsideBoundingRectangle( locationComplex, relativeStartComplex )
236                 feedRateMultiplier = feedRateMinute / self.operatingFeedRatePerSecond / 60.0
237                 splodgeLayerThickness = self.layerHeight / math.sqrt( feedRateMultiplier )
238                 extraLayerThickness = splodgeLayerThickness - self.layerHeight
239                 lift = extraLayerThickness * liftOverExtraThickness
240                 startLine = self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.feedRateMinute, startComplex, location.z + lift )
241                 self.addLineUnlessIdenticalReactivate( startLine )
242                 self.addLineUnlessIdenticalReactivate('M101')
243                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
244                 lineLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
245                 self.distanceFeedRate.addGcodeMovementZWithFeedRate( feedRateMinute, locationComplex, lineLocation.z + lift )
246                 return ''
247
248         def getStartInsideBoundingRectangle( self, locationComplex, relativeStartComplex ):
249                 "Get a start inside the bounding rectangle."
250                 startComplex = locationComplex + relativeStartComplex
251                 if self.boundingRectangle.isPointInside( startComplex ):
252                         return startComplex
253                 for rotation in self.rotations:
254                         rotatedRelativeStartComplex = relativeStartComplex * rotation
255                         startComplex = locationComplex + rotatedRelativeStartComplex
256                         if self.boundingRectangle.isPointInside( startComplex ):
257                                 return startComplex
258                 return startComplex
259
260         def isJustBeforeExtrusion(self):
261                 "Determine if activate command is before linear move command."
262                 for lineIndex in xrange(self.lineIndex + 1, len(self.lines)):
263                         line = self.lines[lineIndex]
264                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
265                         firstWord = gcodec.getFirstWord(splitLine)
266                         if firstWord == 'G1' or firstWord == 'M103':
267                                 return False
268                         if firstWord == 'M101':
269                                 return True
270                 return False
271
272         def parseInitialization( self, splodgeRepository ):
273                 'Parse gcode initialization and store the parameters.'
274                 for self.lineIndex in xrange(len(self.lines)):
275                         line = self.lines[self.lineIndex]
276                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
277                         firstWord = gcodec.getFirstWord(splitLine)
278                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
279                         if firstWord == '(</extruderInitialization>)':
280                                 self.addLineUnlessIdenticalReactivate(gcodec.getTagBracketedProcedure('splodge'))
281                                 return
282                         elif firstWord == '(<layerHeight>':
283                                 self.layerHeight = float(splitLine[1])
284                         elif firstWord == '(<operatingFeedRatePerSecond>':
285                                 self.operatingFeedRatePerSecond = float(splitLine[1])
286                         elif firstWord == '(<edgeWidth>':
287                                 self.edgeWidth = float(splitLine[1])
288                                 self.minimumQuantityLength = 0.1 * self.edgeWidth
289                         self.addLineUnlessIdenticalReactivate(line)
290
291         def parseLine(self, line):
292                 "Parse a gcode line and add it to the bevel gcode."
293                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
294                 if len(splitLine) < 1:
295                         return
296                 firstWord = splitLine[0]
297                 if firstWord == 'G1':
298                         location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
299                         line = self.getSplodgeLine(line, location, splitLine)
300                         self.oldLocation = location
301                 elif firstWord == 'M101':
302                         self.isExtruderActive = True
303                 elif firstWord == 'M103':
304                         self.isExtruderActive = False
305                 self.addLineUnlessIdenticalReactivate(line)
306
307         def setRotations(self):
308                 "Set the rotations."
309                 self.rootHalf = math.sqrt( 0.5 )
310                 self.rotations = []
311                 self.rotations.append( complex( self.rootHalf, self.rootHalf ) )
312                 self.rotations.append( complex( self.rootHalf, - self.rootHalf ) )
313                 self.rotations.append( complex( 0.0, 1.0 ) )
314                 self.rotations.append( complex(0.0, -1.0) )
315                 self.rotations.append( complex( - self.rootHalf, self.rootHalf ) )
316                 self.rotations.append( complex( - self.rootHalf, - self.rootHalf ) )
317
318
319 def main():
320         "Display the splodge dialog."
321         if len(sys.argv) > 1:
322                 writeOutput(' '.join(sys.argv[1 :]))
323         else:
324                 settings.startMainLoopFromConstructor(getNewRepository())
325
326 if __name__ == "__main__":
327         main()