chiark / gitweb /
Add back the ultimaker platform, and made the platform mesh simpler.
[cura.git] / Cura / slice / cura_sf / skeinforge_application / skeinforge_plugins / craft_plugins / fill.py
1 #! /usr/bin/env python
2 """
3 This page is in the table of contents.
4 Fill is a script to fill the edges of a gcode file.
5
6 The fill manual page is at:
7 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill
8
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/
11
12 ==Operation==
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.
14
15 ==Settings==
16 ===Diaphragm===
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.
18
19 ====Diaphragm Period====
20 Default is one hundred.
21
22 Defines the number of layers between diaphrams.
23
24 ====Diaphragm Thickness====
25 Default is zero, because the diaphragm feature is rarely used.
26
27 Defines the number of layers the diaphram is composed of.
28
29 ===Extra Shells===
30 The shells interior edge loops.  Adding extra shells makes the object stronger & heavier.
31
32 ====Extra Shells on Alternating Solid Layers====
33 Default is two.
34
35 Defines the number of extra shells, on the alternating solid layers.
36
37 ====Extra Shells on Base====
38 Default is one.
39
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.
41
42 ====Extra Shells on Sparse Layer====
43 Default is one.
44
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.
46
47 ===Grid===
48 ====Grid Circle Separation over Perimeter Width====
49 Default is 0.2.
50
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.
52
53 ====Grid Extra Overlap====
54 Default is 0.1.
55
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.
57
58 ====Grid Junction Separation over Octogon Radius At End====
59 Default is zero.
60
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.
62
63 ====Grid Junction Separation over Octogon Radius At Middle====
64 Default is zero.
65
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.
67
68 ====Grid Junction Separation Band Height====
69 Default is ten.
70
71 Defines the height of the bands of the accordion pattern.
72
73 ===Infill===
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.
76
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.
79
80 Because this pattern turns the extruder on and off often, it is best to use a stepper motor extruder.
81
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.
84
85 =====Grid Rectangular=====
86 When selected, the infill will be a funky octogon square honeycomb like pattern which gives the object extra strength.
87
88 =====Line=====
89 When selected, the infill will be made up of lines.
90
91 ====Infill Begin Rotation====
92 Default is forty five degrees, giving a diagonal infill.
93
94 Defines the amount the infill direction of the base and every second layer thereafter is rotated.
95
96 ====Infill Odd Layer Extra Rotation====
97 Default is ninety degrees, making the odd layer infill perpendicular to the base layer.
98
99 Defines the extra amount the infill direction of the odd layers is rotated compared to the base layer.
100
101 ====Infill Begin Rotation Repeat====
102 Default is one, giving alternating cross hatching.
103
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.
105
106 ====Infill Perimeter Overlap====
107 Default is 0.15.
108
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.
110
111 ====Infill Solidity====
112 Default is 0.2.
113
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.
115
116 ====Infill Width over Thickness====
117 Default is 1.5.
118
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.
120
121 ===Sharpest Angle===
122 Default: 60 degrees
123
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.
125
126 This parameter is used in fill, raft and skin.
127
128 ===Solid Surface Thickness===
129 Default is three.
130
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.
132
133 ===Start From Choice===
134 Default is 'Lower Left'.
135
136 Defines where each layer starts from.
137
138 ====Lower Left====
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
141
142 ====Nearest====
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.
144
145 ===Surrounding Angle===
146 Default: 60 degrees
147
148 Defines the angle that the surrounding layers around the infill are expanded.
149
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.
151
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.
153
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
156
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'.
159
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.
161
162 ====Infill > Loops > Perimeter====
163 ====Infill > Perimeter > Loops====
164 ====Loops > Infill > Perimeter====
165 ====Loops > Perimeter > Infill====
166 ====Perimeter > Infill > Loops====
167 ====Perimeter > Loops > Infill====
168
169 ==Examples==
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.
171
172 > python fill.py
173 This brings up the fill dialog.
174
175 > python fill.py Screw Holder Bottom.stl
176 The fill tool is parsing the file:
177 Screw Holder Bottom.stl
178 ..
179 The fill tool has created the file:
180 .. Screw Holder Bottom_fill.gcode
181
182 """
183
184 from __future__ import absolute_import
185
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
197 import math
198 import sys
199
200
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'
204
205
206
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)
221                 print(gridPoint)
222                 return
223         yCloseToCenterArounds = getClosestOppositeIntersectionPaths(aroundIntersectionPaths)
224         if len(yCloseToCenterArounds) < 2:
225                 return
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 )
232         pathIndexTable = {}
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:
243                 return
244         yCloseToCenterPaths = []
245         if isDoubleJunction:
246                 yCloseToCenterPaths = getClosestOppositeIntersectionPaths( yIntersectionPaths )
247         else:
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 )
255                 return
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 )
261
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)
268                         return
269
270 def addLoop(infillWidth, infillPaths, loop, rotationPlaneAngle):
271         'Add simplified path to fill.'
272         simplifiedLoop = euclidean.getSimplifiedLoop(loop, infillWidth)
273         if len(simplifiedLoop) < 2:
274                 return
275         simplifiedLoop.append(simplifiedLoop[0])
276         planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedLoop)
277         infillPaths.append(planeRotated)
278
279 def addPath(infillWidth, infillPaths, path, rotationPlaneAngle):
280         'Add simplified path to fill.'
281         simplifiedPath = euclidean.getSimplifiedPath(path, infillWidth)
282         if len(simplifiedPath) < 2:
283                 return
284         planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedPath)
285         infillPaths.append(planeRotated)
286
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 ):
291                         return
292
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 ):
297                         return
298
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:
303                 segmentTable = {}
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 )
315
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 )
320
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)
326
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:
331                         endpoints += segment
332                         return
333                 if abs(segment[0].point - segment[1].point) < doubleInfillWidth:
334                         endpoints += segment
335                         return
336                 if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey - 1, segment):
337                         endpoints += segment
338                         return
339                 if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey + 1, segment):
340                         endpoints += segment
341                         return
342         if solidSurfaceThickness == 0:
343                 removedEndpoints += segment
344                 return
345         if isSegmentCompletelyInAnIntersection(segment, surroundingXIntersections):
346                 removedEndpoints += segment
347                 return
348         endpoints += segment
349
350 def addYIntersectionPathToList( pathIndex, pointIndex, y, yIntersection, yIntersectionPaths ):
351         'Add the y intersection path to the y intersection paths.'
352         if yIntersection == None:
353                 return
354         yIntersectionPath = YIntersectionPath( pathIndex, pointIndex, yIntersection )
355         yIntersectionPath.yMinusCenter = yIntersection - y
356         yIntersectionPaths.append( yIntersectionPath )
357
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:
363                 return 1
364         if distanceFromCenter < distanceFromCenterOther:
365                 return - 1
366         return 0
367
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:
371                 return - 1
372         if self.pointIndex < other.pointIndex:
373                 return 1
374         return 0
375
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:
382                 return
383         if shouldExtraLoopsBeAdded:
384                 nestedRing.extraLoops += allFillLoops
385                 nestedRing.penultimateFillLoops = nestedRing.lastFillLoops
386         nestedRing.lastFillLoops = allFillLoops
387
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)
392
393 def getAdditionalLength( path, point, pointIndex ):
394         'Get the additional length added by inserting a point into a path.'
395         if pointIndex == 0:
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] )
400
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
412
413 def getCraftedText( fileName, gcodeText = '', repository=None):
414         'Fill the inset file or gcode text.'
415         return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
416
417 def getCraftedTextFromText(gcodeText, repository=None):
418         'Fill the inset gcode text.'
419         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'fill'):
420                 return gcodeText
421         if repository == None:
422                 repository = settings.getReadRepository( FillRepository() )
423         if not repository.activateFill.value:
424                 return gcodeText
425         return FillSkein().getCraftedGcode( repository, gcodeText )
426
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]
431                 if value != None:
432                         pathIndexTable[value] = None
433                 return True
434         return False
435
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
447
448 def getNewRepository():
449         'Get new repository.'
450         return FillRepository()
451
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 )
459         if isJunctionWide:
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 ]
470         return []
471
472 def getPlusMinusSign(number):
473         'Get one if the number is zero or positive else negative one.'
474         if number >= 0.0:
475                 return 1.0
476         return - 1.0
477
478 def getWithLeastLength( path, point ):
479         'Insert a point into a path, at the index at which the path would be shortest.'
480         if len(path) < 1:
481                 return 0
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
490
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:
495                 return None
496         if yIntersection < min( segmentFirstY, segmentSecondY ):
497                 return None
498         if yIntersection <= max( segmentFirstY, segmentSecondY ):
499                 return yIntersection
500         return None
501
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 )
506
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:
511                 if isBothOrNone:
512                         return
513                 intersectionPathSecond.gridPoint = gridPoint
514                 insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, intersectionPathSecond, width )
515                 return
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 )
520                 return
521         if isBothOrNone:
522                 return
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 )
528                 return
529         intersectionPathFirst.gridPoint = originalGridPointFirst
530         insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width )
531
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:
535                 return
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 )
563
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] ) ):
568                         return False
569         pointIndexMinusOne = pointIndex - 1
570         if pointIndexMinusOne >= 0:
571                 maskTable = {}
572                 begin = path[ pointIndexMinusOne ]
573                 if pointIndex < len(path):
574                         end = path[pointIndex]
575                         euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width )
576                 segmentTable = {}
577                 euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width )
578                 if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ):
579                         return False
580                 if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndexMinusOne ):
581                         return False
582         if pointIndex < len(path):
583                 maskTable = {}
584                 begin = path[pointIndex]
585                 if pointIndexMinusOne >= 0:
586                         end = path[ pointIndexMinusOne ]
587                         euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width )
588                 segmentTable = {}
589                 euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width )
590                 if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ):
591                         return False
592                 if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ):
593                         return False
594         return True
595
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:
601                 return False
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 ):
607                 return True
608         return euclidean.isXSegmentIntersectingPath( path[ pointIndex + 1 : pointIndex + 21 ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag )
609
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:
615                 return False
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 ):
621                 return True
622         return euclidean.isXSegmentIntersectingPaths( paths, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag )
623
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:
637                 return
638         if closestDistanceSquared < 0.8 * layerInfillWidth * layerInfillWidth:
639                 return
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)
644                 return True
645         return isSidePointAdded(pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width)
646
647 def isSegmentAround(aroundSegmentsDictionary, aroundSegmentsDictionaryKey, segment):
648         'Determine if there is another segment around.'
649         if aroundSegmentsDictionaryKey not in aroundSegmentsDictionary:
650                 return False
651         for aroundSegment in aroundSegmentsDictionary[aroundSegmentsDictionaryKey]:
652                 endpoint = aroundSegment[0]
653                 if isSegmentInX(segment, endpoint.point.real, endpoint.otherEndpoint.point.real):
654                         return True
655         return False
656
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 ):
663                         return True
664         return False
665
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 ):
671                 return False
672         return max( segmentFirstX, segmentSecondX ) > min( xFirst, xSecond )
673
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:
681                 return False
682         centerBeginComplex /= centerBeginLength
683         centerEndComplex /= centerEndLength
684         return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) > 0.9
685
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 ):
689                 return False
690         pointBegin = closestPath[ closestPointIndex - 1 ]
691         pointEnd = closestPath[ closestPointIndex ]
692         removedEndpointPoint = removedEndpointPoint
693         closest = pointBegin
694         farthest = pointEnd
695         removedMinusClosest = removedEndpointPoint - pointBegin
696         removedMinusClosestLength = abs( removedMinusClosest )
697         if removedMinusClosestLength <= 0.0:
698                 return False
699         removedMinusOther = removedEndpointPoint - pointEnd
700         removedMinusOtherLength = abs( removedMinusOther )
701         if removedMinusOtherLength <= 0.0:
702                 return False
703         insertPointAfter = None
704         insertPointBefore = None
705         if removedMinusOtherLength < removedMinusClosestLength:
706                 closest = pointEnd
707                 farthest = pointBegin
708                 removedMinusClosest = removedMinusOther
709                 removedMinusClosestLength = removedMinusOtherLength
710                 insertPointBefore = removedEndpointPoint
711         else:
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
721         maskTable = {}
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 ):
732                         return False
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 )
738         return True
739
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 )
747
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
756                                 return
757         yCloseToCenterPath.isOutside = True
758
759 def writeOutput(fileName, shouldAnalyze=True):
760         'Fill an inset gcode file.'
761         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'fill', shouldAnalyze)
762
763
764 class FillRepository(object):
765         'A class to handle the fill settings.'
766         def __init__(self):
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'
818
819         def execute(self):
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)
824
825
826 class FillSkein(object):
827         'A class to fill a skein of extrusions.'
828         def __init__(self):
829                 'Initialize.'
830                 self.distanceFeedRate = gcodec.DistanceFeedRate()
831                 self.edgeWidth = None
832                 self.extruderActive = False
833                 self.fillInset = 0.18
834                 self.isEdge = False
835                 self.lastExtraShells = - 1
836                 self.lineIndex = 0
837                 self.oldLocation = None
838                 self.oldOrderedLocation = None
839                 self.rotatedLayer = None
840                 self.rotatedLayers = []
841                 self.shutdownLineIndex = sys.maxint
842                 self.nestedRing = None
843                 self.thread = None
844
845         def addFill(self, layerIndex):
846                 'Add fill to the carve layer.'
847 #               if layerIndex > 2:
848 #                       return
849                 settings.printProgressByNumber(layerIndex, len(self.rotatedLayers), 'fill')
850                 arounds = []
851                 endpoints = []
852                 extraShells = self.repository.extraShellsSparseLayer.value
853                 infillPaths = []
854                 layerFillInset = self.fillInset
855                 layerInfillSolidity = self.infillSolidity
856                 layerRemainder = layerIndex % int(round(self.repository.diaphragmPeriod.value))
857                 layerRotation = self.getLayerRotation(layerIndex)
858                 pixelTable = {}
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)
870                                 else:
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:
878                         extraShells = 0
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
891                         else:
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)
901                 if len(arounds) < 1:
902                         self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer)
903                         return
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 = {}
911                 gridCircular = False
912                 removedEndpoints = []
913                 if len(surroundingCarves) >= self.doubleSolidSurfaceThickness:
914                         if self.repository.infillPatternGridCircular.value and self.repository.infillSolidity.value > 0.0:
915                                 gridCircular = True
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]
926                                 else:
927                                         surroundingXIntersections = []
928                                 addSparseEndpoints(doubleInfillWidth, endpoints, self.horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, layerInfillSolidity, removedEndpoints, self.solidSurfaceThickness, surroundingXIntersections)
929                 else:
930                         for segments in self.horizontalSegmentsDictionary.values():
931                                 for segment in segments:
932                                         endpoints += segment
933                 paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.infillWidth, pixelTable, self.sharpestProduct, aroundWidth)
934                 if gridCircular:
935                         startAngle = euclidean.globalGoldenAngle * float(layerIndex)
936                         for gridPoint in self.getGridPoints(fillLoops, reverseRotation):
937                                 self.addGridCircle(gridPoint, infillPaths, layerRotation, pixelTable, rotatedLoops, layerRotation, aroundWidth)
938                 else:
939                         if self.isGridToBeExtruded():
940                                 self.addGrid(
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)
947                 for path in paths:
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)
953
954         def addGcodeFromThreadZ( self, thread, z ):
955                 'Add a gcode thread to the output.'
956                 self.distanceFeedRate.addGcodeFromThreadZ( thread, z )
957
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:
961                         return
962                 explodedPaths = []
963                 pathGroups = []
964                 for path in paths:
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
1001                                 else:
1002                                         paths[ pathGroupIndex ] += explodedPath[1 :]
1003
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)
1013                                 return
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)
1023                         if shouldAddLoop:
1024                                 shouldAddLoop = euclidean.getIsInFilledRegion(rotatedLoops, point)
1025                         if shouldAddLoop:
1026                                 if insideIndexPath == None:
1027                                         insideIndexPath = [pointIndex]
1028                                         insideIndexPaths.append(insideIndexPath)
1029                                 else:
1030                                         insideIndexPath.append(pointIndex)
1031                         else:
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:
1040                         path = []
1041                         for insideIndex in insideIndexPath:
1042                                 if len(path) == 0:
1043                                         path.append(loop[insideIndex])
1044                                 path.append(loop[(insideIndex + 1) % len(loop)])
1045                         addPath(self.infillWidth, infillPaths, path, layerRotation)
1046
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:
1050                         return
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)
1060
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 )
1067
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):
1072                         return
1073                 layerDifference = abs(layerDelta)
1074                 rotatedLayer = self.rotatedLayers[layerIndex]
1075                 if layerDifference in rotatedLayer.rotatedCarveDictionary:
1076                         surroundingCarves.append(rotatedLayer.rotatedCarveDictionary[layerDifference])
1077                         return
1078                 nestedRings = rotatedLayer.nestedRings
1079                 rotatedCarve = []
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
1088
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>)')
1105
1106         def addToThread(self, location):
1107                 'Add a location to thread.'
1108                 if self.oldLocation == None:
1109                         return
1110                 if self.isEdge:
1111                         self.nestedRing.addToLoop( location )
1112                         return
1113                 if self.thread == None:
1114                         self.thread = [ self.oldLocation.dropAxis() ]
1115                         self.nestedRing.edgePaths.append(self.thread)
1116                 self.thread.append(location.dropAxis())
1117
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:
1137                         print('')
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.')
1141                         print('')
1142                 self.parseInitialization()
1143                 if self.edgeWidth == None:
1144                         print('Warning, nothing will be done because self.edgeWidth in getCraftedGcode in FillSkein was None.')
1145                         return ''
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()
1161
1162         def getGridPoints(self, fillLoops, reverseRotation):
1163                 'Get the grid points.'
1164                 if self.infillSolidity > 0.8:
1165                         return []
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))
1176
1177         def getGridPointsByLoops(self, gridRotationAngle, loops):
1178                 'Get the grid points by loops.'
1179                 gridIntersectionsDictionary = {}
1180                 gridPoints = []
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)
1194                 return gridPoints
1195
1196         def getLayerRotation(self, layerIndex):
1197                 'Get the layer rotation.'
1198                 rotation = self.rotatedLayers[layerIndex].rotation
1199                 if rotation != None:
1200                         return rotation
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)
1205
1206         def getNextGripXStep( self, gridXStep ):
1207                 'Get the next grid x step, increment by an extra one every three if hexagonal grid is chosen.'
1208                 gridXStep += 1
1209                 if self.repository.infillPatternGridHexagonal.value:
1210                         if gridXStep % 3 == 0:
1211                                 gridXStep += 1
1212                 return gridXStep
1213
1214         def isGridToBeExtruded(self):
1215                 'Determine if the grid is to be extruded.'
1216                 if self.repository.infillPatternLine.value:
1217                         return False
1218                 return self.repository.infillSolidity.value > 0.0
1219
1220         def isPointInsideLineSegments( self, gridPoint ):
1221                 'Is the point inside the line segments of the loops.'
1222                 if self.solidSurfaceThickness <= 0:
1223                         return True
1224                 fillLine = int(round(gridPoint.imag / self.infillWidth))
1225                 if fillLine not in self.horizontalSegmentsDictionary:
1226                         return False
1227                 if fillLine not in self.surroundingXIntersectionsDictionary:
1228                         return False
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):
1236                                         return True
1237                 return False
1238
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
1245
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)
1255                                 return
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)
1270
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:
1276                         return
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
1284                         self.isEdge = False
1285                         self.thread = None
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 )
1301                         self.thread = None
1302                 elif firstWord == '(<edge>':
1303                         self.isEdge = True
1304
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:
1323                                 print('')
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.')
1328                                 print('')
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
1335
1336
1337 class RotatedLayer(object):
1338         'A rotated layer.'
1339         def __init__( self, z ):
1340                 'Initialize.'
1341                 self.rotatedCarveDictionary = {}
1342                 self.rotation = None
1343                 self.nestedRings = []
1344                 self.z = z
1345
1346         def __repr__(self):
1347                 'Get the string representation of this RotatedLayer.'
1348                 return '%s, %s, %s' % ( self.z, self.rotation, self.nestedRings )
1349
1350
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
1357                 self.y = y
1358
1359         def __repr__(self):
1360                 'Get the string representation of this y intersection.'
1361                 return '%s, %s, %s' % ( self.pathIndex, self.pointIndex, self.y )
1362
1363         def getPath( self, paths ):
1364                 'Get the path from the paths and path index.'
1365                 return paths[ self.pathIndex ]
1366
1367         def getPointIndexPlusOne(self):
1368                 'Get the point index plus one.'
1369                 return self.pointIndex + 1
1370
1371
1372 def main():
1373         'Display the fill dialog.'
1374         if len(sys.argv) > 1:
1375                 writeOutput(' '.join(sys.argv[1 :]))
1376         else:
1377                 settings.startMainLoopFromConstructor(getNewRepository())
1378
1379 if __name__ == '__main__':
1380         main()