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 / 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
61 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
62 from fabmetheus_utilities import archive
63 from fabmetheus_utilities import gcodec
64 from fabmetheus_utilities import settings
65 from skeinforge_application.skeinforge_utilities import skeinforge_craft
66 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
67 from skeinforge_application.skeinforge_utilities import skeinforge_profile
68 import math
69 import sys
70
71
72 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
73 __date__ = '$Date: 2008/21/04 $'
74 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
75
76
77 def getCraftedText( fileName, text, splodgeRepository = None ):
78         "Splodge a gcode linear move file or text."
79         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), splodgeRepository )
80
81 def getCraftedTextFromText( gcodeText, splodgeRepository = None ):
82         "Splodge a gcode linear move text."
83         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'splodge'):
84                 return gcodeText
85         if splodgeRepository == None:
86                 splodgeRepository = settings.getReadRepository( SplodgeRepository() )
87         if not splodgeRepository.activateSplodge.value:
88                 return gcodeText
89         return SplodgeSkein().getCraftedGcode( gcodeText, splodgeRepository )
90
91 def getNewRepository():
92         'Get new repository.'
93         return SplodgeRepository()
94
95 def writeOutput(fileName, shouldAnalyze=True):
96         "Splodge a gcode linear move file."
97         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'splodge', shouldAnalyze)
98
99
100 class SplodgeRepository(object):
101         "A class to handle the splodge settings."
102         def __init__(self):
103                 "Set the default settings, execute title & settings fileName."
104                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.splodge.html', self )
105                 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Splodge', self, '')
106                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Splodge')
107                 self.activateSplodge = settings.BooleanSetting().getFromValue('Activate Splodge', self, False )
108                 settings.LabelSeparator().getFromRepository(self)
109                 settings.LabelDisplay().getFromName('- Initial -', self )
110                 self.initialLiftOverExtraThickness = settings.FloatSpin().getFromValue( 0.5, 'Initial Lift over Extra Thickness (ratio):', self, 1.5, 1.0 )
111                 self.initialSplodgeFeedRate = settings.FloatSpin().getFromValue( 0.4, 'Initial Splodge Feed Rate (mm/s):', self, 2.4, 1.0 )
112                 self.initialSplodgeQuantityLength = settings.FloatSpin().getFromValue( 10.0, 'Initial Splodge Quantity Length (millimeters):', self, 90.0, 30.0 )
113                 settings.LabelSeparator().getFromRepository(self)
114                 settings.LabelDisplay().getFromName('- Operating -', self )
115                 self.operatingLiftOverExtraThickness = settings.FloatSpin().getFromValue( 0.5, 'Operating Lift over Extra Thickness (ratio):', self, 1.5, 1.0 )
116                 self.operatingSplodgeFeedRate = settings.FloatSpin().getFromValue( 0.4, 'Operating Splodge Feed Rate (mm/s):', self, 2.4, 1.0 )
117                 self.operatingSplodgeQuantityLength = settings.FloatSpin().getFromValue(0.4, 'Operating Splodge Quantity Length (millimeters):', self, 2.4, 1.0)
118                 settings.LabelSeparator().getFromRepository(self)
119                 self.executeTitle = 'Splodge'
120
121         def execute(self):
122                 "Splodge button has been clicked."
123                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
124                 for fileName in fileNames:
125                         writeOutput(fileName)
126
127
128 class SplodgeSkein(object):
129         "A class to splodge a skein of extrusions."
130         def __init__(self):
131                 self.distanceFeedRate = gcodec.DistanceFeedRate()
132                 self.feedRateMinute = 961.0
133                 self.isExtruderActive = False
134                 self.hasInitialSplodgeBeenAdded = False
135                 self.isLastExtruderCommandActivate = False
136                 self.lastLineOutput = None
137                 self.lineIndex = 0
138                 self.lines = None
139                 self.oldLocation = None
140                 self.operatingFeedRatePerSecond = 15.0
141
142         def addLineUnlessIdentical(self, line):
143                 "Add a line, unless it is identical to the last line."
144                 if line == self.lastLineOutput:
145                         return
146                 self.lastLineOutput = line
147                 self.distanceFeedRate.addLine(line)
148
149         def addLineUnlessIdenticalReactivate(self, line):
150                 "Add a line, unless it is identical to the last line or another M101."
151                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
152                 if len(splitLine) < 1:
153                         return
154                 firstWord = splitLine[0]
155                 if firstWord == 'M101':
156                         if not self.isLastExtruderCommandActivate:
157                                 self.addLineUnlessIdentical(line)
158                                 self.isLastExtruderCommandActivate = True
159                         return
160                 if firstWord == 'M103':
161                         self.isLastExtruderCommandActivate = False
162                 self.addLineUnlessIdentical(line)
163
164         def getCraftedGcode( self, gcodeText, splodgeRepository ):
165                 "Parse gcode text and store the splodge gcode."
166                 self.lines = archive.getTextLines(gcodeText)
167                 self.setRotations()
168                 self.splodgeRepository = splodgeRepository
169                 self.parseInitialization( splodgeRepository )
170                 self.boundingRectangle = gcodec.BoundingRectangle().getFromGcodeLines( self.lines[self.lineIndex :], 0.5 * self.edgeWidth )
171                 self.initialSplodgeFeedRateMinute = 60.0 * splodgeRepository.initialSplodgeFeedRate.value
172                 self.initialStartupDistance = splodgeRepository.initialSplodgeQuantityLength.value * splodgeRepository.initialSplodgeFeedRate.value / self.operatingFeedRatePerSecond
173                 self.operatingSplodgeFeedRateMinute = 60.0 * splodgeRepository.operatingSplodgeFeedRate.value
174                 self.operatingStartupDistance = splodgeRepository.operatingSplodgeQuantityLength.value * splodgeRepository.operatingSplodgeFeedRate.value / self.operatingFeedRatePerSecond
175                 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
176                         line = self.lines[self.lineIndex]
177                         self.parseLine(line)
178                 return self.distanceFeedRate.output.getvalue()
179
180         def getInitialSplodgeLine( self, line, location ):
181                 "Add the initial splodge line."
182                 if not self.isJustBeforeExtrusion():
183                         return line
184                 self.hasInitialSplodgeBeenAdded = True
185                 if self.splodgeRepository.initialSplodgeQuantityLength.value < self.minimumQuantityLength:
186                         return line
187                 return self.getSplodgeLineGivenDistance( self.initialSplodgeFeedRateMinute, line, self.splodgeRepository.initialLiftOverExtraThickness.value, location, self.initialStartupDistance )
188
189         def getNextActiveLocationComplex(self):
190                 "Get the next active line."
191                 isActive = False
192                 for lineIndex in xrange( self.lineIndex + 1, len(self.lines) ):
193                         line = self.lines[lineIndex]
194                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
195                         firstWord = gcodec.getFirstWord(splitLine)
196                         if firstWord == 'M101':
197                                 isActive = True
198                         if firstWord == 'G1' and isActive:
199                                 return gcodec.getLocationFromSplitLine(self.oldLocation, splitLine).dropAxis()
200                 return None
201
202         def getOperatingSplodgeLine( self, line, location ):
203                 "Get the operating splodge line."
204                 if not self.isJustBeforeExtrusion():
205                         return line
206                 if self.splodgeRepository.operatingSplodgeQuantityLength.value < self.minimumQuantityLength:
207                         return line
208                 return self.getSplodgeLineGivenDistance( self.operatingSplodgeFeedRateMinute, line, self.splodgeRepository.operatingLiftOverExtraThickness.value, location, self.operatingStartupDistance )
209
210         def getSplodgeLine(self, line, location, splitLine):
211                 "Get splodged gcode line."
212                 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
213                 if self.hasInitialSplodgeBeenAdded:
214                         return self.getOperatingSplodgeLine(line, location)
215                 return self.getInitialSplodgeLine(line, location)
216
217         def getSplodgeLineGivenDistance( self, feedRateMinute, line, liftOverExtraThickness, location, startupDistance ):
218                 "Add the splodge line."
219                 locationComplex = location.dropAxis()
220                 relativeStartComplex = None
221                 nextLocationComplex = self.getNextActiveLocationComplex()
222                 if nextLocationComplex != None:
223                         if nextLocationComplex != locationComplex:
224                                 relativeStartComplex = locationComplex - nextLocationComplex
225                 if relativeStartComplex == None:
226                         relativeStartComplex = complex( 19.9, 9.9 )
227                         if self.oldLocation != None:
228                                 oldLocationComplex = self.oldLocation.dropAxis()
229                                 if oldLocationComplex != locationComplex:
230                                         relativeStartComplex = oldLocationComplex - locationComplex
231                 relativeStartComplex *= startupDistance / abs( relativeStartComplex )
232                 startComplex = self.getStartInsideBoundingRectangle( locationComplex, relativeStartComplex )
233                 feedRateMultiplier = feedRateMinute / self.operatingFeedRatePerSecond / 60.0
234                 splodgeLayerThickness = self.layerHeight / math.sqrt( feedRateMultiplier )
235                 extraLayerThickness = splodgeLayerThickness - self.layerHeight
236                 lift = extraLayerThickness * liftOverExtraThickness
237                 startLine = self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.feedRateMinute, startComplex, location.z + lift )
238                 self.addLineUnlessIdenticalReactivate( startLine )
239                 self.addLineUnlessIdenticalReactivate('M101')
240                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
241                 lineLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
242                 self.distanceFeedRate.addGcodeMovementZWithFeedRate( feedRateMinute, locationComplex, lineLocation.z + lift )
243                 return ''
244
245         def getStartInsideBoundingRectangle( self, locationComplex, relativeStartComplex ):
246                 "Get a start inside the bounding rectangle."
247                 startComplex = locationComplex + relativeStartComplex
248                 if self.boundingRectangle.isPointInside( startComplex ):
249                         return startComplex
250                 for rotation in self.rotations:
251                         rotatedRelativeStartComplex = relativeStartComplex * rotation
252                         startComplex = locationComplex + rotatedRelativeStartComplex
253                         if self.boundingRectangle.isPointInside( startComplex ):
254                                 return startComplex
255                 return startComplex
256
257         def isJustBeforeExtrusion(self):
258                 "Determine if activate command is before linear move command."
259                 for lineIndex in xrange(self.lineIndex + 1, len(self.lines)):
260                         line = self.lines[lineIndex]
261                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
262                         firstWord = gcodec.getFirstWord(splitLine)
263                         if firstWord == 'G1' or firstWord == 'M103':
264                                 return False
265                         if firstWord == 'M101':
266                                 return True
267                 return False
268
269         def parseInitialization( self, splodgeRepository ):
270                 'Parse gcode initialization and store the parameters.'
271                 for self.lineIndex in xrange(len(self.lines)):
272                         line = self.lines[self.lineIndex]
273                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
274                         firstWord = gcodec.getFirstWord(splitLine)
275                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
276                         if firstWord == '(</extruderInitialization>)':
277                                 self.addLineUnlessIdenticalReactivate(gcodec.getTagBracketedProcedure('splodge'))
278                                 return
279                         elif firstWord == '(<layerHeight>':
280                                 self.layerHeight = float(splitLine[1])
281                         elif firstWord == '(<operatingFeedRatePerSecond>':
282                                 self.operatingFeedRatePerSecond = float(splitLine[1])
283                         elif firstWord == '(<edgeWidth>':
284                                 self.edgeWidth = float(splitLine[1])
285                                 self.minimumQuantityLength = 0.1 * self.edgeWidth
286                         self.addLineUnlessIdenticalReactivate(line)
287
288         def parseLine(self, line):
289                 "Parse a gcode line and add it to the bevel gcode."
290                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
291                 if len(splitLine) < 1:
292                         return
293                 firstWord = splitLine[0]
294                 if firstWord == 'G1':
295                         location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
296                         line = self.getSplodgeLine(line, location, splitLine)
297                         self.oldLocation = location
298                 elif firstWord == 'M101':
299                         self.isExtruderActive = True
300                 elif firstWord == 'M103':
301                         self.isExtruderActive = False
302                 self.addLineUnlessIdenticalReactivate(line)
303
304         def setRotations(self):
305                 "Set the rotations."
306                 self.rootHalf = math.sqrt( 0.5 )
307                 self.rotations = []
308                 self.rotations.append( complex( self.rootHalf, self.rootHalf ) )
309                 self.rotations.append( complex( self.rootHalf, - self.rootHalf ) )
310                 self.rotations.append( complex( 0.0, 1.0 ) )
311                 self.rotations.append( complex(0.0, -1.0) )
312                 self.rotations.append( complex( - self.rootHalf, self.rootHalf ) )
313                 self.rotations.append( complex( - self.rootHalf, - self.rootHalf ) )
314
315
316 def main():
317         "Display the splodge dialog."
318         if len(sys.argv) > 1:
319                 writeOutput(' '.join(sys.argv[1 :]))
320         else:
321                 settings.startMainLoopFromConstructor(getNewRepository())
322
323 if __name__ == "__main__":
324         main()