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 / limit.py
1 #! /usr/bin/env python
2 """
3 This page is in the table of contents.
4 This plugin limits the feed rate of the tool head, so that the stepper motors are not driven too fast and skip steps.
5
6 The limit manual page is at:
7 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Limit
8
9 The maximum z feed rate is defined in speed.
10
11 ==Operation==
12 The default 'Activate Limit' checkbox is on.  When it is on, the functions described below will work, when it is off, nothing will be done.
13
14 ==Settings==
15 ===Maximum Initial Feed Rate===
16 Default is one millimeter per second.
17
18 Defines the maximum speed of the inital tool head move.
19
20 ==Examples==
21 The following examples limit the file Screw Holder Bottom.stl.  The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and limit.py.
22
23 > python limit.py
24 This brings up the limit dialog.
25
26 > python limit.py Screw Holder Bottom.stl
27 The limit tool is parsing the file:
28 Screw Holder Bottom.stl
29 ..
30 The limit tool has created the file:
31 .. Screw Holder Bottom_limit.gcode
32
33 """
34
35 #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.
36 import __init__
37
38 from datetime import date
39 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
40 from fabmetheus_utilities.vector3 import Vector3
41 from fabmetheus_utilities import archive
42 from fabmetheus_utilities import euclidean
43 from fabmetheus_utilities import gcodec
44 from fabmetheus_utilities import intercircle
45 from fabmetheus_utilities import settings
46 from skeinforge_application.skeinforge_utilities import skeinforge_craft
47 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
48 from skeinforge_application.skeinforge_utilities import skeinforge_profile
49 import math
50 import os
51 import sys
52
53
54 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
55 __date__ = '$Date: 2008/28/04 $'
56 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
57
58
59 def getCraftedText(fileName, gcodeText='', repository=None):
60         'Limit a gcode file or text.'
61         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
62
63 def getCraftedTextFromText(gcodeText, repository=None):
64         'Limit a gcode text.'
65         if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'limit'):
66                 return gcodeText
67         if repository == None:
68                 repository = settings.getReadRepository(LimitRepository())
69         if not repository.activateLimit.value:
70                 return gcodeText
71         return LimitSkein().getCraftedGcode(gcodeText, repository)
72
73 def getNewRepository():
74         'Get new repository.'
75         return LimitRepository()
76
77 def writeOutput(fileName, shouldAnalyze=True):
78         'Limit a gcode file.'
79         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'limit', shouldAnalyze)
80
81
82 class LimitRepository:
83         'A class to handle the limit settings.'
84         def __init__(self):
85                 'Set the default settings, execute title & settings fileName.'
86                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.limit.html', self )
87                 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Limit', self, '')
88                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Limit')
89                 self.activateLimit = settings.BooleanSetting().getFromValue('Activate Limit', self, False)
90                 self.maximumInitialFeedRate = settings.FloatSpin().getFromValue(0.5, 'Maximum Initial Feed Rate (mm/s):', self, 10.0, 1.0)
91                 self.executeTitle = 'Limit'
92
93         def execute(self):
94                 'Limit button has been clicked.'
95                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
96                 for fileName in fileNames:
97                         writeOutput(fileName)
98
99
100 class LimitSkein:
101         'A class to limit a skein of extrusions.'
102         def __init__(self):
103                 self.distanceFeedRate = gcodec.DistanceFeedRate()
104                 self.feedRateMinute = None
105                 self.lineIndex = 0
106                 self.maximumZDrillFeedRatePerSecond = 987654321.0
107                 self.oldLocation = None
108
109         def getCraftedGcode(self, gcodeText, repository):
110                 'Parse gcode text and store the limit gcode.'
111                 self.repository = repository
112                 self.lines = archive.getTextLines(gcodeText)
113                 self.parseInitialization()
114                 self.maximumZDrillFeedRatePerSecond = min(self.maximumZDrillFeedRatePerSecond, self.maximumZFeedRatePerSecond)
115                 self.maximumZCurrentFeedRatePerSecond = self.maximumZFeedRatePerSecond
116                 for lineIndex in xrange(self.lineIndex, len(self.lines)):
117                         self.parseLine( lineIndex )
118                 return self.distanceFeedRate.output.getvalue()
119
120         def getLimitedInitialMovement(self, line, splitLine):
121                 'Get a limited linear movement.'
122                 if self.oldLocation == None:
123                         line = self.distanceFeedRate.getLineWithFeedRate(60.0 * self.repository.maximumInitialFeedRate.value, line, splitLine)
124                 return line
125
126         def getZLimitedLine(self, deltaZ, distance, line, splitLine):
127                 'Get a replaced z limited gcode movement line.'
128                 zFeedRateSecond = self.feedRateMinute * deltaZ / distance / 60.0
129                 if zFeedRateSecond <= self.maximumZCurrentFeedRatePerSecond:
130                         return line
131                 limitedFeedRateMinute = self.feedRateMinute * self.maximumZCurrentFeedRatePerSecond / zFeedRateSecond
132                 return self.distanceFeedRate.getLineWithFeedRate(limitedFeedRateMinute, line, splitLine)
133
134         def getZLimitedLineArc(self, line, splitLine):
135                 'Get a replaced z limited gcode arc movement line.'
136                 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
137                 if self.feedRateMinute == None or self.oldLocation == None:
138                         return line
139                 relativeLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
140                 self.oldLocation += relativeLocation
141                 deltaZ = abs(relativeLocation.z)
142                 distance = gcodec.getArcDistance(relativeLocation, splitLine)
143                 return self.getZLimitedLine(deltaZ, distance, line, splitLine)
144
145         def getZLimitedLineLinear(self, line, location, splitLine):
146                 'Get a replaced z limited gcode linear movement line.'
147                 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
148                 if location == self.oldLocation:
149                         return ''
150                 if self.feedRateMinute == None or self.oldLocation == None:
151                         return line
152                 deltaZ = abs(location.z - self.oldLocation.z)
153                 distance = abs(location - self.oldLocation)
154                 return self.getZLimitedLine(deltaZ, distance, line, splitLine)
155
156         def parseInitialization(self):
157                 'Parse gcode initialization and store the parameters.'
158                 for self.lineIndex in xrange(len(self.lines)):
159                         line = self.lines[self.lineIndex]
160                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
161                         firstWord = gcodec.getFirstWord(splitLine)
162                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
163                         if firstWord == '(</extruderInitialization>)':
164                                 self.distanceFeedRate.addTagBracketedProcedure('limit')
165                                 return
166                         elif firstWord == '(<maximumZDrillFeedRatePerSecond>':
167                                 self.maximumZDrillFeedRatePerSecond = float(splitLine[1])
168                         elif firstWord == '(<maximumZFeedRatePerSecond>':
169                                 self.maximumZFeedRatePerSecond = float(splitLine[1])
170                         self.distanceFeedRate.addLine(line)
171
172         def parseLine( self, lineIndex ):
173                 'Parse a gcode line and add it to the limit skein.'
174                 line = self.lines[lineIndex].lstrip()
175                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
176                 if len(splitLine) < 1:
177                         return
178                 firstWord = gcodec.getFirstWord(splitLine)
179                 if firstWord == 'G1':
180                         location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
181                         line = self.getLimitedInitialMovement(line, splitLine)
182                         line = self.getZLimitedLineLinear(line, location, splitLine)
183                         self.oldLocation = location
184                 elif firstWord == 'G2' or firstWord == 'G3':
185                         line = self.getZLimitedLineArc(line, splitLine)
186                 elif firstWord == 'M101':
187                         self.maximumZCurrentFeedRatePerSecond = self.maximumZDrillFeedRatePerSecond
188                 elif firstWord == 'M103':
189                         self.maximumZCurrentFeedRatePerSecond = self.maximumZFeedRatePerSecond
190                 self.distanceFeedRate.addLine(line)
191
192
193 def main():
194         'Display the limit dialog.'
195         if len(sys.argv) > 1:
196                 writeOutput(' '.join(sys.argv[1 :]))
197         else:
198                 settings.startMainLoopFromConstructor(getNewRepository())
199
200 if __name__ == '__main__':
201         main()