chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / skeinforge_application / skeinforge_plugins / craft_plugins / drill.py
1 """
2 This page is in the table of contents.
3 Drill is a script to drill down small holes.
4
5 ==Operation==
6 The default 'Activate Drill' checkbox is on.  When it is on, the functions described below will work, when it is off, the functions will not be called.
7
8 ==Settings==
9 ===Drilling Margin===
10 The drill script will move the tool from the top of the hole plus the 'Drilling Margin on Top', to the bottom of the hole minus the 'Drilling Margin on Bottom'.
11
12 ===Drilling Margin on Top===
13 Default is three millimeters.
14
15 ===Drilling Margin on Bottom===
16 Default is one millimeter.
17
18 ==Examples==
19 The following examples drill the file Screw Holder Bottom.stl.  The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and drill.py.
20
21 > python drill.py
22 This brings up the drill dialog.
23
24 > python drill.py Screw Holder Bottom.stl
25 The drill tool is parsing the file:
26 Screw Holder Bottom.stl
27 ..
28 The drill tool has created the file:
29 .. Screw Holder Bottom_drill.gcode
30
31 """
32
33 from __future__ import absolute_import
34 #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.
35 import __init__
36
37 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
38 from fabmetheus_utilities import archive
39 from fabmetheus_utilities import euclidean
40 from fabmetheus_utilities import gcodec
41 from fabmetheus_utilities import intercircle
42 from fabmetheus_utilities import settings
43 from skeinforge_application.skeinforge_utilities import skeinforge_craft
44 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
45 from skeinforge_application.skeinforge_utilities import skeinforge_profile
46 import sys
47
48
49 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
50 __date__ = '$Date: 2008/21/04 $'
51 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
52
53 def getCraftedText( fileName, text, repository=None):
54         "Drill a gcode linear move file or text."
55         return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository)
56
57 def getCraftedTextFromText(gcodeText, repository=None):
58         "Drill a gcode linear move text."
59         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'drill'):
60                 return gcodeText
61         if repository == None:
62                 repository = settings.getReadRepository( DrillRepository() )
63         if not repository.activateDrill.value:
64                 return gcodeText
65         return DrillSkein().getCraftedGcode(gcodeText, repository)
66
67 def getNewRepository():
68         'Get new repository.'
69         return DrillRepository()
70
71 def getPolygonCenter( polygon ):
72         "Get the centroid of a polygon."
73         pointSum = complex()
74         areaSum = 0.0
75         for pointIndex in xrange( len( polygon ) ):
76                 pointBegin = polygon[pointIndex]
77                 pointEnd  = polygon[ (pointIndex + 1) % len( polygon ) ]
78                 area = pointBegin.real * pointEnd.imag - pointBegin.imag * pointEnd.real
79                 areaSum += area
80                 pointSum += complex( pointBegin.real + pointEnd.real, pointBegin.imag + pointEnd.imag ) * area
81         return pointSum / 3.0 / areaSum
82
83 def writeOutput(fileName, shouldAnalyze=True):
84         "Drill a gcode linear move file."
85         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'drill', shouldAnalyze)
86
87
88 class ThreadLayer:
89         "A layer of loops and paths."
90         def __init__( self, z ):
91                 "Thread layer constructor."
92                 self.points = []
93                 self.z = z
94
95         def __repr__(self):
96                 "Get the string representation of this thread layer."
97                 return '%s, %s' % ( self.z, self.points )
98
99
100 class DrillRepository:
101         "A class to handle the drill settings."
102         def __init__(self):
103                 "Set the default settings, execute title & settings fileName."
104                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.drill.html', self )
105                 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Drill', self, '')
106                 self.activateDrill = settings.BooleanSetting().getFromValue('Activate Drill', self, True )
107                 self.drillingMarginOnBottom = settings.FloatSpin().getFromValue( 0.0, 'Drilling Margin on Bottom (millimeters):', self, 5.0, 1.0 )
108                 self.drillingMarginOnTop = settings.FloatSpin().getFromValue( 0.0, 'Drilling Margin on Top (millimeters):', self, 20.0, 3.0 )
109                 self.executeTitle = 'Drill'
110
111         def execute(self):
112                 "Drill button has been clicked."
113                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
114                 for fileName in fileNames:
115                         writeOutput(fileName)
116
117
118 class DrillSkein:
119         "A class to drill a skein of extrusions."
120         def __init__(self):
121                 self.boundary = None
122                 self.distanceFeedRate = gcodec.DistanceFeedRate()
123                 self.extruderActive = False
124                 self.halfLayerThickness = 0.4
125                 self.isDrilled = False
126                 self.lineIndex = 0
127                 self.lines = None
128                 self.maximumDistance = 0.06
129                 self.oldLocation = None
130                 self.threadLayer = None
131                 self.threadLayers = []
132
133         def addDrillHoles(self):
134                 "Parse a gcode line."
135                 self.isDrilled = True
136                 if len( self.threadLayers ) < 1:
137                         return
138                 topThreadLayer = self.threadLayers[0]
139                 drillPoints = topThreadLayer.points
140                 for drillPoint in drillPoints:
141                         zTop = topThreadLayer.z + self.halfLayerThickness + self.repository.drillingMarginOnTop.value
142                         drillingCenterDepth = self.getDrillingCenterDepth( topThreadLayer.z, drillPoint )
143                         zBottom = drillingCenterDepth - self.halfLayerThickness - self.repository.drillingMarginOnBottom.value
144                         self.addGcodeFromVerticalThread( drillPoint, zTop, zBottom )
145
146         def addGcodeFromVerticalThread( self, point, zBegin, zEnd ):
147                 "Add a thread to the output."
148                 self.distanceFeedRate.addGcodeMovementZ( point, zBegin )
149                 self.distanceFeedRate.addLine('M101') # Turn extruder on.
150                 self.distanceFeedRate.addGcodeMovementZ( point, zEnd )
151                 self.distanceFeedRate.addLine('M103') # Turn extruder off.
152
153         def addThreadLayerIfNone(self):
154                 "Add a thread layer if it is none."
155                 if self.threadLayer != None:
156                         return
157                 self.threadLayer = ThreadLayer( self.layerZ )
158                 self.threadLayers.append( self.threadLayer )
159
160         def getCraftedGcode(self, gcodeText, repository):
161                 "Parse gcode text and store the drill gcode."
162                 self.lines = archive.getTextLines(gcodeText)
163                 self.repository = repository
164                 self.parseInitialization()
165                 for line in self.lines[self.lineIndex :]:
166                         self.parseNestedRing(line)
167                 for line in self.lines[self.lineIndex :]:
168                         self.parseLine(line)
169                 return self.distanceFeedRate.output.getvalue()
170
171         def getDrillingCenterDepth( self, drillingCenterDepth, drillPoint ):
172                 "Get the drilling center depth."
173                 for threadLayer in self.threadLayers[1 :]:
174                         if self.isPointClose( drillPoint, threadLayer.points ):
175                                 drillingCenterDepth = threadLayer.z
176                         else:
177                                 return drillingCenterDepth
178                 return drillingCenterDepth
179
180         def isPointClose( self, drillPoint, points ):
181                 "Determine if a point on the thread layer is close."
182                 for point in points:
183                         if abs( point - drillPoint ) < self.maximumDistance:
184                                 return True
185                 return False
186
187         def linearMove( self, splitLine ):
188                 "Add a linear move to the loop."
189                 self.addThreadLayerIfNone()
190                 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
191                 if self.extruderActive:
192                         self.boundary = None
193                 self.oldLocation = location
194
195         def parseInitialization(self):
196                 'Parse gcode initialization and store the parameters.'
197                 for self.lineIndex in xrange(len(self.lines)):
198                         line = self.lines[self.lineIndex]
199                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
200                         firstWord = gcodec.getFirstWord(splitLine)
201                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
202                         if firstWord == '(</extruderInitialization>)':
203                                 self.distanceFeedRate.addTagBracketedProcedure('drill')
204                                 return
205                         elif firstWord == '(<layerHeight>':
206                                 self.halfLayerThickness = 0.5 * float(splitLine[1])
207                         elif firstWord == '(<edgeWidth>':
208                                 self.maximumDistance = 0.1 * float(splitLine[1])
209                         self.distanceFeedRate.addLine(line)
210
211         def parseLine(self, line):
212                 "Parse a gcode line."
213                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
214                 if len(splitLine) < 1:
215                         return
216                 firstWord = splitLine[0]
217                 self.distanceFeedRate.addLine(line)
218                 if firstWord == '(<layer>':
219                         if not self.isDrilled:
220                                 self.addDrillHoles()
221
222         def parseNestedRing(self, line):
223                 "Parse a nested ring."
224                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
225                 if len(splitLine) < 1:
226                         return
227                 firstWord = splitLine[0]
228                 if firstWord == 'G1':
229                         self.linearMove(splitLine)
230                 if firstWord == 'M101':
231                         self.extruderActive = True
232                 elif firstWord == 'M103':
233                         self.extruderActive = False
234                 elif firstWord == '(<boundaryPoint>':
235                         location = gcodec.getLocationFromSplitLine(None, splitLine)
236                         if self.boundary == None:
237                                 self.boundary = []
238                         self.boundary.append(location.dropAxis())
239                 elif firstWord == '(<layer>':
240                         self.layerZ = float(splitLine[1])
241                         self.threadLayer = None
242                 elif firstWord == '(<boundaryPerimeter>)':
243                         self.addThreadLayerIfNone()
244                 elif firstWord == '(</boundaryPerimeter>)':
245                         if self.boundary != None:
246                                 self.threadLayer.points.append( getPolygonCenter( self.boundary ) )
247                                 self.boundary = None
248
249
250 def main():
251         "Display the drill dialog."
252         if len(sys.argv) > 1:
253                 writeOutput(' '.join(sys.argv[1 :]))
254         else:
255                 settings.startMainLoopFromConstructor(getNewRepository())
256
257 if __name__ == "__main__":
258         main()