2 This page is in the table of contents.
3 Mill is a script to mill the outlines.
6 The default 'Activate Mill' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called.
10 ====Add Inner Loops====
13 When selected, the inner milling loops will be added.
15 ====Add Outer Loops====
18 When selected, the outer milling loops will be added.
23 When selected, there will be alternating horizontal and vertical milling paths, if it is off there will only be horizontal milling paths.
26 ====Loop Inner Outset over Perimeter Width====
29 Defines the ratio of the amount the inner milling loop will be outset over the edge width.
31 ====Loop Outer Outset over Perimeter Width====
34 Defines the ratio of the amount the outer milling loop will be outset over the edge width. The 'Loop Outer Outset over Perimeter Width' ratio should be greater than the 'Loop Inner Outset over Perimeter Width' ratio.
36 ===Mill Width over Perimeter Width===
39 Defines the ratio of the mill line width over the edge width. If the ratio is one, all the material will be milled. The greater the 'Mill Width over Perimeter Width' the farther apart the mill lines will be and so less of the material will be directly milled, the remaining material might still be removed in chips if the ratio is not much greater than one.
42 The following examples mill the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and mill.py.
45 This brings up the mill dialog.
47 > python mill.py Screw Holder Bottom.stl
48 The mill tool is parsing the file:
49 Screw Holder Bottom.stl
51 The mill tool has created the file:
52 Screw Holder Bottom_mill.gcode
56 from __future__ import absolute_import
58 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
59 from fabmetheus_utilities.geometry.solids import triangle_mesh
60 from fabmetheus_utilities.vector3 import Vector3
61 from fabmetheus_utilities import archive
62 from fabmetheus_utilities import euclidean
63 from fabmetheus_utilities import gcodec
64 from fabmetheus_utilities import intercircle
65 from fabmetheus_utilities import settings
66 from skeinforge_application.skeinforge_utilities import skeinforge_craft
67 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
68 from skeinforge_application.skeinforge_utilities import skeinforge_profile
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'
77 def getCraftedText( fileName, gcodeText = '', repository=None):
78 'Mill the file or gcodeText.'
79 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
81 def getCraftedTextFromText(gcodeText, repository=None):
82 'Mill a gcode linear move gcodeText.'
83 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'mill'):
85 if repository == None:
86 repository = settings.getReadRepository( MillRepository() )
87 if not repository.activateMill.value:
89 return MillSkein().getCraftedGcode(gcodeText, repository)
91 def getNewRepository():
93 return MillRepository()
95 def getPointsFromSegmentTable(segmentTable):
96 'Get the points from the segment table.'
98 segmentTableKeys = segmentTable.keys()
99 segmentTableKeys.sort()
100 for segmentTableKey in segmentTableKeys:
101 for segment in segmentTable[segmentTableKey]:
102 for endpoint in segment:
103 points.append(endpoint.point)
106 def isPointOfTableInLoop( loop, pointTable ):
107 'Determine if a point in the point table is in the loop.'
109 if point in pointTable:
113 def writeOutput(fileName, shouldAnalyze=True):
114 'Mill a gcode linear move file.'
115 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'mill', shouldAnalyze)
118 class Average(object):
119 'A class to hold values and get the average.'
123 def addValue( self, value ):
124 'Add a value to the total and the number of values.'
125 self.numberOfValues += 1
128 def getAverage(self):
130 if self.numberOfValues == 0:
131 print('should never happen, self.numberOfValues in Average is zero')
133 return self.total / float( self.numberOfValues )
136 'Set the number of values and the total to the default.'
137 self.numberOfValues = 0
141 class MillRepository(object):
142 'A class to handle the mill settings.'
144 'Set the default settings, execute title & settings fileName.'
145 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.mill.html', self )
146 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Mill', self, '')
147 self.activateMill = settings.BooleanSetting().getFromValue('Activate Mill', self, True )
148 settings.LabelDisplay().getFromName('- Add Loops -', self )
149 self.addInnerLoops = settings.BooleanSetting().getFromValue('Add Inner Loops', self, True )
150 self.addOuterLoops = settings.BooleanSetting().getFromValue('Add Outer Loops', self, True )
151 self.crossHatch = settings.BooleanSetting().getFromValue('Cross Hatch', self, True )
152 settings.LabelDisplay().getFromName('- Loop Outset -', self )
153 self.loopInnerOutsetOverEdgeWidth = settings.FloatSpin().getFromValue( 0.3, 'Loop Inner Outset over Perimeter Width (ratio):', self, 0.7, 0.5 )
154 self.loopOuterOutsetOverEdgeWidth = settings.FloatSpin().getFromValue( 0.8, 'Loop Outer Outset over Perimeter Width (ratio):', self, 1.4, 1.0 )
155 self.millWidthOverEdgeWidth = settings.FloatSpin().getFromValue( 0.8, 'Mill Width over Edge Width (ratio):', self, 1.8, 1.0 )
156 self.executeTitle = 'Mill'
159 'Mill button has been clicked.'
160 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
161 for fileName in fileNames:
162 writeOutput(fileName)
166 class MillSkein(object):
167 'A class to mill a skein of extrusions.'
169 self.aroundPixelTable = {}
170 self.average = Average()
171 self.boundaryLayers = []
172 self.distanceFeedRate = gcodec.DistanceFeedRate()
174 self.isExtruderActive = False
178 self.oldLocation = None
180 def addGcodeFromLoops(self, loops, z):
181 'Add gcode from loops.'
182 if self.oldLocation == None:
183 self.oldLocation = Vector3()
184 self.oldLocation.z = z
186 self.distanceFeedRate.addGcodeFromThreadZ(loop, z)
187 euclidean.addToThreadsFromLoop(self.halfEdgeWidth, 'loop', loop, self.oldLocation, self)
189 def addGcodeFromThreadZ( self, thread, z ):
190 'Add a thread to the output.'
191 self.distanceFeedRate.addGcodeFromThreadZ( thread, z )
193 def addMillThreads(self):
194 'Add the mill threads to the skein.'
195 boundaryLayer = self.boundaryLayers[self.layerIndex]
196 endpoints = euclidean.getEndpointsFromSegmentTable( boundaryLayer.segmentTable )
197 if len(endpoints) < 1:
199 paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.millWidth, self.aroundPixelTable, 1.0, self.aroundWidth)
200 averageZ = self.average.getAverage()
201 if self.repository.addInnerLoops.value:
202 self.addGcodeFromLoops( boundaryLayer.innerLoops, averageZ )
203 if self.repository.addOuterLoops.value:
204 self.addGcodeFromLoops( boundaryLayer.outerLoops, averageZ )
206 simplifiedPath = euclidean.getSimplifiedPath( path, self.millWidth )
207 self.distanceFeedRate.addGcodeFromThreadZ( simplifiedPath, averageZ )
209 def addSegmentTableLoops( self, boundaryLayerIndex ):
210 'Add the segment tables and loops to the boundary.'
211 boundaryLayer = self.boundaryLayers[boundaryLayerIndex]
212 euclidean.subtractXIntersectionsTable(boundaryLayer.outerHorizontalTable, boundaryLayer.innerHorizontalTable)
213 euclidean.subtractXIntersectionsTable(boundaryLayer.outerVerticalTable, boundaryLayer.innerVerticalTable)
214 boundaryLayer.horizontalSegmentTable = self.getHorizontalSegmentTableForXIntersectionsTable(
215 boundaryLayer.outerHorizontalTable)
216 boundaryLayer.verticalSegmentTable = self.getVerticalSegmentTableForXIntersectionsTable(
217 boundaryLayer.outerVerticalTable)
218 betweenPoints = getPointsFromSegmentTable(boundaryLayer.horizontalSegmentTable)
219 betweenPoints += getPointsFromSegmentTable(boundaryLayer.verticalSegmentTable)
220 innerPoints = euclidean.getPointsByHorizontalDictionary(self.millWidth, boundaryLayer.innerHorizontalTable)
221 innerPoints += euclidean.getPointsByVerticalDictionary(self.millWidth, boundaryLayer.innerVerticalTable)
223 for innerPoint in innerPoints:
224 innerPointTable[innerPoint] = None
225 boundaryLayer.innerLoops = []
226 boundaryLayer.outerLoops = []
227 millRadius = 0.75 * self.millWidth
228 loops = triangle_mesh.getDescendingAreaOrientedLoops(betweenPoints, betweenPoints, millRadius)
230 if isPointOfTableInLoop(loop, innerPointTable):
231 boundaryLayer.innerLoops.append(loop)
233 boundaryLayer.outerLoops.append(loop)
234 if self.repository.crossHatch.value and boundaryLayerIndex % 2 == 1:
235 boundaryLayer.segmentTable = boundaryLayer.verticalSegmentTable
237 boundaryLayer.segmentTable = boundaryLayer.horizontalSegmentTable
239 def getCraftedGcode(self, gcodeText, repository):
240 'Parse gcode text and store the mill gcode.'
241 self.repository = repository
242 self.lines = archive.getTextLines(gcodeText)
243 self.parseInitialization()
244 self.parseBoundaries()
245 for line in self.lines[self.lineIndex :]:
247 return self.distanceFeedRate.output.getvalue()
249 def getHorizontalSegmentTableForXIntersectionsTable( self, xIntersectionsTable ):
250 'Get the horizontal segment table from the xIntersectionsTable.'
251 horizontalSegmentTable = {}
252 xIntersectionsTableKeys = xIntersectionsTable.keys()
253 xIntersectionsTableKeys.sort()
254 for xIntersectionsTableKey in xIntersectionsTableKeys:
255 xIntersections = xIntersectionsTable[ xIntersectionsTableKey ]
256 segments = euclidean.getSegmentsFromXIntersections( xIntersections, xIntersectionsTableKey * self.millWidth )
257 horizontalSegmentTable[ xIntersectionsTableKey ] = segments
258 return horizontalSegmentTable
260 def getHorizontalXIntersectionsTable(self, loops):
261 'Get the horizontal x intersections table from the loops.'
262 horizontalXIntersectionsTable = {}
263 euclidean.addXIntersectionsFromLoopsForTable(loops, horizontalXIntersectionsTable, self.millWidth)
264 return horizontalXIntersectionsTable
266 def getVerticalSegmentTableForXIntersectionsTable( self, xIntersectionsTable ):
267 'Get the vertical segment table from the xIntersectionsTable which has the x and y swapped.'
268 verticalSegmentTable = {}
269 xIntersectionsTableKeys = xIntersectionsTable.keys()
270 xIntersectionsTableKeys.sort()
271 for xIntersectionsTableKey in xIntersectionsTableKeys:
272 xIntersections = xIntersectionsTable[ xIntersectionsTableKey ]
273 segments = euclidean.getSegmentsFromXIntersections( xIntersections, xIntersectionsTableKey * self.millWidth )
274 for segment in segments:
275 for endpoint in segment:
276 endpoint.point = complex( endpoint.point.imag, endpoint.point.real )
277 verticalSegmentTable[ xIntersectionsTableKey ] = segments
278 return verticalSegmentTable
280 def parseBoundaries(self):
281 'Parse the boundaries and add them to the boundary layers.'
284 for line in self.lines[self.lineIndex :]:
285 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
286 firstWord = gcodec.getFirstWord(splitLine)
287 if firstWord == '(</boundaryPerimeter>)':
289 elif firstWord == '(<boundaryPoint>':
290 location = gcodec.getLocationFromSplitLine(None, splitLine)
291 if boundaryLoop == None:
293 boundaryLayer.loops.append(boundaryLoop)
294 boundaryLoop.append(location.dropAxis())
295 elif firstWord == '(<layer>':
296 boundaryLayer = euclidean.LoopLayer(float(splitLine[1]))
297 self.boundaryLayers.append(boundaryLayer)
298 if len(self.boundaryLayers) < 2:
300 for boundaryLayer in self.boundaryLayers:
301 boundaryLayer.innerOutsetLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.loopInnerOutset)
302 boundaryLayer.outerOutsetLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.loopOuterOutset)
303 boundaryLayer.innerHorizontalTable = self.getHorizontalXIntersectionsTable( boundaryLayer.innerOutsetLoops )
304 boundaryLayer.outerHorizontalTable = self.getHorizontalXIntersectionsTable( boundaryLayer.outerOutsetLoops )
305 boundaryLayer.innerVerticalTable = self.getHorizontalXIntersectionsTable( euclidean.getDiagonalFlippedLoops( boundaryLayer.innerOutsetLoops ) )
306 boundaryLayer.outerVerticalTable = self.getHorizontalXIntersectionsTable( euclidean.getDiagonalFlippedLoops( boundaryLayer.outerOutsetLoops ) )
307 for boundaryLayerIndex in xrange( len(self.boundaryLayers) - 2, - 1, - 1 ):
308 boundaryLayer = self.boundaryLayers[ boundaryLayerIndex ]
309 boundaryLayerBelow = self.boundaryLayers[ boundaryLayerIndex + 1 ]
310 euclidean.joinXIntersectionsTables( boundaryLayerBelow.outerHorizontalTable, boundaryLayer.outerHorizontalTable )
311 euclidean.joinXIntersectionsTables( boundaryLayerBelow.outerVerticalTable, boundaryLayer.outerVerticalTable )
312 for boundaryLayerIndex in xrange( 1, len(self.boundaryLayers) ):
313 boundaryLayer = self.boundaryLayers[ boundaryLayerIndex ]
314 boundaryLayerAbove = self.boundaryLayers[ boundaryLayerIndex - 1 ]
315 euclidean.joinXIntersectionsTables( boundaryLayerAbove.innerHorizontalTable, boundaryLayer.innerHorizontalTable )
316 euclidean.joinXIntersectionsTables( boundaryLayerAbove.innerVerticalTable, boundaryLayer.innerVerticalTable )
317 for boundaryLayerIndex in xrange( len(self.boundaryLayers) ):
318 self.addSegmentTableLoops(boundaryLayerIndex)
320 def parseInitialization(self):
321 'Parse gcode initialization and store the parameters.'
322 for self.lineIndex in xrange(len(self.lines)):
323 line = self.lines[self.lineIndex]
324 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
325 firstWord = gcodec.getFirstWord(splitLine)
326 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
327 if firstWord == '(</extruderInitialization>)':
328 self.distanceFeedRate.addTagBracketedProcedure('mill')
330 elif firstWord == '(<edgeWidth>':
331 self.edgeWidth = float(splitLine[1])
332 self.aroundWidth = 0.1 * self.edgeWidth
333 self.halfEdgeWidth = 0.5 * self.edgeWidth
334 self.millWidth = self.edgeWidth * self.repository.millWidthOverEdgeWidth.value
335 self.loopInnerOutset = self.halfEdgeWidth + self.edgeWidth * self.repository.loopInnerOutsetOverEdgeWidth.value
336 self.loopOuterOutset = self.halfEdgeWidth + self.edgeWidth * self.repository.loopOuterOutsetOverEdgeWidth.value
337 self.distanceFeedRate.addLine(line)
339 def parseLine(self, line):
340 'Parse a gcode line and add it to the mill skein.'
341 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
342 if len(splitLine) < 1:
344 firstWord = splitLine[0]
345 if firstWord == 'G1':
346 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
347 if self.isExtruderActive:
348 self.average.addValue(location.z)
349 if self.oldLocation != None:
350 euclidean.addValueSegmentToPixelTable( self.oldLocation.dropAxis(), location.dropAxis(), self.aroundPixelTable, None, self.aroundWidth )
351 self.oldLocation = location
352 elif firstWord == 'M101':
353 self.isExtruderActive = True
354 elif firstWord == 'M103':
355 self.isExtruderActive = False
356 elif firstWord == '(<layer>':
357 settings.printProgress(self.layerIndex, 'mill')
358 self.aroundPixelTable = {}
360 elif firstWord == '(</layer>)':
361 if len(self.boundaryLayers) > self.layerIndex:
362 self.addMillThreads()
364 self.distanceFeedRate.addLine(line)
368 'Display the mill dialog.'
369 if len(sys.argv) > 1:
370 writeOutput(' '.join(sys.argv[1 :]))
372 settings.startMainLoopFromConstructor(getNewRepository())
374 if __name__ == '__main__':