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
190 #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.
193 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
194 from fabmetheus_utilities.geometry.solids import triangle_mesh
195 from fabmetheus_utilities.vector3 import Vector3
196 from fabmetheus_utilities import archive
197 from fabmetheus_utilities import euclidean
198 from fabmetheus_utilities import gcodec
199 from fabmetheus_utilities import intercircle
200 from fabmetheus_utilities import settings
201 from skeinforge_application.skeinforge_utilities import skeinforge_craft
202 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
203 from skeinforge_application.skeinforge_utilities import skeinforge_profile
208 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
209 __date__ = '$Date: 2008/28/04 $'
210 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
214 def addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, gridSearchRadius, isBothOrNone, isDoubleJunction, isJunctionWide, paths, pixelTable, width ):
215 'Add the path around the grid point.'
216 closestPathIndex = None
217 aroundIntersectionPaths = []
218 for aroundIndex in xrange( len(arounds) ):
219 loop = arounds[ aroundIndex ]
220 for pointIndex in xrange(len(loop)):
221 pointFirst = loop[pointIndex]
222 pointSecond = loop[(pointIndex + 1) % len(loop)]
223 yIntersection = euclidean.getYIntersectionIfExists( pointFirst, pointSecond, gridPoint.real )
224 addYIntersectionPathToList( aroundIndex, pointIndex, gridPoint.imag, yIntersection, aroundIntersectionPaths )
225 if len( aroundIntersectionPaths ) < 2:
226 print('Warning, aroundIntersectionPaths is less than 2 in fill.')
227 print(aroundIntersectionPaths)
230 yCloseToCenterArounds = getClosestOppositeIntersectionPaths(aroundIntersectionPaths)
231 if len(yCloseToCenterArounds) < 2:
233 segmentFirstY = min( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y )
234 segmentSecondY = max( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y )
235 yIntersectionPaths = []
236 gridPixel = euclidean.getStepKeyFromPoint( gridPoint / width )
237 segmentFirstPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentFirstY ) / width )
238 segmentSecondPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentSecondY ) / width )
240 addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel )
241 addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel )
242 for pathIndex in pathIndexTable.keys():
243 path = paths[ pathIndex ]
244 for pointIndex in xrange( len(path) - 1 ):
245 pointFirst = path[pointIndex]
246 pointSecond = path[pointIndex + 1]
247 yIntersection = getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, pointFirst, pointSecond, gridPoint.real )
248 addYIntersectionPathToList( pathIndex, pointIndex, gridPoint.imag, yIntersection, yIntersectionPaths )
249 if len( yIntersectionPaths ) < 1:
251 yCloseToCenterPaths = []
253 yCloseToCenterPaths = getClosestOppositeIntersectionPaths( yIntersectionPaths )
255 yIntersectionPaths.sort( compareDistanceFromCenter )
256 yCloseToCenterPaths = [ yIntersectionPaths[0] ]
257 for yCloseToCenterPath in yCloseToCenterPaths:
258 setIsOutside( yCloseToCenterPath, aroundIntersectionPaths )
259 if len( yCloseToCenterPaths ) < 2:
260 yCloseToCenterPaths[0].gridPoint = gridPoint
261 insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yCloseToCenterPaths[0], width )
263 plusMinusSign = getPlusMinusSign( yCloseToCenterPaths[1].y - yCloseToCenterPaths[0].y )
264 yCloseToCenterPaths[0].gridPoint = complex( gridPoint.real, gridPoint.imag - plusMinusSign * gridPointInsetY )
265 yCloseToCenterPaths[1].gridPoint = complex( gridPoint.real, gridPoint.imag + plusMinusSign * gridPointInsetY )
266 yCloseToCenterPaths.sort( comparePointIndexDescending )
267 insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, yCloseToCenterPaths[0], yCloseToCenterPaths[1], isBothOrNone, isJunctionWide, paths, pixelTable, width )
269 def addInfillBoundary(infillBoundary, nestedRings):
270 'Add infill boundary to the nested ring that contains it.'
271 infillPoint = infillBoundary[0]
272 for nestedRing in nestedRings:
273 if euclidean.isPointInsideLoop(nestedRing.boundary, infillPoint):
274 nestedRing.infillBoundaries.append(infillBoundary)
277 def addLoop(infillWidth, infillPaths, loop, rotationPlaneAngle):
278 'Add simplified path to fill.'
279 simplifiedLoop = euclidean.getSimplifiedLoop(loop, infillWidth)
280 if len(simplifiedLoop) < 2:
282 simplifiedLoop.append(simplifiedLoop[0])
283 planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedLoop)
284 infillPaths.append(planeRotated)
286 def addPath(infillWidth, infillPaths, path, rotationPlaneAngle):
287 'Add simplified path to fill.'
288 simplifiedPath = euclidean.getSimplifiedPath(path, infillWidth)
289 if len(simplifiedPath) < 2:
291 planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedPath)
292 infillPaths.append(planeRotated)
294 def addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel ):
295 'Add the path index of the closest segment found toward the second segment.'
296 for yStep in xrange( gridPixel[1], segmentFirstPixel[1] - 1, - 1 ):
297 if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ):
300 def addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel ):
301 'Add the path index of the closest segment found toward the second segment.'
302 for yStep in xrange( gridPixel[1], segmentSecondPixel[1] + 1 ):
303 if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ):
306 def addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width ):
307 'Add a point to a path and the pixel table.'
308 pointIndexMinusOne = pointIndex - 1
309 if pointIndex < len(path) and pointIndexMinusOne >= 0:
311 begin = path[ pointIndexMinusOne ]
312 end = path[pointIndex]
313 euclidean.addValueSegmentToPixelTable( begin, end, segmentTable, pathIndex, width )
314 euclidean.removePixelTableFromPixelTable( segmentTable, pixelTable )
315 if pointIndexMinusOne >= 0:
316 begin = path[ pointIndexMinusOne ]
317 euclidean.addValueSegmentToPixelTable( begin, point, pixelTable, pathIndex, width )
318 if pointIndex < len(path):
319 end = path[pointIndex]
320 euclidean.addValueSegmentToPixelTable( point, end, pixelTable, pathIndex, width )
321 path.insert( pointIndex, point )
323 def addPointOnPathIfFree( path, pathIndex, pixelTable, point, pointIndex, width ):
324 'Add the closest point to a path, if the point added to a path is free.'
325 if isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ):
326 addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width )
328 def addSparseEndpoints(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, solidSurfaceThickness, surroundingXIntersections):
329 'Add sparse endpoints.'
330 segments = horizontalSegmentsDictionary[horizontalSegmentsDictionaryKey]
331 for segment in segments:
332 addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections)
334 def addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections):
335 'Add sparse endpoints from a segment.'
336 if infillSolidity > 0.0:
337 if int(round(round(float(horizontalSegmentsDictionaryKey) * infillSolidity) / infillSolidity)) == horizontalSegmentsDictionaryKey:
340 if abs(segment[0].point - segment[1].point) < doubleInfillWidth:
343 if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey - 1, segment):
346 if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey + 1, segment):
349 if solidSurfaceThickness == 0:
350 removedEndpoints += segment
352 if isSegmentCompletelyInAnIntersection(segment, surroundingXIntersections):
353 removedEndpoints += segment
357 def addYIntersectionPathToList( pathIndex, pointIndex, y, yIntersection, yIntersectionPaths ):
358 'Add the y intersection path to the y intersection paths.'
359 if yIntersection == None:
361 yIntersectionPath = YIntersectionPath( pathIndex, pointIndex, yIntersection )
362 yIntersectionPath.yMinusCenter = yIntersection - y
363 yIntersectionPaths.append( yIntersectionPath )
365 def compareDistanceFromCenter(self, other):
366 'Get comparison in order to sort y intersections in ascending order of distance from the center.'
367 distanceFromCenter = abs( self.yMinusCenter )
368 distanceFromCenterOther = abs( other.yMinusCenter )
369 if distanceFromCenter > distanceFromCenterOther:
371 if distanceFromCenter < distanceFromCenterOther:
375 def comparePointIndexDescending(self, other):
376 'Get comparison in order to sort y intersections in descending order of point index.'
377 if self.pointIndex > other.pointIndex:
379 if self.pointIndex < other.pointIndex:
383 def createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded):
384 'Create extra fill loops.'
385 for innerNestedRing in nestedRing.innerNestedRings:
386 createFillForSurroundings(innerNestedRing.innerNestedRings, radius, radiusAround, shouldExtraLoopsBeAdded)
387 allFillLoops = intercircle.getInsetSeparateLoopsFromAroundLoops(nestedRing.getLoopsToBeFilled(), radius, max(1.4 * radius, radiusAround))
388 if len(allFillLoops) < 1:
390 if shouldExtraLoopsBeAdded:
391 nestedRing.extraLoops += allFillLoops
392 nestedRing.penultimateFillLoops = nestedRing.lastFillLoops
393 nestedRing.lastFillLoops = allFillLoops
395 def createFillForSurroundings(nestedRings, radius, radiusAround, shouldExtraLoopsBeAdded):
396 'Create extra fill loops for nested rings.'
397 for nestedRing in nestedRings:
398 createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded)
400 def getAdditionalLength( path, point, pointIndex ):
401 'Get the additional length added by inserting a point into a path.'
403 return abs( point - path[0] )
404 if pointIndex == len(path):
405 return abs( point - path[-1] )
406 return abs( point - path[pointIndex - 1] ) + abs( point - path[pointIndex] ) - abs( path[pointIndex] - path[pointIndex - 1] )
408 def getClosestOppositeIntersectionPaths( yIntersectionPaths ):
409 'Get the close to center paths, starting with the first and an additional opposite if it exists.'
410 yIntersectionPaths.sort( compareDistanceFromCenter )
411 beforeFirst = yIntersectionPaths[0].yMinusCenter < 0.0
412 yCloseToCenterPaths = [ yIntersectionPaths[0] ]
413 for yIntersectionPath in yIntersectionPaths[1 :]:
414 beforeSecond = yIntersectionPath.yMinusCenter < 0.0
415 if beforeFirst != beforeSecond:
416 yCloseToCenterPaths.append( yIntersectionPath )
417 return yCloseToCenterPaths
418 return yCloseToCenterPaths
420 def getCraftedText( fileName, gcodeText = '', repository=None):
421 'Fill the inset file or gcode text.'
422 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
424 def getCraftedTextFromText(gcodeText, repository=None):
425 'Fill the inset gcode text.'
426 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'fill'):
428 if repository == None:
429 repository = settings.getReadRepository( FillRepository() )
430 if not repository.activateFill.value:
432 return FillSkein().getCraftedGcode( repository, gcodeText )
434 def getKeyIsInPixelTableAddValue( key, pathIndexTable, pixelTable ):
435 '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.'
436 if key in pixelTable:
437 value = pixelTable[key]
439 pathIndexTable[value] = None
443 def getLowerLeftCorner(nestedRings):
444 'Get the lower left corner from the nestedRings.'
445 lowerLeftCorner = Vector3()
446 lowestRealPlusImaginary = 987654321.0
447 for nestedRing in nestedRings:
448 for point in nestedRing.boundary:
449 realPlusImaginary = point.real + point.imag
450 if realPlusImaginary < lowestRealPlusImaginary:
451 lowestRealPlusImaginary = realPlusImaginary
452 lowerLeftCorner.setToXYZ(point.real, point.imag, nestedRing.z)
453 return lowerLeftCorner
455 def getNewRepository():
456 'Get new repository.'
457 return FillRepository()
459 def getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width ):
460 'Get the points around the grid point that is junction wide that do not intersect.'
461 pointIndexPlusOne = yIntersectionPath.getPointIndexPlusOne()
462 path = yIntersectionPath.getPath(paths)
463 begin = path[ yIntersectionPath.pointIndex ]
464 end = path[ pointIndexPlusOne ]
465 plusMinusSign = getPlusMinusSign( end.real - begin.real )
467 gridPointXFirst = complex( yIntersectionPath.gridPoint.real - plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag )
468 gridPointXSecond = complex( yIntersectionPath.gridPoint.real + plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag )
469 if isAddedPointOnPathFree( path, pixelTable, gridPointXSecond, pointIndexPlusOne, width ):
470 if isAddedPointOnPathFree( path, pixelTable, gridPointXFirst, pointIndexPlusOne, width ):
471 return [ gridPointXSecond, gridPointXFirst ]
472 if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ):
473 return [ gridPointXSecond, yIntersectionPath.gridPoint ]
474 return [ gridPointXSecond ]
475 if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ):
476 return [ yIntersectionPath.gridPoint ]
479 def getPlusMinusSign(number):
480 'Get one if the number is zero or positive else negative one.'
485 def getWithLeastLength( path, point ):
486 'Insert a point into a path, at the index at which the path would be shortest.'
489 shortestPointIndex = None
490 shortestAdditionalLength = 999999999987654321.0
491 for pointIndex in xrange( len(path) + 1 ):
492 additionalLength = getAdditionalLength( path, point, pointIndex )
493 if additionalLength < shortestAdditionalLength:
494 shortestAdditionalLength = additionalLength
495 shortestPointIndex = pointIndex
496 return shortestPointIndex
498 def getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, beginComplex, endComplex, x ):
499 'Get the y intersection inside the y segment if it does, else none.'
500 yIntersection = euclidean.getYIntersectionIfExists( beginComplex, endComplex, x )
501 if yIntersection == None:
503 if yIntersection < min( segmentFirstY, segmentSecondY ):
505 if yIntersection <= max( segmentFirstY, segmentSecondY ):
509 def insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yIntersectionPath, width ):
510 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.'
511 linePath = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width )
512 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width )
514 def insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, intersectionPathFirst, intersectionPathSecond, isBothOrNone, isJunctionWide, paths, pixelTable, width ):
515 'Insert a pair of points around a pair of grid points.'
516 gridPointLineFirst = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width )
517 if len( gridPointLineFirst ) < 1:
520 intersectionPathSecond.gridPoint = gridPoint
521 insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, intersectionPathSecond, width )
523 gridPointLineSecond = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathSecond, width )
524 if len( gridPointLineSecond ) > 0:
525 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width )
526 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineSecond, paths, pixelTable, intersectionPathSecond, width )
530 originalGridPointFirst = intersectionPathFirst.gridPoint
531 intersectionPathFirst.gridPoint = gridPoint
532 gridPointLineFirstCenter = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width )
533 if len( gridPointLineFirstCenter ) > 0:
534 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirstCenter, paths, pixelTable, intersectionPathFirst, width )
536 intersectionPathFirst.gridPoint = originalGridPointFirst
537 insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width )
539 def insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width ):
540 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.'
541 if len( linePath ) < 1:
543 if gridPoint in gridPoints:
544 gridPoints.remove( gridPoint )
545 intersectionBeginPoint = None
546 moreThanInset = 2.1 * gridPointInsetX
547 path = yIntersectionPath.getPath(paths)
548 begin = path[ yIntersectionPath.pointIndex ]
549 end = path[ yIntersectionPath.getPointIndexPlusOne() ]
550 if yIntersectionPath.isOutside:
551 distanceX = end.real - begin.real
552 if abs( distanceX ) > 2.1 * moreThanInset:
553 intersectionBeginXDistance = yIntersectionPath.gridPoint.real - begin.real
554 endIntersectionXDistance = end.real - yIntersectionPath.gridPoint.real
555 intersectionPoint = begin * endIntersectionXDistance / distanceX + end * intersectionBeginXDistance / distanceX
556 distanceYAbsoluteInset = max( abs( yIntersectionPath.gridPoint.imag - intersectionPoint.imag ), moreThanInset )
557 intersectionEndSegment = end - intersectionPoint
558 intersectionEndSegmentLength = abs( intersectionEndSegment )
559 if intersectionEndSegmentLength > 1.1 * distanceYAbsoluteInset:
560 intersectionEndPoint = intersectionPoint + intersectionEndSegment * distanceYAbsoluteInset / intersectionEndSegmentLength
561 path.insert( yIntersectionPath.getPointIndexPlusOne(), intersectionEndPoint )
562 intersectionBeginSegment = begin - intersectionPoint
563 intersectionBeginSegmentLength = abs( intersectionBeginSegment )
564 if intersectionBeginSegmentLength > 1.1 * distanceYAbsoluteInset:
565 intersectionBeginPoint = intersectionPoint + intersectionBeginSegment * distanceYAbsoluteInset / intersectionBeginSegmentLength
566 for point in linePath:
567 addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, point, yIntersectionPath.getPointIndexPlusOne(), width )
568 if intersectionBeginPoint != None:
569 addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, intersectionBeginPoint, yIntersectionPath.getPointIndexPlusOne(), width )
571 def isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ):
572 'Determine if the point added to a path is intersecting the pixel table or the path.'
573 if pointIndex > 0 and pointIndex < len(path):
574 if isSharpCorner( ( path[pointIndex - 1] ), point, ( path[pointIndex] ) ):
576 pointIndexMinusOne = pointIndex - 1
577 if pointIndexMinusOne >= 0:
579 begin = path[ pointIndexMinusOne ]
580 if pointIndex < len(path):
581 end = path[pointIndex]
582 euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width )
584 euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width )
585 if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ):
587 if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndexMinusOne ):
589 if pointIndex < len(path):
591 begin = path[pointIndex]
592 if pointIndexMinusOne >= 0:
593 end = path[ pointIndexMinusOne ]
594 euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width )
596 euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width )
597 if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ):
599 if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ):
603 def isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ):
604 'Determine if the point added to a path is intersecting the path by checking line intersection.'
605 segment = point - begin
606 segmentLength = abs(segment)
607 if segmentLength <= 0.0:
609 normalizedSegment = segment / segmentLength
610 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
611 pointRotated = segmentYMirror * point
612 beginRotated = segmentYMirror * begin
613 if euclidean.isXSegmentIntersectingPath( path[ max( 0, pointIndex - 20 ) : pointIndex ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag ):
615 return euclidean.isXSegmentIntersectingPath( path[ pointIndex + 1 : pointIndex + 21 ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag )
617 def isIntersectingLoopsPaths( loops, paths, pointBegin, pointEnd ):
618 'Determine if the segment between the first and second point is intersecting the loop list.'
619 normalizedSegment = pointEnd.dropAxis() - pointBegin.dropAxis()
620 normalizedSegmentLength = abs( normalizedSegment )
621 if normalizedSegmentLength == 0.0:
623 normalizedSegment /= normalizedSegmentLength
624 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
625 pointBeginRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointBegin )
626 pointEndRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointEnd )
627 if euclidean.isLoopListIntersectingInsideXSegment( loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ):
629 return euclidean.isXSegmentIntersectingPaths( paths, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag )
631 def isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, width):
632 'Add the closest removed endpoint to the path, with minimal twisting.'
633 closestDistanceSquared = 999999999987654321.0
634 closestPathIndex = None
635 for pathIndex in xrange(len(paths)):
636 path = paths[ pathIndex ]
637 for pointIndex in xrange(len(path)):
638 point = path[pointIndex]
639 distanceSquared = abs(point - removedEndpointPoint)
640 if distanceSquared < closestDistanceSquared:
641 closestDistanceSquared = distanceSquared
642 closestPathIndex = pathIndex
643 if closestPathIndex == None:
645 if closestDistanceSquared < 0.8 * layerInfillWidth * layerInfillWidth:
647 closestPath = paths[closestPathIndex]
648 closestPointIndex = getWithLeastLength(closestPath, removedEndpointPoint)
649 if isAddedPointOnPathFree(closestPath, pixelTable, removedEndpointPoint, closestPointIndex, width):
650 addPointOnPath(closestPath, closestPathIndex, pixelTable, removedEndpointPoint, closestPointIndex, width)
652 return isSidePointAdded(pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width)
654 def isSegmentAround(aroundSegmentsDictionary, aroundSegmentsDictionaryKey, segment):
655 'Determine if there is another segment around.'
656 if aroundSegmentsDictionaryKey not in aroundSegmentsDictionary:
658 for aroundSegment in aroundSegmentsDictionary[aroundSegmentsDictionaryKey]:
659 endpoint = aroundSegment[0]
660 if isSegmentInX(segment, endpoint.point.real, endpoint.otherEndpoint.point.real):
664 def isSegmentCompletelyInAnIntersection( segment, xIntersections ):
665 'Add sparse endpoints from a segment.'
666 for xIntersectionIndex in xrange( 0, len( xIntersections ), 2 ):
667 surroundingXFirst = xIntersections[ xIntersectionIndex ]
668 surroundingXSecond = xIntersections[ xIntersectionIndex + 1 ]
669 if euclidean.isSegmentCompletelyInX( segment, surroundingXFirst, surroundingXSecond ):
673 def isSegmentInX( segment, xFirst, xSecond ):
674 'Determine if the segment overlaps within x.'
675 segmentFirstX = segment[0].point.real
676 segmentSecondX = segment[1].point.real
677 if min( segmentFirstX, segmentSecondX ) > max( xFirst, xSecond ):
679 return max( segmentFirstX, segmentSecondX ) > min( xFirst, xSecond )
681 def isSharpCorner( beginComplex, centerComplex, endComplex ):
682 'Determine if the three complex points form a sharp corner.'
683 centerBeginComplex = beginComplex - centerComplex
684 centerEndComplex = endComplex - centerComplex
685 centerBeginLength = abs( centerBeginComplex )
686 centerEndLength = abs( centerEndComplex )
687 if centerBeginLength <= 0.0 or centerEndLength <= 0.0:
689 centerBeginComplex /= centerBeginLength
690 centerEndComplex /= centerEndLength
691 return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) > 0.9
693 def isSidePointAdded( pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width ):
694 'Add side point along with the closest removed endpoint to the path, with minimal twisting.'
695 if closestPointIndex <= 0 or closestPointIndex >= len( closestPath ):
697 pointBegin = closestPath[ closestPointIndex - 1 ]
698 pointEnd = closestPath[ closestPointIndex ]
699 removedEndpointPoint = removedEndpointPoint
702 removedMinusClosest = removedEndpointPoint - pointBegin
703 removedMinusClosestLength = abs( removedMinusClosest )
704 if removedMinusClosestLength <= 0.0:
706 removedMinusOther = removedEndpointPoint - pointEnd
707 removedMinusOtherLength = abs( removedMinusOther )
708 if removedMinusOtherLength <= 0.0:
710 insertPointAfter = None
711 insertPointBefore = None
712 if removedMinusOtherLength < removedMinusClosestLength:
714 farthest = pointBegin
715 removedMinusClosest = removedMinusOther
716 removedMinusClosestLength = removedMinusOtherLength
717 insertPointBefore = removedEndpointPoint
719 insertPointAfter = removedEndpointPoint
720 removedMinusClosestNormalized = removedMinusClosest / removedMinusClosestLength
721 perpendicular = removedMinusClosestNormalized * complex( 0.0, layerInfillWidth )
722 sidePoint = removedEndpointPoint + perpendicular
723 #extra check in case the line to the side point somehow slips by the line to the perpendicular
724 sidePointOther = removedEndpointPoint - perpendicular
725 if abs( sidePoint - farthest ) > abs( sidePointOther - farthest ):
726 perpendicular = - perpendicular
727 sidePoint = sidePointOther
729 closestSegmentTable = {}
730 toPerpendicularTable = {}
731 euclidean.addValueSegmentToPixelTable( pointBegin, pointEnd, maskTable, None, width )
732 euclidean.addValueSegmentToPixelTable( closest, removedEndpointPoint, closestSegmentTable, None, width )
733 euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width )
734 if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ):
735 sidePoint = removedEndpointPoint - perpendicular
736 toPerpendicularTable = {}
737 euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width )
738 if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ):
740 if insertPointBefore != None:
741 addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointBefore, closestPointIndex, width )
742 addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, sidePoint, closestPointIndex, width )
743 if insertPointAfter != None:
744 addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointAfter, closestPointIndex, width )
747 def removeEndpoints(layerInfillWidth, paths, pixelTable, removedEndpoints, aroundWidth):
748 'Remove endpoints which are added to the path.'
749 for removedEndpointIndex in xrange(len(removedEndpoints) -1, -1, -1):
750 removedEndpoint = removedEndpoints[removedEndpointIndex]
751 removedEndpointPoint = removedEndpoint.point
752 if isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, aroundWidth):
753 removedEndpoints.remove(removedEndpoint )
755 def setIsOutside( yCloseToCenterPath, yIntersectionPaths ):
756 'Determine if the yCloseToCenterPath is outside.'
757 beforeClose = yCloseToCenterPath.yMinusCenter < 0.0
758 for yIntersectionPath in yIntersectionPaths:
759 if yIntersectionPath != yCloseToCenterPath:
760 beforePath = yIntersectionPath.yMinusCenter < 0.0
761 if beforeClose == beforePath:
762 yCloseToCenterPath.isOutside = False
764 yCloseToCenterPath.isOutside = True
766 def writeOutput(fileName, shouldAnalyze=True):
767 'Fill an inset gcode file.'
768 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'fill', shouldAnalyze)
771 class FillRepository:
772 'A class to handle the fill settings.'
774 'Set the default settings, execute title & settings fileName.'
775 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.fill.html', self )
776 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Fill', self, '')
777 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill')
778 self.activateFill = settings.BooleanSetting().getFromValue('Activate Fill', self, True)
779 self.solidSurfaceTop = settings.BooleanSetting().getFromValue('Solid Surface Top', self, True)
780 self.overrideFirstLayerSequence = settings.BooleanSetting().getFromValue('Override First Layer Sequence', self, True)
781 settings.LabelSeparator().getFromRepository(self)
782 settings.LabelDisplay().getFromName('- Diaphragm -', self )
783 self.diaphragmPeriod = settings.IntSpin().getFromValue( 20, 'Diaphragm Period (layers):', self, 200, 100 )
784 self.diaphragmThickness = settings.IntSpin().getFromValue( 0, 'Diaphragm Thickness (layers):', self, 5, 0 )
785 settings.LabelSeparator().getFromRepository(self)
786 settings.LabelDisplay().getFromName('- Extra Shells -', self )
787 self.extraShellsAlternatingSolidLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Alternating Solid Layer (layers):', self, 3, 2 )
788 self.extraShellsBase = settings.IntSpin().getFromValue( 0, 'Extra Shells on Base (layers):', self, 3, 1 )
789 self.extraShellsSparseLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Sparse Layer (layers):', self, 3, 1 )
790 settings.LabelSeparator().getFromRepository(self)
791 settings.LabelDisplay().getFromName('- Grid -', self )
792 self.gridCircleSeparationOverEdgeWidth = settings.FloatSpin().getFromValue(0.0, 'Grid Circle Separation over Perimeter Width (ratio):', self, 1.0, 0.2)
793 self.gridExtraOverlap = settings.FloatSpin().getFromValue( 0.0, 'Grid Extra Overlap (ratio):', self, 0.5, 0.1 )
794 self.gridJunctionSeparationBandHeight = settings.IntSpin().getFromValue( 0, 'Grid Junction Separation Band Height (layers):', self, 20, 10 )
795 self.gridJunctionSeparationOverOctogonRadiusAtEnd = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At End (ratio):', self, 0.8, 0.0 )
796 self.gridJunctionSeparationOverOctogonRadiusAtMiddle = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At Middle (ratio):', self, 0.8, 0.0 )
797 settings.LabelSeparator().getFromRepository(self)
798 settings.LabelDisplay().getFromName('- Infill -', self )
799 self.infillBeginRotation = settings.FloatSpin().getFromValue( 0.0, 'Infill Begin Rotation (degrees):', self, 90.0, 45.0 )
800 self.infillBeginRotationRepeat = settings.IntSpin().getFromValue( 0, 'Infill Begin Rotation Repeat (layers):', self, 3, 1 )
801 self.infillOddLayerExtraRotation = settings.FloatSpin().getFromValue(30.0, 'Infill Odd Layer Extra Rotation (degrees):', self, 90.0, 90.0)
802 self.infillPatternLabel = settings.LabelDisplay().getFromName('Infill Pattern:', self )
803 infillLatentStringVar = settings.LatentStringVar()
804 self.infillPatternGridCircular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Circular', self, False )
805 self.infillPatternGridHexagonal = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Hexagonal', self, False )
806 self.infillPatternGridRectangular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Rectangular', self, False )
807 self.infillPatternLine = settings.Radio().getFromRadio( infillLatentStringVar, 'Line', self, True )
808 self.infillPerimeterOverlap = settings.FloatSpin().getFromValue( 0.0, 'Infill Perimeter Overlap (ratio):', self, 0.4, 0.15 )
809 self.infillSolidity = settings.FloatSpin().getFromValue( 0.04, 'Infill Solidity (ratio):', self, 0.3, 0.2 )
810 settings.LabelSeparator().getFromRepository(self)
811 self.sharpestAngle = settings.FloatSpin().getFromValue(50.0, 'Sharpest Angle (degrees):', self, 70.0, 60.0)
812 self.solidSurfaceThickness = settings.IntSpin().getFromValue(0, 'Solid Surface Thickness (layers):', self, 5, 3)
813 self.startFromChoice = settings.MenuButtonDisplay().getFromName('Start From Choice:', self)
814 self.startFromLowerLeft = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Lower Left', self, True)
815 self.startFromNearest = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Nearest', self, False)
816 self.surroundingAngle = settings.FloatSpin().getFromValue(30.0, 'Surrounding Angle (degrees):', self, 80.0, 60.0)
817 self.threadSequenceChoice = settings.MenuButtonDisplay().getFromName('Thread Sequence Choice:', self)
818 self.threadSequenceInfillLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Loops > Perimeter', self, False)
819 self.threadSequenceInfillPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Perimeter > Loops', self, False)
820 self.threadSequenceLoopsInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Infill > Perimeter', self, False)
821 self.threadSequenceLoopsPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Perimeter > Infill', self, True)
822 self.threadSequencePerimeterInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Infill > Loops', self, False)
823 self.threadSequencePerimeterLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Loops > Infill', self, False)
824 self.executeTitle = 'Fill'
827 'Fill button has been clicked.'
828 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
829 for fileName in fileNames:
830 writeOutput(fileName)
834 'A class to fill a skein of extrusions.'
837 self.distanceFeedRate = gcodec.DistanceFeedRate()
838 self.edgeWidth = None
839 self.extruderActive = False
840 self.fillInset = 0.18
842 self.lastExtraShells = - 1
844 self.oldLocation = None
845 self.oldOrderedLocation = None
846 self.rotatedLayer = None
847 self.rotatedLayers = []
848 self.shutdownLineIndex = sys.maxint
849 self.nestedRing = None
852 def addFill(self, layerIndex):
853 'Add fill to the carve layer.'
856 settings.printProgressByNumber(layerIndex, len(self.rotatedLayers), 'fill')
859 extraShells = self.repository.extraShellsSparseLayer.value
861 layerFillInset = self.fillInset
862 layerInfillSolidity = self.infillSolidity
863 layerRemainder = layerIndex % int(round(self.repository.diaphragmPeriod.value))
864 layerRotation = self.getLayerRotation(layerIndex)
866 reverseRotation = complex(layerRotation.real, - layerRotation.imag)
867 rotatedLayer = self.rotatedLayers[layerIndex]
868 self.isDoubleJunction = True
869 self.isJunctionWide = True
870 surroundingCarves = []
871 self.distanceFeedRate.addLine('(<layer> %s )' % rotatedLayer.z)
872 if layerRemainder >= int(round(self.repository.diaphragmThickness.value)):
873 for surroundingIndex in xrange(1, self.solidSurfaceThickness + 1):
874 if self.repository.solidSurfaceTop.value:
875 self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves)
876 self.addRotatedCarve(layerIndex, surroundingIndex, reverseRotation, surroundingCarves)
878 self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves)
879 self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves)
880 if len(surroundingCarves) < self.doubleSolidSurfaceThickness:
881 extraShells = self.repository.extraShellsAlternatingSolidLayer.value
882 if self.lastExtraShells != self.repository.extraShellsBase.value:
883 extraShells = self.repository.extraShellsBase.value
884 if rotatedLayer.rotation != None:
886 self.distanceFeedRate.addLine('(<bridgeRotation> %s )' % layerRotation)
887 self.distanceFeedRate.addLine('(<rotation> %s )' % layerRotation)
888 # aroundWidth = 0.34321 * self.infillWidth
889 aroundWidth = 0.24321 * self.infillWidth
890 doubleInfillWidth = 2.0 * self.infillWidth
891 gridPointInsetX = 0.5 * self.fillInset
892 self.lastExtraShells = extraShells
893 if self.repository.infillPatternGridHexagonal.value:
894 infillBeginRotationPolar = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation)
895 if abs(euclidean.getDotProduct(layerRotation, infillBeginRotationPolar)) < math.sqrt( 0.5):
896 layerInfillSolidity *= 0.5
897 self.isDoubleJunction = False
899 self.isJunctionWide = False
900 nestedRings = euclidean.getOrderedNestedRings(rotatedLayer.nestedRings)
901 radiusAround = 0.5 * min(self.infillWidth, self.edgeWidth)
902 createFillForSurroundings(nestedRings, self.edgeMinusHalfInfillWidth, radiusAround, False)
903 for extraShellIndex in xrange(extraShells):
904 createFillForSurroundings(nestedRings, self.infillWidth, radiusAround, True)
905 fillLoops = euclidean.getFillOfSurroundings(nestedRings, None)
906 rotatedLoops = euclidean.getRotatedComplexLists(reverseRotation, fillLoops)
907 infillDictionary = triangle_mesh.getInfillDictionary(arounds, aroundWidth, self.fillInset, self.infillWidth, pixelTable, rotatedLoops)
909 self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer)
911 self.horizontalSegmentsDictionary = {}
912 for infillDictionaryKey in infillDictionary.keys():
913 xIntersections = infillDictionary[infillDictionaryKey]
914 xIntersections.sort()
915 y = infillDictionaryKey * self.infillWidth
916 self.horizontalSegmentsDictionary[infillDictionaryKey] = euclidean.getSegmentsFromXIntersections(xIntersections, y)
917 self.surroundingXIntersectionsDictionary = {}
919 removedEndpoints = []
920 if len(surroundingCarves) >= self.doubleSolidSurfaceThickness:
921 if self.repository.infillPatternGridCircular.value and self.repository.infillSolidity.value > 0.0:
923 layerInfillSolidity = 0.0
924 xSurroundingIntersectionsDictionaries = [infillDictionary]
925 for surroundingCarve in surroundingCarves:
926 xSurroundingIntersectionsDictionary = {}
927 euclidean.addXIntersectionsFromLoopsForTable(surroundingCarve, xSurroundingIntersectionsDictionary, self.infillWidth)
928 xSurroundingIntersectionsDictionaries.append(xSurroundingIntersectionsDictionary)
929 self.surroundingXIntersectionsDictionary = euclidean.getIntersectionOfXIntersectionsTables(xSurroundingIntersectionsDictionaries)
930 for horizontalSegmentsDictionaryKey in self.horizontalSegmentsDictionary.keys():
931 if horizontalSegmentsDictionaryKey in self.surroundingXIntersectionsDictionary:
932 surroundingXIntersections = self.surroundingXIntersectionsDictionary[horizontalSegmentsDictionaryKey]
934 surroundingXIntersections = []
935 addSparseEndpoints(doubleInfillWidth, endpoints, self.horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, layerInfillSolidity, removedEndpoints, self.solidSurfaceThickness, surroundingXIntersections)
937 for segments in self.horizontalSegmentsDictionary.values():
938 for segment in segments:
940 paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.infillWidth, pixelTable, self.sharpestProduct, aroundWidth)
942 startAngle = euclidean.globalGoldenAngle * float(layerIndex)
943 for gridPoint in self.getGridPoints(fillLoops, reverseRotation):
944 self.addGridCircle(gridPoint, infillPaths, layerRotation, pixelTable, rotatedLoops, layerRotation, aroundWidth)
946 if self.isGridToBeExtruded():
948 arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, aroundWidth)
949 oldRemovedEndpointLength = len(removedEndpoints) + 1
950 while oldRemovedEndpointLength - len(removedEndpoints) > 0:
951 oldRemovedEndpointLength = len(removedEndpoints)
952 removeEndpoints(self.infillWidth, paths, pixelTable, removedEndpoints, aroundWidth)
953 paths = euclidean.getConnectedPaths(paths, pixelTable, self.sharpestProduct, aroundWidth)
955 addPath(self.infillWidth, infillPaths, path, layerRotation)
956 euclidean.transferPathsToNestedRings(nestedRings, infillPaths)
957 for fillLoop in fillLoops:
958 addInfillBoundary(fillLoop, nestedRings)
959 self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer)
961 def addGcodeFromThreadZ( self, thread, z ):
962 'Add a gcode thread to the output.'
963 self.distanceFeedRate.addGcodeFromThreadZ( thread, z )
965 def addGrid(self, arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, width):
966 'Add the grid to the infill layer.'
967 if len(surroundingCarves) < self.doubleSolidSurfaceThickness:
972 pathIndexBegin = len( explodedPaths )
973 for pointIndex in xrange( len(path) - 1 ):
974 pathSegment = [ path[pointIndex], path[pointIndex + 1] ]
975 explodedPaths.append( pathSegment )
976 pathGroups.append( ( pathIndexBegin, len( explodedPaths ) ) )
977 for pathIndex in xrange( len( explodedPaths ) ):
978 explodedPath = explodedPaths[ pathIndex ]
979 euclidean.addPathToPixelTable( explodedPath, pixelTable, pathIndex, width )
980 gridPoints = self.getGridPoints(fillLoops, reverseRotation)
981 gridPointInsetY = gridPointInsetX * ( 1.0 - self.repository.gridExtraOverlap.value )
982 if self.repository.infillPatternGridRectangular.value:
983 gridBandHeight = self.repository.gridJunctionSeparationBandHeight.value
984 gridLayerRemainder = ( layerIndex - self.solidSurfaceThickness ) % gridBandHeight
985 halfBandHeight = 0.5 * float( gridBandHeight )
986 halfBandHeightFloor = math.floor( halfBandHeight )
987 fromMiddle = math.floor( abs( gridLayerRemainder - halfBandHeight ) )
988 fromEnd = halfBandHeightFloor - fromMiddle
989 gridJunctionSeparation = self.gridJunctionEnd * fromMiddle + self.gridJunctionMiddle * fromEnd
990 gridJunctionSeparation /= halfBandHeightFloor
991 gridPointInsetX += gridJunctionSeparation
992 gridPointInsetY += gridJunctionSeparation
993 oldGridPointLength = len( gridPoints ) + 1
994 while oldGridPointLength - len( gridPoints ) > 0:
995 oldGridPointLength = len( gridPoints )
996 self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, True, explodedPaths, pixelTable, width )
997 oldGridPointLength = len( gridPoints ) + 1
998 while oldGridPointLength - len( gridPoints ) > 0:
999 oldGridPointLength = len( gridPoints )
1000 self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, False, explodedPaths, pixelTable, width )
1001 for pathGroupIndex in xrange( len( pathGroups ) ):
1002 pathGroup = pathGroups[ pathGroupIndex ]
1003 paths[ pathGroupIndex ] = []
1004 for explodedPathIndex in xrange( pathGroup[0], pathGroup[1] ):
1005 explodedPath = explodedPaths[ explodedPathIndex ]
1006 if len( paths[ pathGroupIndex ] ) == 0:
1007 paths[ pathGroupIndex ] = explodedPath
1009 paths[ pathGroupIndex ] += explodedPath[1 :]
1011 def addGridCircle(self, center, infillPaths, layerRotation, pixelTable, rotatedLoops, startRotation, width):
1012 'Add circle to the grid.'
1013 startAngle = -math.atan2(startRotation.imag, startRotation.real)
1014 loop = euclidean.getComplexPolygon(center, self.gridCircleRadius, 17, startAngle)
1015 loopPixelDictionary = {}
1016 euclidean.addLoopToPixelTable(loop, loopPixelDictionary, width)
1017 if not euclidean.isPixelTableIntersecting(pixelTable, loopPixelDictionary):
1018 if euclidean.getIsInFilledRegion(rotatedLoops, euclidean.getLeftPoint(loop)):
1019 addLoop(self.infillWidth, infillPaths, loop, layerRotation)
1021 insideIndexPaths = []
1022 insideIndexPath = None
1023 for pointIndex, point in enumerate(loop):
1024 nextPoint = loop[(pointIndex + 1) % len(loop)]
1025 segmentDictionary = {}
1026 euclidean.addValueSegmentToPixelTable(point, nextPoint, segmentDictionary, None, width)
1027 euclidean.addSquareTwoToPixelDictionary(segmentDictionary, point, None, width)
1028 euclidean.addSquareTwoToPixelDictionary(segmentDictionary, nextPoint, None, width)
1029 shouldAddLoop = not euclidean.isPixelTableIntersecting(pixelTable, segmentDictionary)
1031 shouldAddLoop = euclidean.getIsInFilledRegion(rotatedLoops, point)
1033 if insideIndexPath == None:
1034 insideIndexPath = [pointIndex]
1035 insideIndexPaths.append(insideIndexPath)
1037 insideIndexPath.append(pointIndex)
1039 insideIndexPath = None
1040 if len(insideIndexPaths) > 1:
1041 insideIndexPathFirst = insideIndexPaths[0]
1042 insideIndexPathLast = insideIndexPaths[-1]
1043 if insideIndexPathFirst[0] == 0 and insideIndexPathLast[-1] == len(loop) - 1:
1044 insideIndexPaths[0] = insideIndexPathLast + insideIndexPathFirst
1045 del insideIndexPaths[-1]
1046 for insideIndexPath in insideIndexPaths:
1048 for insideIndex in insideIndexPath:
1050 path.append(loop[insideIndex])
1051 path.append(loop[(insideIndex + 1) % len(loop)])
1052 addPath(self.infillWidth, infillPaths, path, layerRotation)
1054 def addGridLinePoints( self, begin, end, gridPoints, gridRotationAngle, offset, y ):
1055 'Add the segments of one line of a grid to the infill.'
1056 if self.gridRadius == 0.0:
1058 gridXStep = int(math.floor((begin) / self.gridXStepSize)) - 3
1059 gridXOffset = offset + self.gridXStepSize * float(gridXStep)
1060 while gridXOffset < end:
1061 if gridXOffset >= begin:
1062 gridPointComplex = complex(gridXOffset, y) * gridRotationAngle
1063 if self.repository.infillPatternGridCircular.value or self.isPointInsideLineSegments(gridPointComplex):
1064 gridPoints.append(gridPointComplex)
1065 gridXStep = self.getNextGripXStep(gridXStep)
1066 gridXOffset = offset + self.gridXStepSize * float(gridXStep)
1068 def addRemainingGridPoints(
1069 self, arounds, gridPointInsetX, gridPointInsetY, gridPoints, isBothOrNone, paths, pixelTable, width):
1070 'Add the remaining grid points to the grid point list.'
1071 for gridPointIndex in xrange( len( gridPoints ) - 1, - 1, - 1 ):
1072 gridPoint = gridPoints[ gridPointIndex ]
1073 addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, self.gridRadius, isBothOrNone, self.isDoubleJunction, self.isJunctionWide, paths, pixelTable, width )
1075 def addRotatedCarve(self, currentLayer, layerDelta, reverseRotation, surroundingCarves):
1076 'Add a rotated carve to the surrounding carves.rotatedCarveDictionary'
1077 layerIndex = currentLayer + layerDelta
1078 if layerIndex < 0 or layerIndex >= len(self.rotatedLayers):
1080 layerDifference = abs(layerDelta)
1081 rotatedLayer = self.rotatedLayers[layerIndex]
1082 if layerDifference in rotatedLayer.rotatedCarveDictionary:
1083 surroundingCarves.append(rotatedLayer.rotatedCarveDictionary[layerDifference])
1085 nestedRings = rotatedLayer.nestedRings
1087 for nestedRing in nestedRings:
1088 planeRotatedLoop = euclidean.getRotatedComplexes(reverseRotation, nestedRing.boundary)
1089 rotatedCarve.append(planeRotatedLoop)
1090 outsetRadius = float(layerDifference) * self.layerHeight * self.surroundingSlope - self.edgeWidth
1091 if outsetRadius > 0.0:
1092 rotatedCarve = intercircle.getInsetSeparateLoopsFromAroundLoops(rotatedCarve, -outsetRadius, self.layerHeight)
1093 surroundingCarves.append(rotatedCarve)
1094 rotatedLayer.rotatedCarveDictionary[layerDifference] = rotatedCarve
1096 def addThreadsBridgeLayer(self, layerIndex, nestedRings, rotatedLayer, testLoops=None):
1097 'Add the threads, add the bridge end & the layer end tag.'
1098 if self.oldOrderedLocation == None or self.repository.startFromLowerLeft.value:
1099 self.oldOrderedLocation = getLowerLeftCorner(nestedRings)
1100 extrusionHalfWidth = 0.5 * self.infillWidth
1101 threadSequence = self.threadSequence
1102 if layerIndex < 1 and self.repository.overrideFirstLayerSequence.value:
1103 threadSequence = ['edge', 'loops', 'infill']
1104 euclidean.addToThreadsRemove(extrusionHalfWidth, nestedRings, self.oldOrderedLocation, self, threadSequence)
1105 if testLoops != None:
1106 for testLoop in testLoops:
1107 self.addGcodeFromThreadZ(testLoop, self.oldOrderedLocation.z)
1108 self.distanceFeedRate.addLine('(</rotation>)')
1109 if rotatedLayer.rotation != None:
1110 self.distanceFeedRate.addLine('(</bridgeRotation>)')
1111 self.distanceFeedRate.addLine('(</layer>)')
1113 def addToThread(self, location):
1114 'Add a location to thread.'
1115 if self.oldLocation == None:
1118 self.nestedRing.addToLoop( location )
1120 if self.thread == None:
1121 self.thread = [ self.oldLocation.dropAxis() ]
1122 self.nestedRing.edgePaths.append(self.thread)
1123 self.thread.append(location.dropAxis())
1125 def getCraftedGcode( self, repository, gcodeText ):
1126 'Parse gcode text and store the bevel gcode.'
1127 self.repository = repository
1128 self.lines = archive.getTextLines(gcodeText)
1129 self.sharpestProduct = math.sin(math.radians(repository.sharpestAngle.value))
1130 self.threadSequence = None
1131 if repository.threadSequenceInfillLoops.value:
1132 self.threadSequence = ['infill', 'loops', 'edge']
1133 if repository.threadSequenceInfillPerimeter.value:
1134 self.threadSequence = ['infill', 'edge', 'loops']
1135 if repository.threadSequenceLoopsInfill.value:
1136 self.threadSequence = ['loops', 'infill', 'edge']
1137 if repository.threadSequenceLoopsPerimeter.value:
1138 self.threadSequence = ['loops', 'edge', 'infill']
1139 if repository.threadSequencePerimeterInfill.value:
1140 self.threadSequence = ['edge', 'infill', 'loops']
1141 if repository.threadSequencePerimeterLoops.value:
1142 self.threadSequence = ['edge', 'loops', 'infill']
1143 if self.repository.infillPerimeterOverlap.value > 0.45:
1145 print('!!! WARNING !!!')
1146 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.')
1147 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.')
1149 self.parseInitialization()
1150 if self.edgeWidth == None:
1151 print('Warning, nothing will be done because self.edgeWidth in getCraftedGcode in FillSkein was None.')
1153 self.fillInset = self.infillWidth - self.infillWidth * self.repository.infillPerimeterOverlap.value
1154 self.infillSolidity = repository.infillSolidity.value
1155 self.edgeMinusHalfInfillWidth = self.edgeWidth - 0.5 * self.infillWidth
1156 if self.isGridToBeExtruded():
1157 self.setGridVariables(repository)
1158 self.infillBeginRotation = math.radians( repository.infillBeginRotation.value )
1159 self.infillOddLayerExtraRotation = math.radians( repository.infillOddLayerExtraRotation.value )
1160 self.solidSurfaceThickness = int( round( self.repository.solidSurfaceThickness.value ) )
1161 self.doubleSolidSurfaceThickness = self.solidSurfaceThickness + self.solidSurfaceThickness
1162 for lineIndex in xrange(self.lineIndex, len(self.lines)):
1163 self.parseLine( lineIndex )
1164 for layerIndex in xrange(len(self.rotatedLayers)):
1165 self.addFill(layerIndex)
1166 self.distanceFeedRate.addLines( self.lines[ self.shutdownLineIndex : ] )
1167 return self.distanceFeedRate.output.getvalue()
1169 def getGridPoints(self, fillLoops, reverseRotation):
1170 'Get the grid points.'
1171 if self.infillSolidity > 0.8:
1173 rotationBaseAngle = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation)
1174 reverseRotationBaseAngle = complex(rotationBaseAngle.real, - rotationBaseAngle.imag)
1175 gridRotationAngle = reverseRotation * rotationBaseAngle
1176 slightlyGreaterThanFillInset = intercircle.globalIntercircleMultiplier * self.gridInset
1177 triangle_mesh.sortLoopsInOrderOfArea(True, fillLoops)
1178 rotatedLoops = euclidean.getRotatedComplexLists(reverseRotationBaseAngle, fillLoops)
1179 if self.repository.infillPatternGridCircular.value:
1180 return self.getGridPointsByLoops(
1181 gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, -self.gridCircleRadius))
1182 return self.getGridPointsByLoops(gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, self.gridInset))
1184 def getGridPointsByLoops(self, gridRotationAngle, loops):
1185 'Get the grid points by loops.'
1186 gridIntersectionsDictionary = {}
1188 euclidean.addXIntersectionsFromLoopsForTable(loops, gridIntersectionsDictionary, self.gridRadius)
1189 for gridIntersectionsKey in gridIntersectionsDictionary:
1190 y = gridIntersectionsKey * self.gridRadius + self.gridRadius * 0.5
1191 gridIntersections = gridIntersectionsDictionary[gridIntersectionsKey]
1192 gridIntersections.sort()
1193 gridIntersectionsLength = len(gridIntersections)
1194 if gridIntersectionsLength % 2 == 1:
1195 gridIntersectionsLength -= 1
1196 for gridIntersectionIndex in xrange(0, gridIntersectionsLength, 2):
1197 begin = gridIntersections[gridIntersectionIndex]
1198 end = gridIntersections[gridIntersectionIndex + 1]
1199 offset = self.offsetMultiplier * (gridIntersectionsKey % 2) + self.offsetBaseX
1200 self.addGridLinePoints(begin, end, gridPoints, gridRotationAngle, offset, y)
1203 def getLayerRotation(self, layerIndex):
1204 'Get the layer rotation.'
1205 rotation = self.rotatedLayers[layerIndex].rotation
1206 if rotation != None:
1208 infillBeginRotationRepeat = self.repository.infillBeginRotationRepeat.value
1209 infillOddLayerRotationMultiplier = float( layerIndex % ( infillBeginRotationRepeat + 1 ) == infillBeginRotationRepeat )
1210 layerAngle = self.infillBeginRotation + infillOddLayerRotationMultiplier * self.infillOddLayerExtraRotation
1211 return euclidean.getWiddershinsUnitPolar(layerAngle)
1213 def getNextGripXStep( self, gridXStep ):
1214 'Get the next grid x step, increment by an extra one every three if hexagonal grid is chosen.'
1216 if self.repository.infillPatternGridHexagonal.value:
1217 if gridXStep % 3 == 0:
1221 def isGridToBeExtruded(self):
1222 'Determine if the grid is to be extruded.'
1223 if self.repository.infillPatternLine.value:
1225 return self.repository.infillSolidity.value > 0.0
1227 def isPointInsideLineSegments( self, gridPoint ):
1228 'Is the point inside the line segments of the loops.'
1229 if self.solidSurfaceThickness <= 0:
1231 fillLine = int(round(gridPoint.imag / self.infillWidth))
1232 if fillLine not in self.horizontalSegmentsDictionary:
1234 if fillLine not in self.surroundingXIntersectionsDictionary:
1236 lineSegments = self.horizontalSegmentsDictionary[fillLine]
1237 surroundingXIntersections = self.surroundingXIntersectionsDictionary[fillLine]
1238 for lineSegment in lineSegments:
1239 if isSegmentCompletelyInAnIntersection(lineSegment, surroundingXIntersections ):
1240 xFirst = lineSegment[0].point.real
1241 xSecond = lineSegment[1].point.real
1242 if gridPoint.real > min(xFirst, xSecond) and gridPoint.real < max(xFirst, xSecond):
1246 def linearMove( self, splitLine ):
1247 'Add a linear move to the thread.'
1248 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
1249 if self.extruderActive:
1250 self.addToThread( location )
1251 self.oldLocation = location
1253 def parseInitialization(self):
1254 'Parse gcode initialization and store the parameters.'
1255 for self.lineIndex in xrange(len(self.lines)):
1256 line = self.lines[self.lineIndex]
1257 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
1258 firstWord = gcodec.getFirstWord(splitLine)
1259 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
1260 if firstWord == '(<crafting>)':
1261 self.distanceFeedRate.addLine(line)
1263 elif firstWord == '(<infillWidth>':
1264 self.infillWidth = float(splitLine[1])
1265 elif firstWord == '(<layerHeight>':
1266 self.layerHeight = float(splitLine[1])
1267 self.surroundingSlope = math.tan(math.radians(min(self.repository.surroundingAngle.value, 80.0)))
1268 self.distanceFeedRate.addTagRoundedLine('infillPerimeterOverlap', self.repository.infillPerimeterOverlap.value)
1269 self.distanceFeedRate.addTagRoundedLine('sharpestProduct', self.sharpestProduct)
1270 elif firstWord == '(<edgeWidth>':
1271 self.edgeWidth = float(splitLine[1])
1272 threadSequenceString = ' '.join( self.threadSequence )
1273 self.distanceFeedRate.addTagBracketedLine('threadSequenceString', threadSequenceString )
1274 elif firstWord == '(</extruderInitialization>)':
1275 self.distanceFeedRate.addTagBracketedProcedure('fill')
1276 self.distanceFeedRate.addLine(line)
1278 def parseLine( self, lineIndex ):
1279 'Parse a gcode line and add it to the fill skein.'
1280 line = self.lines[lineIndex]
1281 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
1282 if len(splitLine) < 1:
1284 firstWord = splitLine[0]
1285 if firstWord == 'G1':
1286 self.linearMove(splitLine)
1287 elif firstWord == 'M101':
1288 self.extruderActive = True
1289 elif firstWord == 'M103':
1290 self.extruderActive = False
1293 elif firstWord == '(<boundaryPerimeter>)':
1294 self.nestedRing = euclidean.NestedBand()
1295 self.rotatedLayer.nestedRings.append( self.nestedRing )
1296 elif firstWord == '(</boundaryPerimeter>)':
1297 self.nestedRing = None
1298 elif firstWord == '(<boundaryPoint>':
1299 location = gcodec.getLocationFromSplitLine(None, splitLine)
1300 self.nestedRing.addToBoundary( location )
1301 elif firstWord == '(<bridgeRotation>':
1302 self.rotatedLayer.rotation = gcodec.getRotationBySplitLine(splitLine)
1303 elif firstWord == '(</crafting>)':
1304 self.shutdownLineIndex = lineIndex
1305 elif firstWord == '(<layer>':
1306 self.rotatedLayer = RotatedLayer(float(splitLine[1]))
1307 self.rotatedLayers.append( self.rotatedLayer )
1309 elif firstWord == '(<edge>':
1312 def setGridVariables( self, repository ):
1313 'Set the grid variables.'
1314 self.gridInset = 1.2 * self.infillWidth
1315 self.gridRadius = self.infillWidth / self.infillSolidity
1316 self.gridXStepSize = 2.0 * self.gridRadius
1317 self.offsetMultiplier = self.gridRadius
1318 if self.repository.infillPatternGridHexagonal.value:
1319 self.gridXStepSize = 4.0 / 3.0 * self.gridRadius
1320 self.offsetMultiplier = 1.5 * self.gridXStepSize
1321 if self.repository.infillPatternGridCircular.value:
1322 self.gridRadius += self.gridRadius
1323 self.gridXStepSize = self.gridRadius / math.sqrt(.75)
1324 self.offsetMultiplier = 0.5 * self.gridXStepSize
1325 circleInsetOverEdgeWidth = repository.gridCircleSeparationOverEdgeWidth.value + 0.5
1326 self.gridMinimumCircleRadius = self.edgeWidth
1327 self.gridInset = self.gridMinimumCircleRadius
1328 self.gridCircleRadius = self.offsetMultiplier - circleInsetOverEdgeWidth * self.edgeWidth
1329 if self.gridCircleRadius < self.gridMinimumCircleRadius:
1331 print('!!! WARNING !!!')
1332 print('Grid Circle Separation over Edge Width is too high, which makes the grid circles too small.')
1333 print('You should reduce Grid Circle Separation over Edge Width to a reasonable value, like the default of 0.5.')
1334 print('The grid circle radius will be set to the minimum grid circle radius.')
1336 self.gridCircleRadius = self.gridMinimumCircleRadius
1337 self.offsetBaseX = 0.25 * self.gridXStepSize
1338 if self.repository.infillPatternGridRectangular.value:
1339 halfGridMinusWidth = 0.5 * ( self.gridRadius - self.infillWidth )
1340 self.gridJunctionEnd = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtEnd.value
1341 self.gridJunctionMiddle = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtMiddle.value
1346 def __init__( self, z ):
1348 self.rotatedCarveDictionary = {}
1349 self.rotation = None
1350 self.nestedRings = []
1354 'Get the string representation of this RotatedLayer.'
1355 return '%s, %s, %s' % ( self.z, self.rotation, self.nestedRings )
1358 class YIntersectionPath:
1359 'A class to hold the y intersection position, the loop which it intersected and the point index of the loop which it intersected.'
1360 def __init__( self, pathIndex, pointIndex, y ):
1361 'Initialize from the path, point index, and y.'
1362 self.pathIndex = pathIndex
1363 self.pointIndex = pointIndex
1367 'Get the string representation of this y intersection.'
1368 return '%s, %s, %s' % ( self.pathIndex, self.pointIndex, self.y )
1370 def getPath( self, paths ):
1371 'Get the path from the paths and path index.'
1372 return paths[ self.pathIndex ]
1374 def getPointIndexPlusOne(self):
1375 'Get the point index plus one.'
1376 return self.pointIndex + 1
1380 'Display the fill dialog.'
1381 if len(sys.argv) > 1:
1382 writeOutput(' '.join(sys.argv[1 :]))
1384 settings.startMainLoopFromConstructor(getNewRepository())
1386 if __name__ == '__main__':