3 This page is in the table of contents.
4 Fill is a script to fill the edges of a gcode file.
6 The fill manual page is at:
7 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill
9 Allan Ecker aka The Masked Retriever has written the "Skeinforge Quicktip: Fill" at:
10 http://blog.thingiverse.com/2009/07/21/mysteries-of-skeinforge-fill/
13 The default 'Activate Fill' checkbox is off. When it is on, the functions described below will work, when it is off, the functions will not be called.
17 The diaphragm is a solid group of layers, at regular intervals. It can be used with a sparse infill to give the object watertight, horizontal compartments and/or a higher shear strength.
19 ====Diaphragm Period====
20 Default is one hundred.
22 Defines the number of layers between diaphrams.
24 ====Diaphragm Thickness====
25 Default is zero, because the diaphragm feature is rarely used.
27 Defines the number of layers the diaphram is composed of.
30 The shells interior edge loops. Adding extra shells makes the object stronger & heavier.
32 ====Extra Shells on Alternating Solid Layers====
35 Defines the number of extra shells, on the alternating solid layers.
37 ====Extra Shells on Base====
40 Defines the number of extra shells on the bottom, base layer and every even solid layer after that. Setting this to a different value than the "Extra Shells on Alternating Solid Layers" means the infill pattern will alternate, creating a strong interleaved bond even if the edge loop shrinks.
42 ====Extra Shells on Sparse Layer====
45 Defines the number of extra shells on the sparse layers. The solid layers are those at the top & bottom, and wherever the object has a plateau or overhang, the sparse layers are the layers in between.
48 ====Grid Circle Separation over Perimeter Width====
51 Defines the ratio of the amount the grid circle is inset over the edge width, the default is zero. With a value of zero the circles will touch, with a value of one two threads could be fitted between the circles.
53 ====Grid Extra Overlap====
56 Defines the amount of extra overlap added when extruding the grid to compensate for the fact that when the first thread going through a grid point is extruded, since there is nothing there yet for it to connect to it will shrink extra.
58 ====Grid Junction Separation over Octogon Radius At End====
61 Defines the ratio of the amount the grid square is increased in each direction over the extrusion width at the end. With a value of one or so the grid pattern will have large squares to go with the octogons.
63 ====Grid Junction Separation over Octogon Radius At Middle====
66 Defines the increase at the middle. If this value is different than the value at the end, the grid would have an accordion pattern, which would give it a higher shear strength.
68 ====Grid Junction Separation Band Height====
71 Defines the height of the bands of the accordion pattern.
74 ====Infill Pattern====
75 Default is 'Line', since it is quicker to generate and does not add extra movements for the extruder. The grid pattern has extra diagonal lines, so when choosing a grid option, set the infill solidity to 0.2 or less so that there is not too much plastic and the grid generation time, which increases with the third power of solidity, will be reasonable.
77 =====Grid Circular=====
78 When selected, the infill will be a grid of separated circles. Because the circles are separated, the pattern is weak, it only provides support for the top layer threads and some strength in the z direction. The flip side is that this infill does not warp the object, the object will get warped only by the walls.
80 Because this pattern turns the extruder on and off often, it is best to use a stepper motor extruder.
82 =====Grid Hexagonal=====
83 When selected, the infill will be a hexagonal grid. Because the grid is made with threads rather than with molding or milling, only a partial hexagon is possible, so the rectangular grid pattern is stronger.
85 =====Grid Rectangular=====
86 When selected, the infill will be a funky octogon square honeycomb like pattern which gives the object extra strength.
89 When selected, the infill will be made up of lines.
91 ====Infill Begin Rotation====
92 Default is forty five degrees, giving a diagonal infill.
94 Defines the amount the infill direction of the base and every second layer thereafter is rotated.
96 ====Infill Odd Layer Extra Rotation====
97 Default is ninety degrees, making the odd layer infill perpendicular to the base layer.
99 Defines the extra amount the infill direction of the odd layers is rotated compared to the base layer.
101 ====Infill Begin Rotation Repeat====
102 Default is one, giving alternating cross hatching.
104 Defines the number of layers that the infill begin rotation will repeat. With a value higher than one, the infill will go in one direction more often, giving the object more strength in one direction and less in the other, this is useful for beams and cantilevers.
106 ====Infill Perimeter Overlap====
109 Defines the amount the infill overlaps the edge over the average of the edge and infill width. The higher the value the more the infill will overlap the edge, and the thicker join between the infill and the edge. If the value is too high, the join will be so thick that the nozzle will run plow through the join below making a mess, also when it is above 0.45 fill may not be able to create infill correctly. If you want to stretch the infill a lot, set 'Path Stretch over Perimeter Width' in stretch to a high value.
111 ====Infill Solidity====
114 Defines the solidity of the infill, this is the most important setting in fill. A value of one means the infill lines will be right beside each other, resulting in a solid, strong, heavy shape which takes a long time to extrude. A low value means the infill will be sparse, the interior will be mosty empty space, the object will be weak, light and quick to build.
116 ====Infill Width over Thickness====
119 Defines the ratio of the infill width over the layer height. The higher the value the wider apart the infill will be and therefore the sparser the infill will be.
124 Defines the sharpest angle that a thread is allowed to make before it is separated into two threads. If 'Sharpest Angle' is too low, the extruder will stop and start often, slowing printing and putting more wear and tear on the extruder. If 'Sharpest Angle' is too high, then threads will almost double back on themselves, leading to bumps in the fill, and sometimes filament being dragged by the nozzle.
126 This parameter is used in fill, raft and skin.
128 ===Solid Surface Thickness===
131 Defines the number of solid layers that are at the bottom, top, plateaus and overhang. With a value of zero, the entire object will be composed of a sparse infill, and water could flow right through it. With a value of one, water will leak slowly through the surface and with a value of three, the object could be watertight. The higher the solid surface thickness, the stronger and heavier the object will be.
133 ===Start From Choice===
134 Default is 'Lower Left'.
136 Defines where each layer starts from.
139 When selected the layer will start from the lower left corner. This is to extrude in round robin fashion so that the first extrusion will be deposited on the coolest part of the last layer. The reason for this is described at:
140 http://hydraraptor.blogspot.com/2010/12/round-robin.html
143 When selected the layer will start from the closest point to the end of the last layer. This leads to less stringing, but the first extrusion will be deposited on the hottest part of the last layer which leads to melting problems. So this option is deprecated, eventually this option will be removed and the layers will always start from the lower left.
145 ===Surrounding Angle===
148 Defines the angle that the surrounding layers around the infill are expanded.
150 To decide whether or not the infill should be sparse or solid, fill looks at the 'Solid Surface Thickness' surrounding layers above and below the infill. If any of the expanded layers above or below the infill do not cover the infill, then the infill will be solid in that region. The layers are expanded by the height difference times the tangent of the surrounding angle, which is from the vertical. For example, if the model is a wedge with a wall angle less than the surrounding angle, the interior layers (those which are not on the bottom or top) will be sparse. If the wall angle is greater than the surrounding angle, the interior layers will be solid.
152 The time required to examine the surrounding layers increases with the surrounding angle, so the surrounding angle is limited to eighty degrees, regardless of the input value.
154 If you have an organic shape with gently sloping surfaces; if the surrounding angle is set too high, then too many layers will be sparse. If the surrounding angle is too low, then too many layers will be solid and the extruder may end up plowing through previous layers:
155 http://hydraraptor.blogspot.com/2008/08/bearing-fruit.html
157 ===Thread Sequence Choice===
158 The 'Thread Sequence Choice' is the sequence in which the threads will be extruded on the second and higher layers. There are three kinds of thread, the edge threads on the outside of the object, the loop threads aka inner shell threads, and the interior infill threads. The first layer thread sequence is 'Perimeter > Loops > Infill'.
160 The default choice is 'Perimeter > Loops > Infill', which the default stretch parameters are based on. If you change from the default sequence choice setting of edge, then loops, then infill, the optimal stretch thread parameters would also be different. In general, if the infill is extruded first, the infill would have to be stretched more so that even after the filament shrinkage, it would still be long enough to connect to the loop or edge. The six sequence combinations follow below.
162 ====Infill > Loops > Perimeter====
163 ====Infill > Perimeter > Loops====
164 ====Loops > Infill > Perimeter====
165 ====Loops > Perimeter > Infill====
166 ====Perimeter > Infill > Loops====
167 ====Perimeter > Loops > Infill====
170 The following examples fill the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and fill.py.
173 This brings up the fill dialog.
175 > python fill.py Screw Holder Bottom.stl
176 The fill tool is parsing the file:
177 Screw Holder Bottom.stl
179 The fill tool has created the file:
180 .. Screw Holder Bottom_fill.gcode
184 from __future__ import absolute_import
186 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
187 from fabmetheus_utilities.geometry.solids import triangle_mesh
188 from fabmetheus_utilities.vector3 import Vector3
189 from fabmetheus_utilities import archive
190 from fabmetheus_utilities import euclidean
191 from fabmetheus_utilities import gcodec
192 from fabmetheus_utilities import intercircle
193 from fabmetheus_utilities import settings
194 from skeinforge_application.skeinforge_utilities import skeinforge_craft
195 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
196 from skeinforge_application.skeinforge_utilities import skeinforge_profile
201 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
202 __date__ = '$Date: 2008/28/04 $'
203 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
207 def addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, gridSearchRadius, isBothOrNone, isDoubleJunction, isJunctionWide, paths, pixelTable, width ):
208 'Add the path around the grid point.'
209 closestPathIndex = None
210 aroundIntersectionPaths = []
211 for aroundIndex in xrange( len(arounds) ):
212 loop = arounds[ aroundIndex ]
213 for pointIndex in xrange(len(loop)):
214 pointFirst = loop[pointIndex]
215 pointSecond = loop[(pointIndex + 1) % len(loop)]
216 yIntersection = euclidean.getYIntersectionIfExists( pointFirst, pointSecond, gridPoint.real )
217 addYIntersectionPathToList( aroundIndex, pointIndex, gridPoint.imag, yIntersection, aroundIntersectionPaths )
218 if len( aroundIntersectionPaths ) < 2:
219 print('Warning, aroundIntersectionPaths is less than 2 in fill.')
220 print(aroundIntersectionPaths)
223 yCloseToCenterArounds = getClosestOppositeIntersectionPaths(aroundIntersectionPaths)
224 if len(yCloseToCenterArounds) < 2:
226 segmentFirstY = min( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y )
227 segmentSecondY = max( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y )
228 yIntersectionPaths = []
229 gridPixel = euclidean.getStepKeyFromPoint( gridPoint / width )
230 segmentFirstPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentFirstY ) / width )
231 segmentSecondPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentSecondY ) / width )
233 addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel )
234 addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel )
235 for pathIndex in pathIndexTable.keys():
236 path = paths[ pathIndex ]
237 for pointIndex in xrange( len(path) - 1 ):
238 pointFirst = path[pointIndex]
239 pointSecond = path[pointIndex + 1]
240 yIntersection = getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, pointFirst, pointSecond, gridPoint.real )
241 addYIntersectionPathToList( pathIndex, pointIndex, gridPoint.imag, yIntersection, yIntersectionPaths )
242 if len( yIntersectionPaths ) < 1:
244 yCloseToCenterPaths = []
246 yCloseToCenterPaths = getClosestOppositeIntersectionPaths( yIntersectionPaths )
248 yIntersectionPaths.sort( compareDistanceFromCenter )
249 yCloseToCenterPaths = [ yIntersectionPaths[0] ]
250 for yCloseToCenterPath in yCloseToCenterPaths:
251 setIsOutside( yCloseToCenterPath, aroundIntersectionPaths )
252 if len( yCloseToCenterPaths ) < 2:
253 yCloseToCenterPaths[0].gridPoint = gridPoint
254 insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yCloseToCenterPaths[0], width )
256 plusMinusSign = getPlusMinusSign( yCloseToCenterPaths[1].y - yCloseToCenterPaths[0].y )
257 yCloseToCenterPaths[0].gridPoint = complex( gridPoint.real, gridPoint.imag - plusMinusSign * gridPointInsetY )
258 yCloseToCenterPaths[1].gridPoint = complex( gridPoint.real, gridPoint.imag + plusMinusSign * gridPointInsetY )
259 yCloseToCenterPaths.sort( comparePointIndexDescending )
260 insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, yCloseToCenterPaths[0], yCloseToCenterPaths[1], isBothOrNone, isJunctionWide, paths, pixelTable, width )
262 def addInfillBoundary(infillBoundary, nestedRings):
263 'Add infill boundary to the nested ring that contains it.'
264 infillPoint = infillBoundary[0]
265 for nestedRing in nestedRings:
266 if euclidean.isPointInsideLoop(nestedRing.boundary, infillPoint):
267 nestedRing.infillBoundaries.append(infillBoundary)
270 def addLoop(infillWidth, infillPaths, loop, rotationPlaneAngle):
271 'Add simplified path to fill.'
272 simplifiedLoop = euclidean.getSimplifiedLoop(loop, infillWidth)
273 if len(simplifiedLoop) < 2:
275 simplifiedLoop.append(simplifiedLoop[0])
276 planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedLoop)
277 infillPaths.append(planeRotated)
279 def addPath(infillWidth, infillPaths, path, rotationPlaneAngle):
280 'Add simplified path to fill.'
281 simplifiedPath = euclidean.getSimplifiedPath(path, infillWidth)
282 if len(simplifiedPath) < 2:
284 planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedPath)
285 infillPaths.append(planeRotated)
287 def addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel ):
288 'Add the path index of the closest segment found toward the second segment.'
289 for yStep in xrange( gridPixel[1], segmentFirstPixel[1] - 1, - 1 ):
290 if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ):
293 def addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel ):
294 'Add the path index of the closest segment found toward the second segment.'
295 for yStep in xrange( gridPixel[1], segmentSecondPixel[1] + 1 ):
296 if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ):
299 def addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width ):
300 'Add a point to a path and the pixel table.'
301 pointIndexMinusOne = pointIndex - 1
302 if pointIndex < len(path) and pointIndexMinusOne >= 0:
304 begin = path[ pointIndexMinusOne ]
305 end = path[pointIndex]
306 euclidean.addValueSegmentToPixelTable( begin, end, segmentTable, pathIndex, width )
307 euclidean.removePixelTableFromPixelTable( segmentTable, pixelTable )
308 if pointIndexMinusOne >= 0:
309 begin = path[ pointIndexMinusOne ]
310 euclidean.addValueSegmentToPixelTable( begin, point, pixelTable, pathIndex, width )
311 if pointIndex < len(path):
312 end = path[pointIndex]
313 euclidean.addValueSegmentToPixelTable( point, end, pixelTable, pathIndex, width )
314 path.insert( pointIndex, point )
316 def addPointOnPathIfFree( path, pathIndex, pixelTable, point, pointIndex, width ):
317 'Add the closest point to a path, if the point added to a path is free.'
318 if isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ):
319 addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width )
321 def addSparseEndpoints(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, solidSurfaceThickness, surroundingXIntersections):
322 'Add sparse endpoints.'
323 segments = horizontalSegmentsDictionary[horizontalSegmentsDictionaryKey]
324 for segment in segments:
325 addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections)
327 def addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections):
328 'Add sparse endpoints from a segment.'
329 if infillSolidity > 0.0:
330 if int(round(round(float(horizontalSegmentsDictionaryKey) * infillSolidity) / infillSolidity)) == horizontalSegmentsDictionaryKey:
333 if abs(segment[0].point - segment[1].point) < doubleInfillWidth:
336 if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey - 1, segment):
339 if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey + 1, segment):
342 if solidSurfaceThickness == 0:
343 removedEndpoints += segment
345 if isSegmentCompletelyInAnIntersection(segment, surroundingXIntersections):
346 removedEndpoints += segment
350 def addYIntersectionPathToList( pathIndex, pointIndex, y, yIntersection, yIntersectionPaths ):
351 'Add the y intersection path to the y intersection paths.'
352 if yIntersection == None:
354 yIntersectionPath = YIntersectionPath( pathIndex, pointIndex, yIntersection )
355 yIntersectionPath.yMinusCenter = yIntersection - y
356 yIntersectionPaths.append( yIntersectionPath )
358 def compareDistanceFromCenter(self, other):
359 'Get comparison in order to sort y intersections in ascending order of distance from the center.'
360 distanceFromCenter = abs( self.yMinusCenter )
361 distanceFromCenterOther = abs( other.yMinusCenter )
362 if distanceFromCenter > distanceFromCenterOther:
364 if distanceFromCenter < distanceFromCenterOther:
368 def comparePointIndexDescending(self, other):
369 'Get comparison in order to sort y intersections in descending order of point index.'
370 if self.pointIndex > other.pointIndex:
372 if self.pointIndex < other.pointIndex:
376 def createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded):
377 'Create extra fill loops.'
378 for innerNestedRing in nestedRing.innerNestedRings:
379 createFillForSurroundings(innerNestedRing.innerNestedRings, radius, radiusAround, shouldExtraLoopsBeAdded)
380 allFillLoops = intercircle.getInsetSeparateLoopsFromAroundLoops(nestedRing.getLoopsToBeFilled(), radius, max(1.4 * radius, radiusAround))
381 if len(allFillLoops) < 1:
383 if shouldExtraLoopsBeAdded:
384 nestedRing.extraLoops += allFillLoops
385 nestedRing.penultimateFillLoops = nestedRing.lastFillLoops
386 nestedRing.lastFillLoops = allFillLoops
388 def createFillForSurroundings(nestedRings, radius, radiusAround, shouldExtraLoopsBeAdded):
389 'Create extra fill loops for nested rings.'
390 for nestedRing in nestedRings:
391 createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded)
393 def getAdditionalLength( path, point, pointIndex ):
394 'Get the additional length added by inserting a point into a path.'
396 return abs( point - path[0] )
397 if pointIndex == len(path):
398 return abs( point - path[-1] )
399 return abs( point - path[pointIndex - 1] ) + abs( point - path[pointIndex] ) - abs( path[pointIndex] - path[pointIndex - 1] )
401 def getClosestOppositeIntersectionPaths( yIntersectionPaths ):
402 'Get the close to center paths, starting with the first and an additional opposite if it exists.'
403 yIntersectionPaths.sort( compareDistanceFromCenter )
404 beforeFirst = yIntersectionPaths[0].yMinusCenter < 0.0
405 yCloseToCenterPaths = [ yIntersectionPaths[0] ]
406 for yIntersectionPath in yIntersectionPaths[1 :]:
407 beforeSecond = yIntersectionPath.yMinusCenter < 0.0
408 if beforeFirst != beforeSecond:
409 yCloseToCenterPaths.append( yIntersectionPath )
410 return yCloseToCenterPaths
411 return yCloseToCenterPaths
413 def getCraftedText( fileName, gcodeText = '', repository=None):
414 'Fill the inset file or gcode text.'
415 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
417 def getCraftedTextFromText(gcodeText, repository=None):
418 'Fill the inset gcode text.'
419 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'fill'):
421 if repository == None:
422 repository = settings.getReadRepository( FillRepository() )
423 if not repository.activateFill.value:
425 return FillSkein().getCraftedGcode( repository, gcodeText )
427 def getKeyIsInPixelTableAddValue( key, pathIndexTable, pixelTable ):
428 'Determine if the key is in the pixel table, and if it is and if the value is not None add it to the path index table.'
429 if key in pixelTable:
430 value = pixelTable[key]
432 pathIndexTable[value] = None
436 def getLowerLeftCorner(nestedRings):
437 'Get the lower left corner from the nestedRings.'
438 lowerLeftCorner = Vector3()
439 lowestRealPlusImaginary = 987654321.0
440 for nestedRing in nestedRings:
441 for point in nestedRing.boundary:
442 realPlusImaginary = point.real + point.imag
443 if realPlusImaginary < lowestRealPlusImaginary:
444 lowestRealPlusImaginary = realPlusImaginary
445 lowerLeftCorner.setToXYZ(point.real, point.imag, nestedRing.z)
446 return lowerLeftCorner
448 def getNewRepository():
449 'Get new repository.'
450 return FillRepository()
452 def getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width ):
453 'Get the points around the grid point that is junction wide that do not intersect.'
454 pointIndexPlusOne = yIntersectionPath.getPointIndexPlusOne()
455 path = yIntersectionPath.getPath(paths)
456 begin = path[ yIntersectionPath.pointIndex ]
457 end = path[ pointIndexPlusOne ]
458 plusMinusSign = getPlusMinusSign( end.real - begin.real )
460 gridPointXFirst = complex( yIntersectionPath.gridPoint.real - plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag )
461 gridPointXSecond = complex( yIntersectionPath.gridPoint.real + plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag )
462 if isAddedPointOnPathFree( path, pixelTable, gridPointXSecond, pointIndexPlusOne, width ):
463 if isAddedPointOnPathFree( path, pixelTable, gridPointXFirst, pointIndexPlusOne, width ):
464 return [ gridPointXSecond, gridPointXFirst ]
465 if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ):
466 return [ gridPointXSecond, yIntersectionPath.gridPoint ]
467 return [ gridPointXSecond ]
468 if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ):
469 return [ yIntersectionPath.gridPoint ]
472 def getPlusMinusSign(number):
473 'Get one if the number is zero or positive else negative one.'
478 def getWithLeastLength( path, point ):
479 'Insert a point into a path, at the index at which the path would be shortest.'
482 shortestPointIndex = None
483 shortestAdditionalLength = 999999999987654321.0
484 for pointIndex in xrange( len(path) + 1 ):
485 additionalLength = getAdditionalLength( path, point, pointIndex )
486 if additionalLength < shortestAdditionalLength:
487 shortestAdditionalLength = additionalLength
488 shortestPointIndex = pointIndex
489 return shortestPointIndex
491 def getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, beginComplex, endComplex, x ):
492 'Get the y intersection inside the y segment if it does, else none.'
493 yIntersection = euclidean.getYIntersectionIfExists( beginComplex, endComplex, x )
494 if yIntersection == None:
496 if yIntersection < min( segmentFirstY, segmentSecondY ):
498 if yIntersection <= max( segmentFirstY, segmentSecondY ):
502 def insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yIntersectionPath, width ):
503 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.'
504 linePath = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width )
505 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width )
507 def insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, intersectionPathFirst, intersectionPathSecond, isBothOrNone, isJunctionWide, paths, pixelTable, width ):
508 'Insert a pair of points around a pair of grid points.'
509 gridPointLineFirst = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width )
510 if len( gridPointLineFirst ) < 1:
513 intersectionPathSecond.gridPoint = gridPoint
514 insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, intersectionPathSecond, width )
516 gridPointLineSecond = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathSecond, width )
517 if len( gridPointLineSecond ) > 0:
518 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width )
519 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineSecond, paths, pixelTable, intersectionPathSecond, width )
523 originalGridPointFirst = intersectionPathFirst.gridPoint
524 intersectionPathFirst.gridPoint = gridPoint
525 gridPointLineFirstCenter = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width )
526 if len( gridPointLineFirstCenter ) > 0:
527 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirstCenter, paths, pixelTable, intersectionPathFirst, width )
529 intersectionPathFirst.gridPoint = originalGridPointFirst
530 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width )
532 def insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width ):
533 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.'
534 if len( linePath ) < 1:
536 if gridPoint in gridPoints:
537 gridPoints.remove( gridPoint )
538 intersectionBeginPoint = None
539 moreThanInset = 2.1 * gridPointInsetX
540 path = yIntersectionPath.getPath(paths)
541 begin = path[ yIntersectionPath.pointIndex ]
542 end = path[ yIntersectionPath.getPointIndexPlusOne() ]
543 if yIntersectionPath.isOutside:
544 distanceX = end.real - begin.real
545 if abs( distanceX ) > 2.1 * moreThanInset:
546 intersectionBeginXDistance = yIntersectionPath.gridPoint.real - begin.real
547 endIntersectionXDistance = end.real - yIntersectionPath.gridPoint.real
548 intersectionPoint = begin * endIntersectionXDistance / distanceX + end * intersectionBeginXDistance / distanceX
549 distanceYAbsoluteInset = max( abs( yIntersectionPath.gridPoint.imag - intersectionPoint.imag ), moreThanInset )
550 intersectionEndSegment = end - intersectionPoint
551 intersectionEndSegmentLength = abs( intersectionEndSegment )
552 if intersectionEndSegmentLength > 1.1 * distanceYAbsoluteInset:
553 intersectionEndPoint = intersectionPoint + intersectionEndSegment * distanceYAbsoluteInset / intersectionEndSegmentLength
554 path.insert( yIntersectionPath.getPointIndexPlusOne(), intersectionEndPoint )
555 intersectionBeginSegment = begin - intersectionPoint
556 intersectionBeginSegmentLength = abs( intersectionBeginSegment )
557 if intersectionBeginSegmentLength > 1.1 * distanceYAbsoluteInset:
558 intersectionBeginPoint = intersectionPoint + intersectionBeginSegment * distanceYAbsoluteInset / intersectionBeginSegmentLength
559 for point in linePath:
560 addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, point, yIntersectionPath.getPointIndexPlusOne(), width )
561 if intersectionBeginPoint != None:
562 addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, intersectionBeginPoint, yIntersectionPath.getPointIndexPlusOne(), width )
564 def isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ):
565 'Determine if the point added to a path is intersecting the pixel table or the path.'
566 if 0 < pointIndex < len(path):
567 if isSharpCorner( ( path[pointIndex - 1] ), point, ( path[pointIndex] ) ):
569 pointIndexMinusOne = pointIndex - 1
570 if pointIndexMinusOne >= 0:
572 begin = path[ pointIndexMinusOne ]
573 if pointIndex < len(path):
574 end = path[pointIndex]
575 euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width )
577 euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width )
578 if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ):
580 if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndexMinusOne ):
582 if pointIndex < len(path):
584 begin = path[pointIndex]
585 if pointIndexMinusOne >= 0:
586 end = path[ pointIndexMinusOne ]
587 euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width )
589 euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width )
590 if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ):
592 if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ):
596 def isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ):
597 'Determine if the point added to a path is intersecting the path by checking line intersection.'
598 segment = point - begin
599 segmentLength = abs(segment)
600 if segmentLength <= 0.0:
602 normalizedSegment = segment / segmentLength
603 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
604 pointRotated = segmentYMirror * point
605 beginRotated = segmentYMirror * begin
606 if euclidean.isXSegmentIntersectingPath( path[ max( 0, pointIndex - 20 ) : pointIndex ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag ):
608 return euclidean.isXSegmentIntersectingPath( path[ pointIndex + 1 : pointIndex + 21 ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag )
610 def isIntersectingLoopsPaths( loops, paths, pointBegin, pointEnd ):
611 'Determine if the segment between the first and second point is intersecting the loop list.'
612 normalizedSegment = pointEnd.dropAxis() - pointBegin.dropAxis()
613 normalizedSegmentLength = abs( normalizedSegment )
614 if normalizedSegmentLength == 0.0:
616 normalizedSegment /= normalizedSegmentLength
617 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
618 pointBeginRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointBegin )
619 pointEndRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointEnd )
620 if euclidean.isLoopListIntersectingInsideXSegment( loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ):
622 return euclidean.isXSegmentIntersectingPaths( paths, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag )
624 def isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, width):
625 'Add the closest removed endpoint to the path, with minimal twisting.'
626 closestDistanceSquared = 999999999987654321.0
627 closestPathIndex = None
628 for pathIndex in xrange(len(paths)):
629 path = paths[ pathIndex ]
630 for pointIndex in xrange(len(path)):
631 point = path[pointIndex]
632 distanceSquared = abs(point - removedEndpointPoint)
633 if distanceSquared < closestDistanceSquared:
634 closestDistanceSquared = distanceSquared
635 closestPathIndex = pathIndex
636 if closestPathIndex == None:
638 if closestDistanceSquared < 0.8 * layerInfillWidth * layerInfillWidth:
640 closestPath = paths[closestPathIndex]
641 closestPointIndex = getWithLeastLength(closestPath, removedEndpointPoint)
642 if isAddedPointOnPathFree(closestPath, pixelTable, removedEndpointPoint, closestPointIndex, width):
643 addPointOnPath(closestPath, closestPathIndex, pixelTable, removedEndpointPoint, closestPointIndex, width)
645 return isSidePointAdded(pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width)
647 def isSegmentAround(aroundSegmentsDictionary, aroundSegmentsDictionaryKey, segment):
648 'Determine if there is another segment around.'
649 if aroundSegmentsDictionaryKey not in aroundSegmentsDictionary:
651 for aroundSegment in aroundSegmentsDictionary[aroundSegmentsDictionaryKey]:
652 endpoint = aroundSegment[0]
653 if isSegmentInX(segment, endpoint.point.real, endpoint.otherEndpoint.point.real):
657 def isSegmentCompletelyInAnIntersection( segment, xIntersections ):
658 'Add sparse endpoints from a segment.'
659 for xIntersectionIndex in xrange( 0, len( xIntersections ), 2 ):
660 surroundingXFirst = xIntersections[ xIntersectionIndex ]
661 surroundingXSecond = xIntersections[ xIntersectionIndex + 1 ]
662 if euclidean.isSegmentCompletelyInX( segment, surroundingXFirst, surroundingXSecond ):
666 def isSegmentInX( segment, xFirst, xSecond ):
667 'Determine if the segment overlaps within x.'
668 segmentFirstX = segment[0].point.real
669 segmentSecondX = segment[1].point.real
670 if min( segmentFirstX, segmentSecondX ) > max( xFirst, xSecond ):
672 return max( segmentFirstX, segmentSecondX ) > min( xFirst, xSecond )
674 def isSharpCorner( beginComplex, centerComplex, endComplex ):
675 'Determine if the three complex points form a sharp corner.'
676 centerBeginComplex = beginComplex - centerComplex
677 centerEndComplex = endComplex - centerComplex
678 centerBeginLength = abs( centerBeginComplex )
679 centerEndLength = abs( centerEndComplex )
680 if centerBeginLength <= 0.0 or centerEndLength <= 0.0:
682 centerBeginComplex /= centerBeginLength
683 centerEndComplex /= centerEndLength
684 return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) > 0.9
686 def isSidePointAdded( pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width ):
687 'Add side point along with the closest removed endpoint to the path, with minimal twisting.'
688 if closestPointIndex <= 0 or closestPointIndex >= len( closestPath ):
690 pointBegin = closestPath[ closestPointIndex - 1 ]
691 pointEnd = closestPath[ closestPointIndex ]
692 removedEndpointPoint = removedEndpointPoint
695 removedMinusClosest = removedEndpointPoint - pointBegin
696 removedMinusClosestLength = abs( removedMinusClosest )
697 if removedMinusClosestLength <= 0.0:
699 removedMinusOther = removedEndpointPoint - pointEnd
700 removedMinusOtherLength = abs( removedMinusOther )
701 if removedMinusOtherLength <= 0.0:
703 insertPointAfter = None
704 insertPointBefore = None
705 if removedMinusOtherLength < removedMinusClosestLength:
707 farthest = pointBegin
708 removedMinusClosest = removedMinusOther
709 removedMinusClosestLength = removedMinusOtherLength
710 insertPointBefore = removedEndpointPoint
712 insertPointAfter = removedEndpointPoint
713 removedMinusClosestNormalized = removedMinusClosest / removedMinusClosestLength
714 perpendicular = removedMinusClosestNormalized * complex( 0.0, layerInfillWidth )
715 sidePoint = removedEndpointPoint + perpendicular
716 #extra check in case the line to the side point somehow slips by the line to the perpendicular
717 sidePointOther = removedEndpointPoint - perpendicular
718 if abs( sidePoint - farthest ) > abs( sidePointOther - farthest ):
719 perpendicular = - perpendicular
720 sidePoint = sidePointOther
722 closestSegmentTable = {}
723 toPerpendicularTable = {}
724 euclidean.addValueSegmentToPixelTable( pointBegin, pointEnd, maskTable, None, width )
725 euclidean.addValueSegmentToPixelTable( closest, removedEndpointPoint, closestSegmentTable, None, width )
726 euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width )
727 if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ):
728 sidePoint = removedEndpointPoint - perpendicular
729 toPerpendicularTable = {}
730 euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width )
731 if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ):
733 if insertPointBefore != None:
734 addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointBefore, closestPointIndex, width )
735 addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, sidePoint, closestPointIndex, width )
736 if insertPointAfter != None:
737 addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointAfter, closestPointIndex, width )
740 def removeEndpoints(layerInfillWidth, paths, pixelTable, removedEndpoints, aroundWidth):
741 'Remove endpoints which are added to the path.'
742 for removedEndpointIndex in xrange(len(removedEndpoints) -1, -1, -1):
743 removedEndpoint = removedEndpoints[removedEndpointIndex]
744 removedEndpointPoint = removedEndpoint.point
745 if isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, aroundWidth):
746 removedEndpoints.remove(removedEndpoint )
748 def setIsOutside( yCloseToCenterPath, yIntersectionPaths ):
749 'Determine if the yCloseToCenterPath is outside.'
750 beforeClose = yCloseToCenterPath.yMinusCenter < 0.0
751 for yIntersectionPath in yIntersectionPaths:
752 if yIntersectionPath != yCloseToCenterPath:
753 beforePath = yIntersectionPath.yMinusCenter < 0.0
754 if beforeClose == beforePath:
755 yCloseToCenterPath.isOutside = False
757 yCloseToCenterPath.isOutside = True
759 def writeOutput(fileName, shouldAnalyze=True):
760 'Fill an inset gcode file.'
761 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'fill', shouldAnalyze)
764 class FillRepository(object):
765 'A class to handle the fill settings.'
767 'Set the default settings, execute title & settings fileName.'
768 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.fill.html', self )
769 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Fill', self, '')
770 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill')
771 self.activateFill = settings.BooleanSetting().getFromValue('Activate Fill', self, True)
772 self.solidSurfaceTop = settings.BooleanSetting().getFromValue('Solid Surface Top', self, True)
773 self.overrideFirstLayerSequence = settings.BooleanSetting().getFromValue('Override First Layer Sequence', self, True)
774 settings.LabelSeparator().getFromRepository(self)
775 settings.LabelDisplay().getFromName('- Diaphragm -', self )
776 self.diaphragmPeriod = settings.IntSpin().getFromValue( 20, 'Diaphragm Period (layers):', self, 200, 100 )
777 self.diaphragmThickness = settings.IntSpin().getFromValue( 0, 'Diaphragm Thickness (layers):', self, 5, 0 )
778 settings.LabelSeparator().getFromRepository(self)
779 settings.LabelDisplay().getFromName('- Extra Shells -', self )
780 self.extraShellsAlternatingSolidLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Alternating Solid Layer (layers):', self, 3, 2 )
781 self.extraShellsBase = settings.IntSpin().getFromValue( 0, 'Extra Shells on Base (layers):', self, 3, 1 )
782 self.extraShellsSparseLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Sparse Layer (layers):', self, 3, 1 )
783 settings.LabelSeparator().getFromRepository(self)
784 settings.LabelDisplay().getFromName('- Grid -', self )
785 self.gridCircleSeparationOverEdgeWidth = settings.FloatSpin().getFromValue(0.0, 'Grid Circle Separation over Perimeter Width (ratio):', self, 1.0, 0.2)
786 self.gridExtraOverlap = settings.FloatSpin().getFromValue( 0.0, 'Grid Extra Overlap (ratio):', self, 0.5, 0.1 )
787 self.gridJunctionSeparationBandHeight = settings.IntSpin().getFromValue( 0, 'Grid Junction Separation Band Height (layers):', self, 20, 10 )
788 self.gridJunctionSeparationOverOctogonRadiusAtEnd = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At End (ratio):', self, 0.8, 0.0 )
789 self.gridJunctionSeparationOverOctogonRadiusAtMiddle = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At Middle (ratio):', self, 0.8, 0.0 )
790 settings.LabelSeparator().getFromRepository(self)
791 settings.LabelDisplay().getFromName('- Infill -', self )
792 self.infillBeginRotation = settings.FloatSpin().getFromValue( 0.0, 'Infill Begin Rotation (degrees):', self, 90.0, 45.0 )
793 self.infillBeginRotationRepeat = settings.IntSpin().getFromValue( 0, 'Infill Begin Rotation Repeat (layers):', self, 3, 1 )
794 self.infillOddLayerExtraRotation = settings.FloatSpin().getFromValue(30.0, 'Infill Odd Layer Extra Rotation (degrees):', self, 90.0, 90.0)
795 self.infillPatternLabel = settings.LabelDisplay().getFromName('Infill Pattern:', self )
796 infillLatentStringVar = settings.LatentStringVar()
797 self.infillPatternGridCircular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Circular', self, False )
798 self.infillPatternGridHexagonal = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Hexagonal', self, False )
799 self.infillPatternGridRectangular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Rectangular', self, False )
800 self.infillPatternLine = settings.Radio().getFromRadio( infillLatentStringVar, 'Line', self, True )
801 self.infillPerimeterOverlap = settings.FloatSpin().getFromValue( 0.0, 'Infill Perimeter Overlap (ratio):', self, 0.4, 0.15 )
802 self.infillSolidity = settings.FloatSpin().getFromValue( 0.04, 'Infill Solidity (ratio):', self, 0.3, 0.2 )
803 settings.LabelSeparator().getFromRepository(self)
804 self.sharpestAngle = settings.FloatSpin().getFromValue(50.0, 'Sharpest Angle (degrees):', self, 70.0, 60.0)
805 self.solidSurfaceThickness = settings.IntSpin().getFromValue(0, 'Solid Surface Thickness (layers):', self, 5, 3)
806 self.startFromChoice = settings.MenuButtonDisplay().getFromName('Start From Choice:', self)
807 self.startFromLowerLeft = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Lower Left', self, True)
808 self.startFromNearest = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Nearest', self, False)
809 self.surroundingAngle = settings.FloatSpin().getFromValue(30.0, 'Surrounding Angle (degrees):', self, 80.0, 60.0)
810 self.threadSequenceChoice = settings.MenuButtonDisplay().getFromName('Thread Sequence Choice:', self)
811 self.threadSequenceInfillLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Loops > Perimeter', self, False)
812 self.threadSequenceInfillPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Perimeter > Loops', self, False)
813 self.threadSequenceLoopsInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Infill > Perimeter', self, False)
814 self.threadSequenceLoopsPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Perimeter > Infill', self, True)
815 self.threadSequencePerimeterInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Infill > Loops', self, False)
816 self.threadSequencePerimeterLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Loops > Infill', self, False)
817 self.executeTitle = 'Fill'
820 'Fill button has been clicked.'
821 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
822 for fileName in fileNames:
823 writeOutput(fileName)
826 class FillSkein(object):
827 'A class to fill a skein of extrusions.'
830 self.distanceFeedRate = gcodec.DistanceFeedRate()
831 self.edgeWidth = None
832 self.extruderActive = False
833 self.fillInset = 0.18
835 self.lastExtraShells = - 1
837 self.oldLocation = None
838 self.oldOrderedLocation = None
839 self.rotatedLayer = None
840 self.rotatedLayers = []
841 self.shutdownLineIndex = sys.maxint
842 self.nestedRing = None
845 def addFill(self, layerIndex):
846 'Add fill to the carve layer.'
849 settings.printProgressByNumber(layerIndex, len(self.rotatedLayers), 'fill')
852 extraShells = self.repository.extraShellsSparseLayer.value
854 layerFillInset = self.fillInset
855 layerInfillSolidity = self.infillSolidity
856 layerRemainder = layerIndex % int(round(self.repository.diaphragmPeriod.value))
857 layerRotation = self.getLayerRotation(layerIndex)
859 reverseRotation = complex(layerRotation.real, - layerRotation.imag)
860 rotatedLayer = self.rotatedLayers[layerIndex]
861 self.isDoubleJunction = True
862 self.isJunctionWide = True
863 surroundingCarves = []
864 self.distanceFeedRate.addLine('(<layer> %s )' % rotatedLayer.z)
865 if layerRemainder >= int(round(self.repository.diaphragmThickness.value)):
866 for surroundingIndex in xrange(1, self.solidSurfaceThickness + 1):
867 if self.repository.solidSurfaceTop.value:
868 self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves)
869 self.addRotatedCarve(layerIndex, surroundingIndex, reverseRotation, surroundingCarves)
871 self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves)
872 self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves)
873 if len(surroundingCarves) < self.doubleSolidSurfaceThickness:
874 extraShells = self.repository.extraShellsAlternatingSolidLayer.value
875 if self.lastExtraShells != self.repository.extraShellsBase.value:
876 extraShells = self.repository.extraShellsBase.value
877 if rotatedLayer.rotation != None:
879 self.distanceFeedRate.addLine('(<bridgeRotation> %s )' % layerRotation)
880 self.distanceFeedRate.addLine('(<rotation> %s )' % layerRotation)
881 # aroundWidth = 0.34321 * self.infillWidth
882 aroundWidth = 0.24321 * self.infillWidth
883 doubleInfillWidth = 2.0 * self.infillWidth
884 gridPointInsetX = 0.5 * self.fillInset
885 self.lastExtraShells = extraShells
886 if self.repository.infillPatternGridHexagonal.value:
887 infillBeginRotationPolar = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation)
888 if abs(euclidean.getDotProduct(layerRotation, infillBeginRotationPolar)) < math.sqrt( 0.5):
889 layerInfillSolidity *= 0.5
890 self.isDoubleJunction = False
892 self.isJunctionWide = False
893 nestedRings = euclidean.getOrderedNestedRings(rotatedLayer.nestedRings)
894 radiusAround = 0.5 * min(self.infillWidth, self.edgeWidth)
895 createFillForSurroundings(nestedRings, self.edgeMinusHalfInfillWidth, radiusAround, False)
896 for extraShellIndex in xrange(extraShells):
897 createFillForSurroundings(nestedRings, self.infillWidth, radiusAround, True)
898 fillLoops = euclidean.getFillOfSurroundings(nestedRings, None)
899 rotatedLoops = euclidean.getRotatedComplexLists(reverseRotation, fillLoops)
900 infillDictionary = triangle_mesh.getInfillDictionary(arounds, aroundWidth, self.fillInset, self.infillWidth, pixelTable, rotatedLoops)
902 self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer)
904 self.horizontalSegmentsDictionary = {}
905 for infillDictionaryKey in infillDictionary.keys():
906 xIntersections = infillDictionary[infillDictionaryKey]
907 xIntersections.sort()
908 y = infillDictionaryKey * self.infillWidth
909 self.horizontalSegmentsDictionary[infillDictionaryKey] = euclidean.getSegmentsFromXIntersections(xIntersections, y)
910 self.surroundingXIntersectionsDictionary = {}
912 removedEndpoints = []
913 if len(surroundingCarves) >= self.doubleSolidSurfaceThickness:
914 if self.repository.infillPatternGridCircular.value and self.repository.infillSolidity.value > 0.0:
916 layerInfillSolidity = 0.0
917 xSurroundingIntersectionsDictionaries = [infillDictionary]
918 for surroundingCarve in surroundingCarves:
919 xSurroundingIntersectionsDictionary = {}
920 euclidean.addXIntersectionsFromLoopsForTable(surroundingCarve, xSurroundingIntersectionsDictionary, self.infillWidth)
921 xSurroundingIntersectionsDictionaries.append(xSurroundingIntersectionsDictionary)
922 self.surroundingXIntersectionsDictionary = euclidean.getIntersectionOfXIntersectionsTables(xSurroundingIntersectionsDictionaries)
923 for horizontalSegmentsDictionaryKey in self.horizontalSegmentsDictionary.keys():
924 if horizontalSegmentsDictionaryKey in self.surroundingXIntersectionsDictionary:
925 surroundingXIntersections = self.surroundingXIntersectionsDictionary[horizontalSegmentsDictionaryKey]
927 surroundingXIntersections = []
928 addSparseEndpoints(doubleInfillWidth, endpoints, self.horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, layerInfillSolidity, removedEndpoints, self.solidSurfaceThickness, surroundingXIntersections)
930 for segments in self.horizontalSegmentsDictionary.values():
931 for segment in segments:
933 paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.infillWidth, pixelTable, self.sharpestProduct, aroundWidth)
935 startAngle = euclidean.globalGoldenAngle * float(layerIndex)
936 for gridPoint in self.getGridPoints(fillLoops, reverseRotation):
937 self.addGridCircle(gridPoint, infillPaths, layerRotation, pixelTable, rotatedLoops, layerRotation, aroundWidth)
939 if self.isGridToBeExtruded():
941 arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, aroundWidth)
942 oldRemovedEndpointLength = len(removedEndpoints) + 1
943 while oldRemovedEndpointLength - len(removedEndpoints) > 0:
944 oldRemovedEndpointLength = len(removedEndpoints)
945 removeEndpoints(self.infillWidth, paths, pixelTable, removedEndpoints, aroundWidth)
946 paths = euclidean.getConnectedPaths(paths, pixelTable, self.sharpestProduct, aroundWidth)
948 addPath(self.infillWidth, infillPaths, path, layerRotation)
949 euclidean.transferPathsToNestedRings(nestedRings, infillPaths)
950 for fillLoop in fillLoops:
951 addInfillBoundary(fillLoop, nestedRings)
952 self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer)
954 def addGcodeFromThreadZ( self, thread, z ):
955 'Add a gcode thread to the output.'
956 self.distanceFeedRate.addGcodeFromThreadZ( thread, z )
958 def addGrid(self, arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, width):
959 'Add the grid to the infill layer.'
960 if len(surroundingCarves) < self.doubleSolidSurfaceThickness:
965 pathIndexBegin = len( explodedPaths )
966 for pointIndex in xrange( len(path) - 1 ):
967 pathSegment = [ path[pointIndex], path[pointIndex + 1] ]
968 explodedPaths.append( pathSegment )
969 pathGroups.append( ( pathIndexBegin, len( explodedPaths ) ) )
970 for pathIndex in xrange( len( explodedPaths ) ):
971 explodedPath = explodedPaths[ pathIndex ]
972 euclidean.addPathToPixelTable( explodedPath, pixelTable, pathIndex, width )
973 gridPoints = self.getGridPoints(fillLoops, reverseRotation)
974 gridPointInsetY = gridPointInsetX * ( 1.0 - self.repository.gridExtraOverlap.value )
975 if self.repository.infillPatternGridRectangular.value:
976 gridBandHeight = self.repository.gridJunctionSeparationBandHeight.value
977 gridLayerRemainder = ( layerIndex - self.solidSurfaceThickness ) % gridBandHeight
978 halfBandHeight = 0.5 * float( gridBandHeight )
979 halfBandHeightFloor = math.floor( halfBandHeight )
980 fromMiddle = math.floor( abs( gridLayerRemainder - halfBandHeight ) )
981 fromEnd = halfBandHeightFloor - fromMiddle
982 gridJunctionSeparation = self.gridJunctionEnd * fromMiddle + self.gridJunctionMiddle * fromEnd
983 gridJunctionSeparation /= halfBandHeightFloor
984 gridPointInsetX += gridJunctionSeparation
985 gridPointInsetY += gridJunctionSeparation
986 oldGridPointLength = len( gridPoints ) + 1
987 while oldGridPointLength - len( gridPoints ) > 0:
988 oldGridPointLength = len( gridPoints )
989 self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, True, explodedPaths, pixelTable, width )
990 oldGridPointLength = len( gridPoints ) + 1
991 while oldGridPointLength - len( gridPoints ) > 0:
992 oldGridPointLength = len( gridPoints )
993 self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, False, explodedPaths, pixelTable, width )
994 for pathGroupIndex in xrange( len( pathGroups ) ):
995 pathGroup = pathGroups[ pathGroupIndex ]
996 paths[ pathGroupIndex ] = []
997 for explodedPathIndex in xrange( pathGroup[0], pathGroup[1] ):
998 explodedPath = explodedPaths[ explodedPathIndex ]
999 if len( paths[ pathGroupIndex ] ) == 0:
1000 paths[ pathGroupIndex ] = explodedPath
1002 paths[ pathGroupIndex ] += explodedPath[1 :]
1004 def addGridCircle(self, center, infillPaths, layerRotation, pixelTable, rotatedLoops, startRotation, width):
1005 'Add circle to the grid.'
1006 startAngle = -math.atan2(startRotation.imag, startRotation.real)
1007 loop = euclidean.getComplexPolygon(center, self.gridCircleRadius, 17, startAngle)
1008 loopPixelDictionary = {}
1009 euclidean.addLoopToPixelTable(loop, loopPixelDictionary, width)
1010 if not euclidean.isPixelTableIntersecting(pixelTable, loopPixelDictionary):
1011 if euclidean.getIsInFilledRegion(rotatedLoops, euclidean.getLeftPoint(loop)):
1012 addLoop(self.infillWidth, infillPaths, loop, layerRotation)
1014 insideIndexPaths = []
1015 insideIndexPath = None
1016 for pointIndex, point in enumerate(loop):
1017 nextPoint = loop[(pointIndex + 1) % len(loop)]
1018 segmentDictionary = {}
1019 euclidean.addValueSegmentToPixelTable(point, nextPoint, segmentDictionary, None, width)
1020 euclidean.addSquareTwoToPixelDictionary(segmentDictionary, point, None, width)
1021 euclidean.addSquareTwoToPixelDictionary(segmentDictionary, nextPoint, None, width)
1022 shouldAddLoop = not euclidean.isPixelTableIntersecting(pixelTable, segmentDictionary)
1024 shouldAddLoop = euclidean.getIsInFilledRegion(rotatedLoops, point)
1026 if insideIndexPath == None:
1027 insideIndexPath = [pointIndex]
1028 insideIndexPaths.append(insideIndexPath)
1030 insideIndexPath.append(pointIndex)
1032 insideIndexPath = None
1033 if len(insideIndexPaths) > 1:
1034 insideIndexPathFirst = insideIndexPaths[0]
1035 insideIndexPathLast = insideIndexPaths[-1]
1036 if insideIndexPathFirst[0] == 0 and insideIndexPathLast[-1] == len(loop) - 1:
1037 insideIndexPaths[0] = insideIndexPathLast + insideIndexPathFirst
1038 del insideIndexPaths[-1]
1039 for insideIndexPath in insideIndexPaths:
1041 for insideIndex in insideIndexPath:
1043 path.append(loop[insideIndex])
1044 path.append(loop[(insideIndex + 1) % len(loop)])
1045 addPath(self.infillWidth, infillPaths, path, layerRotation)
1047 def addGridLinePoints( self, begin, end, gridPoints, gridRotationAngle, offset, y ):
1048 'Add the segments of one line of a grid to the infill.'
1049 if self.gridRadius == 0.0:
1051 gridXStep = int(math.floor((begin) / self.gridXStepSize)) - 3
1052 gridXOffset = offset + self.gridXStepSize * float(gridXStep)
1053 while gridXOffset < end:
1054 if gridXOffset >= begin:
1055 gridPointComplex = complex(gridXOffset, y) * gridRotationAngle
1056 if self.repository.infillPatternGridCircular.value or self.isPointInsideLineSegments(gridPointComplex):
1057 gridPoints.append(gridPointComplex)
1058 gridXStep = self.getNextGripXStep(gridXStep)
1059 gridXOffset = offset + self.gridXStepSize * float(gridXStep)
1061 def addRemainingGridPoints(
1062 self, arounds, gridPointInsetX, gridPointInsetY, gridPoints, isBothOrNone, paths, pixelTable, width):
1063 'Add the remaining grid points to the grid point list.'
1064 for gridPointIndex in xrange( len( gridPoints ) - 1, - 1, - 1 ):
1065 gridPoint = gridPoints[ gridPointIndex ]
1066 addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, self.gridRadius, isBothOrNone, self.isDoubleJunction, self.isJunctionWide, paths, pixelTable, width )
1068 def addRotatedCarve(self, currentLayer, layerDelta, reverseRotation, surroundingCarves):
1069 'Add a rotated carve to the surrounding carves.rotatedCarveDictionary'
1070 layerIndex = currentLayer + layerDelta
1071 if layerIndex < 0 or layerIndex >= len(self.rotatedLayers):
1073 layerDifference = abs(layerDelta)
1074 rotatedLayer = self.rotatedLayers[layerIndex]
1075 if layerDifference in rotatedLayer.rotatedCarveDictionary:
1076 surroundingCarves.append(rotatedLayer.rotatedCarveDictionary[layerDifference])
1078 nestedRings = rotatedLayer.nestedRings
1080 for nestedRing in nestedRings:
1081 planeRotatedLoop = euclidean.getRotatedComplexes(reverseRotation, nestedRing.boundary)
1082 rotatedCarve.append(planeRotatedLoop)
1083 outsetRadius = float(layerDifference) * self.layerHeight * self.surroundingSlope - self.edgeWidth
1084 if outsetRadius > 0.0:
1085 rotatedCarve = intercircle.getInsetSeparateLoopsFromAroundLoops(rotatedCarve, -outsetRadius, self.layerHeight)
1086 surroundingCarves.append(rotatedCarve)
1087 rotatedLayer.rotatedCarveDictionary[layerDifference] = rotatedCarve
1089 def addThreadsBridgeLayer(self, layerIndex, nestedRings, rotatedLayer, testLoops=None):
1090 'Add the threads, add the bridge end & the layer end tag.'
1091 if self.oldOrderedLocation == None or self.repository.startFromLowerLeft.value:
1092 self.oldOrderedLocation = getLowerLeftCorner(nestedRings)
1093 extrusionHalfWidth = 0.5 * self.infillWidth
1094 threadSequence = self.threadSequence
1095 if layerIndex < 1 and self.repository.overrideFirstLayerSequence.value:
1096 threadSequence = ['edge', 'loops', 'infill']
1097 euclidean.addToThreadsRemove(extrusionHalfWidth, nestedRings, self.oldOrderedLocation, self, threadSequence)
1098 if testLoops != None:
1099 for testLoop in testLoops:
1100 self.addGcodeFromThreadZ(testLoop, self.oldOrderedLocation.z)
1101 self.distanceFeedRate.addLine('(</rotation>)')
1102 if rotatedLayer.rotation != None:
1103 self.distanceFeedRate.addLine('(</bridgeRotation>)')
1104 self.distanceFeedRate.addLine('(</layer>)')
1106 def addToThread(self, location):
1107 'Add a location to thread.'
1108 if self.oldLocation == None:
1111 self.nestedRing.addToLoop( location )
1113 if self.thread == None:
1114 self.thread = [ self.oldLocation.dropAxis() ]
1115 self.nestedRing.edgePaths.append(self.thread)
1116 self.thread.append(location.dropAxis())
1118 def getCraftedGcode( self, repository, gcodeText ):
1119 'Parse gcode text and store the bevel gcode.'
1120 self.repository = repository
1121 self.lines = archive.getTextLines(gcodeText)
1122 self.sharpestProduct = math.sin(math.radians(repository.sharpestAngle.value))
1123 self.threadSequence = None
1124 if repository.threadSequenceInfillLoops.value:
1125 self.threadSequence = ['infill', 'loops', 'edge']
1126 if repository.threadSequenceInfillPerimeter.value:
1127 self.threadSequence = ['infill', 'edge', 'loops']
1128 if repository.threadSequenceLoopsInfill.value:
1129 self.threadSequence = ['loops', 'infill', 'edge']
1130 if repository.threadSequenceLoopsPerimeter.value:
1131 self.threadSequence = ['loops', 'edge', 'infill']
1132 if repository.threadSequencePerimeterInfill.value:
1133 self.threadSequence = ['edge', 'infill', 'loops']
1134 if repository.threadSequencePerimeterLoops.value:
1135 self.threadSequence = ['edge', 'loops', 'infill']
1136 if self.repository.infillPerimeterOverlap.value > 0.45:
1138 print('!!! WARNING !!!')
1139 print('"Infill Perimeter Overlap" is greater than 0.45, which may create problems with the infill, like threads going through empty space and/or the extruder switching on and off a lot.')
1140 print('If you want to stretch the infill a lot, set "Path Stretch over Perimeter Width" in stretch to a high value instead of setting "Infill Perimeter Overlap" to a high value.')
1142 self.parseInitialization()
1143 if self.edgeWidth == None:
1144 print('Warning, nothing will be done because self.edgeWidth in getCraftedGcode in FillSkein was None.')
1146 self.fillInset = self.infillWidth - self.infillWidth * self.repository.infillPerimeterOverlap.value
1147 self.infillSolidity = repository.infillSolidity.value
1148 self.edgeMinusHalfInfillWidth = self.edgeWidth - 0.5 * self.infillWidth
1149 if self.isGridToBeExtruded():
1150 self.setGridVariables(repository)
1151 self.infillBeginRotation = math.radians( repository.infillBeginRotation.value )
1152 self.infillOddLayerExtraRotation = math.radians( repository.infillOddLayerExtraRotation.value )
1153 self.solidSurfaceThickness = int( round( self.repository.solidSurfaceThickness.value ) )
1154 self.doubleSolidSurfaceThickness = self.solidSurfaceThickness + self.solidSurfaceThickness
1155 for lineIndex in xrange(self.lineIndex, len(self.lines)):
1156 self.parseLine( lineIndex )
1157 for layerIndex in xrange(len(self.rotatedLayers)):
1158 self.addFill(layerIndex)
1159 self.distanceFeedRate.addLines( self.lines[ self.shutdownLineIndex : ] )
1160 return self.distanceFeedRate.output.getvalue()
1162 def getGridPoints(self, fillLoops, reverseRotation):
1163 'Get the grid points.'
1164 if self.infillSolidity > 0.8:
1166 rotationBaseAngle = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation)
1167 reverseRotationBaseAngle = complex(rotationBaseAngle.real, - rotationBaseAngle.imag)
1168 gridRotationAngle = reverseRotation * rotationBaseAngle
1169 slightlyGreaterThanFillInset = intercircle.globalIntercircleMultiplier * self.gridInset
1170 triangle_mesh.sortLoopsInOrderOfArea(True, fillLoops)
1171 rotatedLoops = euclidean.getRotatedComplexLists(reverseRotationBaseAngle, fillLoops)
1172 if self.repository.infillPatternGridCircular.value:
1173 return self.getGridPointsByLoops(
1174 gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, -self.gridCircleRadius))
1175 return self.getGridPointsByLoops(gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, self.gridInset))
1177 def getGridPointsByLoops(self, gridRotationAngle, loops):
1178 'Get the grid points by loops.'
1179 gridIntersectionsDictionary = {}
1181 euclidean.addXIntersectionsFromLoopsForTable(loops, gridIntersectionsDictionary, self.gridRadius)
1182 for gridIntersectionsKey in gridIntersectionsDictionary:
1183 y = gridIntersectionsKey * self.gridRadius + self.gridRadius * 0.5
1184 gridIntersections = gridIntersectionsDictionary[gridIntersectionsKey]
1185 gridIntersections.sort()
1186 gridIntersectionsLength = len(gridIntersections)
1187 if gridIntersectionsLength % 2 == 1:
1188 gridIntersectionsLength -= 1
1189 for gridIntersectionIndex in xrange(0, gridIntersectionsLength, 2):
1190 begin = gridIntersections[gridIntersectionIndex]
1191 end = gridIntersections[gridIntersectionIndex + 1]
1192 offset = self.offsetMultiplier * (gridIntersectionsKey % 2) + self.offsetBaseX
1193 self.addGridLinePoints(begin, end, gridPoints, gridRotationAngle, offset, y)
1196 def getLayerRotation(self, layerIndex):
1197 'Get the layer rotation.'
1198 rotation = self.rotatedLayers[layerIndex].rotation
1199 if rotation != None:
1201 infillBeginRotationRepeat = self.repository.infillBeginRotationRepeat.value
1202 infillOddLayerRotationMultiplier = float( layerIndex % ( infillBeginRotationRepeat + 1 ) == infillBeginRotationRepeat )
1203 layerAngle = self.infillBeginRotation + infillOddLayerRotationMultiplier * self.infillOddLayerExtraRotation
1204 return euclidean.getWiddershinsUnitPolar(layerAngle)
1206 def getNextGripXStep( self, gridXStep ):
1207 'Get the next grid x step, increment by an extra one every three if hexagonal grid is chosen.'
1209 if self.repository.infillPatternGridHexagonal.value:
1210 if gridXStep % 3 == 0:
1214 def isGridToBeExtruded(self):
1215 'Determine if the grid is to be extruded.'
1216 if self.repository.infillPatternLine.value:
1218 return self.repository.infillSolidity.value > 0.0
1220 def isPointInsideLineSegments( self, gridPoint ):
1221 'Is the point inside the line segments of the loops.'
1222 if self.solidSurfaceThickness <= 0:
1224 fillLine = int(round(gridPoint.imag / self.infillWidth))
1225 if fillLine not in self.horizontalSegmentsDictionary:
1227 if fillLine not in self.surroundingXIntersectionsDictionary:
1229 lineSegments = self.horizontalSegmentsDictionary[fillLine]
1230 surroundingXIntersections = self.surroundingXIntersectionsDictionary[fillLine]
1231 for lineSegment in lineSegments:
1232 if isSegmentCompletelyInAnIntersection(lineSegment, surroundingXIntersections ):
1233 xFirst = lineSegment[0].point.real
1234 xSecond = lineSegment[1].point.real
1235 if min(xFirst, xSecond) < gridPoint.real < max(xFirst, xSecond):
1239 def linearMove( self, splitLine ):
1240 'Add a linear move to the thread.'
1241 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
1242 if self.extruderActive:
1243 self.addToThread( location )
1244 self.oldLocation = location
1246 def parseInitialization(self):
1247 'Parse gcode initialization and store the parameters.'
1248 for self.lineIndex in xrange(len(self.lines)):
1249 line = self.lines[self.lineIndex]
1250 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
1251 firstWord = gcodec.getFirstWord(splitLine)
1252 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
1253 if firstWord == '(<crafting>)':
1254 self.distanceFeedRate.addLine(line)
1256 elif firstWord == '(<infillWidth>':
1257 self.infillWidth = float(splitLine[1])
1258 elif firstWord == '(<layerHeight>':
1259 self.layerHeight = float(splitLine[1])
1260 self.surroundingSlope = math.tan(math.radians(min(self.repository.surroundingAngle.value, 80.0)))
1261 self.distanceFeedRate.addTagRoundedLine('infillPerimeterOverlap', self.repository.infillPerimeterOverlap.value)
1262 self.distanceFeedRate.addTagRoundedLine('sharpestProduct', self.sharpestProduct)
1263 elif firstWord == '(<edgeWidth>':
1264 self.edgeWidth = float(splitLine[1])
1265 threadSequenceString = ' '.join( self.threadSequence )
1266 self.distanceFeedRate.addTagBracketedLine('threadSequenceString', threadSequenceString )
1267 elif firstWord == '(</extruderInitialization>)':
1268 self.distanceFeedRate.addTagBracketedProcedure('fill')
1269 self.distanceFeedRate.addLine(line)
1271 def parseLine( self, lineIndex ):
1272 'Parse a gcode line and add it to the fill skein.'
1273 line = self.lines[lineIndex]
1274 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
1275 if len(splitLine) < 1:
1277 firstWord = splitLine[0]
1278 if firstWord == 'G1':
1279 self.linearMove(splitLine)
1280 elif firstWord == 'M101':
1281 self.extruderActive = True
1282 elif firstWord == 'M103':
1283 self.extruderActive = False
1286 elif firstWord == '(<boundaryPerimeter>)':
1287 self.nestedRing = euclidean.NestedBand()
1288 self.rotatedLayer.nestedRings.append( self.nestedRing )
1289 elif firstWord == '(</boundaryPerimeter>)':
1290 self.nestedRing = None
1291 elif firstWord == '(<boundaryPoint>':
1292 location = gcodec.getLocationFromSplitLine(None, splitLine)
1293 self.nestedRing.addToBoundary( location )
1294 elif firstWord == '(<bridgeRotation>':
1295 self.rotatedLayer.rotation = gcodec.getRotationBySplitLine(splitLine)
1296 elif firstWord == '(</crafting>)':
1297 self.shutdownLineIndex = lineIndex
1298 elif firstWord == '(<layer>':
1299 self.rotatedLayer = RotatedLayer(float(splitLine[1]))
1300 self.rotatedLayers.append( self.rotatedLayer )
1302 elif firstWord == '(<edge>':
1305 def setGridVariables( self, repository ):
1306 'Set the grid variables.'
1307 self.gridInset = 1.2 * self.infillWidth
1308 self.gridRadius = self.infillWidth / self.infillSolidity
1309 self.gridXStepSize = 2.0 * self.gridRadius
1310 self.offsetMultiplier = self.gridRadius
1311 if self.repository.infillPatternGridHexagonal.value:
1312 self.gridXStepSize = 4.0 / 3.0 * self.gridRadius
1313 self.offsetMultiplier = 1.5 * self.gridXStepSize
1314 if self.repository.infillPatternGridCircular.value:
1315 self.gridRadius += self.gridRadius
1316 self.gridXStepSize = self.gridRadius / math.sqrt(.75)
1317 self.offsetMultiplier = 0.5 * self.gridXStepSize
1318 circleInsetOverEdgeWidth = repository.gridCircleSeparationOverEdgeWidth.value + 0.5
1319 self.gridMinimumCircleRadius = self.edgeWidth
1320 self.gridInset = self.gridMinimumCircleRadius
1321 self.gridCircleRadius = self.offsetMultiplier - circleInsetOverEdgeWidth * self.edgeWidth
1322 if self.gridCircleRadius < self.gridMinimumCircleRadius:
1324 print('!!! WARNING !!!')
1325 print('Grid Circle Separation over Edge Width is too high, which makes the grid circles too small.')
1326 print('You should reduce Grid Circle Separation over Edge Width to a reasonable value, like the default of 0.5.')
1327 print('The grid circle radius will be set to the minimum grid circle radius.')
1329 self.gridCircleRadius = self.gridMinimumCircleRadius
1330 self.offsetBaseX = 0.25 * self.gridXStepSize
1331 if self.repository.infillPatternGridRectangular.value:
1332 halfGridMinusWidth = 0.5 * ( self.gridRadius - self.infillWidth )
1333 self.gridJunctionEnd = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtEnd.value
1334 self.gridJunctionMiddle = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtMiddle.value
1337 class RotatedLayer(object):
1339 def __init__( self, z ):
1341 self.rotatedCarveDictionary = {}
1342 self.rotation = None
1343 self.nestedRings = []
1347 'Get the string representation of this RotatedLayer.'
1348 return '%s, %s, %s' % ( self.z, self.rotation, self.nestedRings )
1351 class YIntersectionPath(object):
1352 'A class to hold the y intersection position, the loop which it intersected and the point index of the loop which it intersected.'
1353 def __init__( self, pathIndex, pointIndex, y ):
1354 'Initialize from the path, point index, and y.'
1355 self.pathIndex = pathIndex
1356 self.pointIndex = pointIndex
1360 'Get the string representation of this y intersection.'
1361 return '%s, %s, %s' % ( self.pathIndex, self.pointIndex, self.y )
1363 def getPath( self, paths ):
1364 'Get the path from the paths and path index.'
1365 return paths[ self.pathIndex ]
1367 def getPointIndexPlusOne(self):
1368 'Get the point index plus one.'
1369 return self.pointIndex + 1
1373 'Display the fill dialog.'
1374 if len(sys.argv) > 1:
1375 writeOutput(' '.join(sys.argv[1 :]))
1377 settings.startMainLoopFromConstructor(getNewRepository())
1379 if __name__ == '__main__':