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