chiark / gitweb /
9b4ac36c31c7cb13a2ebf8eee19dfdfa6016c537
[cura.git] / Cura / skeinforge_application / skeinforge_plugins / craft_plugins / hop.py
1 """
2 This page is in the table of contents.
3 Hop is a script to raise the extruder when it is not extruding.
4
5 Note: 
6
7 Note: In some cases where you have thin overhang this plugin can help solve the problem object being knocked off by the head
8
9 The hop manual page is at:
10 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Hop
11
12 ==Operation==
13 The default 'Activate Hop' checkbox is off.
14
15 It is off because Vik and Nophead found better results without hopping.  Numerous users reported better output without this plugin hence it is off by default.  
16
17 When activated the extruder will hop when traveling.  When it is off, nothing will be done.
18
19 ==Settings==
20 ===Hop Over Layer Thickness===
21 Default is one.
22
23 Defines the ratio of the hop height over the layer height, this is the most important hop setting.
24
25 ===Minimum Hop Angle===
26 Default is 20 degrees.
27
28 Defines the minimum angle that the path of the extruder will be raised.  An angle of ninety means that the extruder will go straight up as soon as it is not extruding and a low angle means the extruder path will gradually rise to the hop height.
29
30 ==Examples==
31 The following examples hop the file Screw Holder Bottom.stl.  The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and hop.py.
32
33 > python hop.py
34 This brings up the hop dialog.
35
36 > python hop.py Screw Holder Bottom.stl
37 The hop tool is parsing the file:
38 Screw Holder Bottom.stl
39 ..
40 The hop tool has created the file:
41 .. Screw Holder Bottom_hop.gcode
42
43 """
44
45 from __future__ import absolute_import
46 #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.
47 import __init__
48
49 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
50 from fabmetheus_utilities import archive
51 from fabmetheus_utilities import euclidean
52 from fabmetheus_utilities import gcodec
53 from fabmetheus_utilities import settings
54 from skeinforge_application.skeinforge_utilities import skeinforge_craft
55 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
56 from skeinforge_application.skeinforge_utilities import skeinforge_profile
57 import math
58 import sys
59
60
61 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
62 __date__ = '$Date: 2008/21/04 $'
63 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
64
65
66 def getCraftedText( fileName, text, hopRepository = None ):
67         "Hop a gcode linear move text."
68         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), hopRepository )
69
70 def getCraftedTextFromText( gcodeText, hopRepository = None ):
71         "Hop a gcode linear move text."
72         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'hop'):
73                 return gcodeText
74         if hopRepository == None:
75                 hopRepository = settings.getReadRepository( HopRepository() )
76         if not hopRepository.activateHop.value:
77                 return gcodeText
78         return HopSkein().getCraftedGcode( gcodeText, hopRepository )
79
80 def getNewRepository():
81         'Get new repository.'
82         return HopRepository()
83
84 def writeOutput(fileName, shouldAnalyze=True):
85         "Hop a gcode linear move file.  Chain hop the gcode if it is not already hopped."
86         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'hop', shouldAnalyze)
87
88
89 class HopRepository:
90         "A class to handle the hop settings."
91         def __init__(self):
92                 "Set the default settings, execute title & settings fileName."
93                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.hop.html', self )
94                 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Hop', self, '')
95                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Hop')
96                 self.activateHop = settings.BooleanSetting().getFromValue('Activate Hop', self, False )
97                 self.hopOverLayerThickness = settings.FloatSpin().getFromValue( 0.5, 'Hop Over Layer Thickness (ratio):', self, 1.5, 1.0 )
98                 self.minimumHopAngle = settings.FloatSpin().getFromValue( 20.0, 'Minimum Hop Angle (degrees):', self, 60.0, 30.0 )
99                 self.executeTitle = 'Hop'
100
101         def execute(self):
102                 "Hop button has been clicked."
103                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
104                 for fileName in fileNames:
105                         writeOutput(fileName)
106
107
108 class HopSkein:
109         "A class to hop a skein of extrusions."
110         def __init__(self):
111                 'Initialize'
112                 self.distanceFeedRate = gcodec.DistanceFeedRate()
113                 self.extruderActive = False
114                 self.feedRateMinute = 961.0
115                 self.hopHeight = 0.4
116                 self.hopDistance = self.hopHeight
117                 self.justDeactivated = False
118                 self.layerCount = settings.LayerCount()
119                 self.lineIndex = 0
120                 self.lines = None
121                 self.oldLocation = None
122
123         def getCraftedGcode( self, gcodeText, hopRepository ):
124                 "Parse gcode text and store the hop gcode."
125                 self.lines = archive.getTextLines(gcodeText)
126                 self.minimumSlope = math.tan( math.radians( hopRepository.minimumHopAngle.value ) )
127                 self.parseInitialization( hopRepository )
128                 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
129                         line = self.lines[self.lineIndex]
130                         self.parseLine(line)
131                 return self.distanceFeedRate.output.getvalue()
132
133         def getHopLine(self, line):
134                 "Get hopped gcode line."
135                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
136                 self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine )
137                 if self.extruderActive:
138                         return line
139                 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
140                 highestZ = location.z
141                 if self.oldLocation != None:
142                         highestZ = max( highestZ, self.oldLocation.z )
143                 highestZHop = highestZ + self.hopHeight
144                 locationComplex = location.dropAxis()
145                 if self.justDeactivated:
146                         oldLocationComplex = self.oldLocation.dropAxis()
147                         distance = abs( locationComplex - oldLocationComplex )
148                         if distance < self.minimumDistance:
149                                 if self.isNextTravel() or distance == 0.0:
150                                         return self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop )
151                         alongRatio = min( 0.41666666, self.hopDistance / distance )
152                         oneMinusAlong = 1.0 - alongRatio
153                         closeLocation = oldLocationComplex * oneMinusAlong + locationComplex * alongRatio
154                         self.distanceFeedRate.addLine( self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop ) )
155                         if self.isNextTravel():
156                                 return self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop )
157                         farLocation = oldLocationComplex * alongRatio + locationComplex * oneMinusAlong
158                         self.distanceFeedRate.addGcodeMovementZWithFeedRate( self.feedRateMinute, farLocation, highestZHop )
159                         return line
160                 if self.isNextTravel():
161                         return self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop )
162                 return line
163
164         def isNextTravel(self):
165                 "Determine if there is another linear travel before the thread ends."
166                 for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ):
167                         line = self.lines[ afterIndex ]
168                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
169                         firstWord = gcodec.getFirstWord(splitLine)
170                         if firstWord == 'G1':
171                                 return True
172                         if firstWord == 'M101':
173                                 return False
174                 return False
175
176         def parseInitialization( self, hopRepository ):
177                 'Parse gcode initialization and store the parameters.'
178                 for self.lineIndex in xrange(len(self.lines)):
179                         line = self.lines[self.lineIndex]
180                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
181                         firstWord = gcodec.getFirstWord(splitLine)
182                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
183                         if firstWord == '(<layerHeight>':
184                                 layerHeight = float(splitLine[1])
185                                 self.hopHeight = hopRepository.hopOverLayerThickness.value * layerHeight
186                                 self.hopDistance = self.hopHeight / self.minimumSlope
187                                 self.minimumDistance = 0.5 * layerHeight
188                         elif firstWord == '(</extruderInitialization>)':
189                                 self.distanceFeedRate.addTagBracketedProcedure('hop')
190                                 return
191                         self.distanceFeedRate.addLine(line)
192
193         def parseLine(self, line):
194                 "Parse a gcode line and add it to the bevel gcode."
195                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
196                 if len(splitLine) < 1:
197                         return
198                 firstWord = splitLine[0]
199                 if self.distanceFeedRate.getIsAlteration(line):
200                         return
201                 if firstWord == 'G1':
202                         line = self.getHopLine(line)
203                         self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
204                         self.justDeactivated = False
205                 elif firstWord == '(<layer>':
206                         self.layerCount.printProgressIncrement('hop')
207                 elif firstWord == 'M101':
208                         self.extruderActive = True
209                 elif firstWord == 'M103':
210                         self.extruderActive = False
211                         self.justDeactivated = True
212                 self.distanceFeedRate.addLineCheckAlteration(line)
213
214
215 def main():
216         "Display the hop dialog."
217         if len(sys.argv) > 1:
218                 writeOutput(' '.join(sys.argv[1 :]))
219         else:
220                 settings.startMainLoopFromConstructor(getNewRepository())
221
222 if __name__ == "__main__":
223         main()