1 """
2 Support adds layers for supporting overhangs.
3 Extracted from the Skeinforge raft plugin.
4
5 Credits:
6 Original Author: Enrique Perez (http://skeinforge.com)
7 Contributors: Please see the documentation in Skeinforge
8 Modifed as SFACT: Ahmet Cem Turan (github.com/ahmetcemturan/SFACT)
9
10 License:
11 GNU Affero General Public License http://www.gnu.org/licenses/agpl.html
12 """
13
14 from config import config
15 from fabmetheus_utilities import archive, euclidean, intercircle
16 from fabmetheus_utilities.geometry.solids import triangle_mesh
17 from fabmetheus_utilities.vector3 import Vector3
18 from entities import SupportPath
19 import logging
20 import math
21 import os
22
23 name = __name__
24 logger = logging.getLogger(name)
25
32
34 'A class to support a skein of extrusions.'
36 self.slicedModel = slicedModel
37 self.boundaryLayers = []
38 self.supportLayers = []
39
40 self.debug = config.getboolean(name, 'debug')
41 self.supportLocation = config.get(name, 'location')
42 self.supportMinimumAngle = config.getfloat(name, 'min.angle')
43 self.minimumSupportRatio = math.tan(math.radians(self.supportMinimumAngle))
44 self.supportCrossHatch = config.getboolean(name, 'crosshatch')
45 self.supportFeedRate = config.getfloat('speed', 'feed.rate.support')
46 self.supportFlowRateRatio = config.getfloat('speed', 'flow.rate.support.ratio')
47
48 self.raftAdditionalMarginOverLengthPercent = config.getfloat(name, 'extension.percent')
49 self.raftMargin = config.getfloat(name, 'extension.distance')
50 self.infillOverhangOverExtrusionWidth = config.getfloat(name, 'infill.overhang.ratio')
51
52 self.supportStartFile = config.get(name, 'support.start.file')
53 self.supportEndFile = config.get(name, 'support.end.file')
54 self.absoluteSupportStartFilePath = os.path.join('alterations', self.supportStartFile)
55 self.absoluteSupportEndFilePath = os.path.join('alterations', self.supportEndFile)
56 self.supportStartLines = archive.getTextLines(archive.getFileText(self.absoluteSupportStartFilePath, printWarning=False))
57 self.supportEndLines = archive.getTextLines(archive.getFileText(self.absoluteSupportEndFilePath, printWarning=False))
58
59 self.extrusionWidth = config.getfloat('carve', 'extrusion.width')
60 self.supportGapOverPerimeterExtrusionWidth = config.getfloat(name, 'gap.over.perimeter.extrusion.width.ratio')
61 self.supportOutset = self.extrusionWidth * self.supportGapOverPerimeterExtrusionWidth
62
63 self.interfaceInfillDensity = config.getfloat(name, 'interface.infill.density')
64 self.interfaceLayerThicknessRatio = config.getfloat(name, 'interface.layer.thickness.ratio')
65 interfaceExtrusionWidth = self.extrusionWidth * self.interfaceLayerThicknessRatio
66 self.interfaceStep = interfaceExtrusionWidth / self.interfaceInfillDensity
67
68 self.cornerMinimum = self.slicedModel.carvingCornerMinimum
69 self.cornerMaximum = self.slicedModel.carvingCornerMaximum
70 self.cornerMinimumComplex = self.cornerMinimum.dropAxis()
71 self.cornerMaximumComplex = self.cornerMaximum.dropAxis()
72
74 'Add support layers to sliced model'
75
76 for layer in self.slicedModel.layers.values():
77 perimeters = []
78 layer.getPerimeterPaths(perimeters)
79 boundaryLayer = euclidean.LoopLayer(layer.z)
80 for perimeter in perimeters:
81 boundaryLoop = []
82 boundaryLayer.loops.append(boundaryLoop)
83 for boundaryPoint in perimeter.boundaryPoints:
84 boundaryLoop.append(boundaryPoint.dropAxis())
85 self.boundaryLayers.append(boundaryLayer)
86
87 if len(self.boundaryLayers) < 0:
88 logger.error('This should never happen, there are no boundary layers in support')
89 return
90
91 originalExtent = self.cornerMaximumComplex - self.cornerMinimumComplex
92 self.raftOutsetRadius = self.raftMargin + (self.raftAdditionalMarginOverLengthPercent * 0.01) * max(originalExtent.real, originalExtent.imag)
93 self.setBoundaryLayers()
94
95 for layer in self.slicedModel.layers.values():
96 endpoints = self.getSupportEndpoints(layer.index)
97 if len(endpoints) > 0:
98 self.addSupportLayer(endpoints, layer)
99
101 'Get the support layer segments.'
102 if len(self.supportLayers) <= layerIndex:
103 return []
104 supportSegmentTable = self.supportLayers[layerIndex].supportSegmentTable
105 if layerIndex % 2 == 1 and self.supportCrossHatch:
106 return getVerticalEndpoints(supportSegmentTable, self.interfaceStep, 0.1 * self.extrusionWidth, self.interfaceStep)
107 return euclidean.getEndpointsFromSegmentTable(supportSegmentTable)
108
110 'Set the boundary layers.'
111
112 if len(self.boundaryLayers) < 2:
113 return
114
115 if self.supportLocation == 'EmptyLayersOnly':
116 supportLayer = SupportLayer([])
117 self.supportLayers.append(supportLayer)
118 for boundaryLayerIndex in xrange(1, len(self.boundaryLayers) - 1):
119 self.addEmptyLayerSupport(boundaryLayerIndex)
120 self.truncateSupportSegmentTables()
121 self.addSegmentTablesToSupportLayers()
122 return
123
124 for boundaryLayer in self.boundaryLayers:
125
126 supportLoops = intercircle.getInsetSeparateLoopsFromLoops(-self.supportOutset, boundaryLayer.loops, 0.8)
127 supportLayer = SupportLayer(supportLoops)
128 self.supportLayers.append(supportLayer)
129
130
131 for supportLayerIndex in xrange(len(self.supportLayers) -1 ):
132 self.addSupportSegmentTable(supportLayerIndex)
133
134 self.truncateSupportSegmentTables()
135
136 for supportLayerIndex in xrange(len(self.supportLayers) - 1):
137 boundaryLoops = self.boundaryLayers[supportLayerIndex].loops
138 self.extendXIntersections(boundaryLoops, self.supportOutset, self.supportLayers[supportLayerIndex].xIntersectionsTable)
139
140 for supportLayer in self.supportLayers:
141 self.addToFillXIntersectionIndexTables(supportLayer)
142
143 if self.supportLocation == 'ExteriorOnly':
144 for supportLayerIndex in xrange(1, len(self.supportLayers)):
145 self.subtractJoinedFill(supportLayerIndex)
146
147 for supportLayer in self.supportLayers:
148 euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable)
149
150 for supportLayerIndex in xrange(len(self.supportLayers) - 2, -1, -1):
151 xIntersectionsTable = self.supportLayers[supportLayerIndex].xIntersectionsTable
152 aboveXIntersectionsTable = self.supportLayers[supportLayerIndex + 1].xIntersectionsTable
153 euclidean.joinXIntersectionsTables(aboveXIntersectionsTable, xIntersectionsTable)
154
155 for supportLayerIndex in xrange(len(self.supportLayers)):
156 supportLayer = self.supportLayers[supportLayerIndex]
157 self.extendXIntersections(supportLayer.supportLoops, self.raftOutsetRadius, supportLayer.xIntersectionsTable)
158
159 for supportLayer in self.supportLayers:
160 euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable)
161
162 self.addSegmentTablesToSupportLayers()
163
165 'Add support layer before the object layer.'
166
167
168 for line in self.supportStartLines:
169 layer.preSupportGcodeCommands.append(line)
170
171 aroundPixelTable = {}
172 aroundWidth = 0.25 * self.interfaceStep
173 boundaryLoops = self.boundaryLayers[layer.index].loops
174 halfSupportOutset = 0.5 * self.supportOutset
175 aroundBoundaryLoops = intercircle.getAroundsFromLoops(boundaryLoops, halfSupportOutset)
176 for aroundBoundaryLoop in aroundBoundaryLoops:
177 euclidean.addLoopToPixelTable(aroundBoundaryLoop, aroundPixelTable, aroundWidth)
178 paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * self.interfaceStep, aroundPixelTable, aroundWidth)
179 feedRateMinuteMultiplied = self.supportFeedRate * 60
180 supportFlowRateMultiplied = self.supportFlowRateRatio * self.supportFeedRate
181
182 for path in paths:
183 supportPath = SupportPath(layer.z, self.slicedModel.runtimeParameters)
184 supportPath.addPath(path)
185 layer.supportPaths.append(supportPath)
186
187 layer.postSupportGcodeCommands.extend(self.supportEndLines)
188
190 'Add support segments from the boundary layers.'
191 aboveLayer = self.boundaryLayers[ layerIndex + 1 ]
192 aboveLoops = aboveLayer.loops
193 supportLayer = self.supportLayers[layerIndex]
194
195 if len(aboveLoops) < 1:
196 return
197
198 boundaryLayer = self.boundaryLayers[layerIndex]
199 rise = aboveLayer.z - boundaryLayer.z
200
201 outsetSupportLoops = intercircle.getInsetSeparateLoopsFromLoops(-self.minimumSupportRatio * rise, boundaryLayer.loops)
202 numberOfSubSteps = 4
203 subStepSize = self.interfaceStep / float(numberOfSubSteps)
204 aboveIntersectionsTable = {}
205 euclidean.addXIntersectionsFromLoopsForTable(aboveLoops, aboveIntersectionsTable, subStepSize)
206 outsetIntersectionsTable = {}
207 euclidean.addXIntersectionsFromLoopsForTable(outsetSupportLoops, outsetIntersectionsTable, subStepSize)
208 euclidean.subtractXIntersectionsTable(aboveIntersectionsTable, outsetIntersectionsTable)
209 for aboveIntersectionsTableKey in aboveIntersectionsTable.keys():
210 supportIntersectionsTableKey = int(round(float(aboveIntersectionsTableKey) / numberOfSubSteps))
211 xIntersectionIndexList = []
212 if supportIntersectionsTableKey in supportLayer.xIntersectionsTable:
213 euclidean.addXIntersectionIndexesFromXIntersections(0, xIntersectionIndexList, supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ])
214 euclidean.addXIntersectionIndexesFromXIntersections(1, xIntersectionIndexList, aboveIntersectionsTable[ aboveIntersectionsTableKey ])
215 supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ] = euclidean.getJoinOfXIntersectionIndexes(xIntersectionIndexList)
216
218 'Add segment tables to the support layers.'
219 for supportLayer in self.supportLayers:
220 supportLayer.supportSegmentTable = {}
221 xIntersectionsTable = supportLayer.xIntersectionsTable
222 for xIntersectionsTableKey in xIntersectionsTable:
223 y = xIntersectionsTableKey * self.interfaceStep
224 supportLayer.supportSegmentTable[ xIntersectionsTableKey ] = euclidean.getSegmentsFromXIntersections(xIntersectionsTable[ xIntersectionsTableKey ], y)
225
237
239 'Join the fill then subtract it from the support layer table.'
240 supportLayer = self.supportLayers[supportLayerIndex]
241 fillXIntersectionsTable = supportLayer.fillXIntersectionsTable
242 belowFillXIntersectionsTable = self.supportLayers[ supportLayerIndex - 1 ].fillXIntersectionsTable
243 euclidean.joinXIntersectionsTables(belowFillXIntersectionsTable, supportLayer.fillXIntersectionsTable)
244 euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable)
245
247 'Truncate the support segments after the last support segment which contains elements.'
248 for supportLayerIndex in xrange(len(self.supportLayers) - 1, -1, -1):
249 if len(self.supportLayers[supportLayerIndex].xIntersectionsTable) > 0:
250 self.supportLayers = self.supportLayers[ : supportLayerIndex + 1 ]
251 return
252 self.supportLayers = []
253
255 'Add fill segments from the boundary layers.'
256 supportLoops = supportLayer.supportLoops
257 supportLayer.fillXIntersectionsTable = {}
258 if len(supportLoops) < 1:
259 return
260 euclidean.addXIntersectionsFromLoopsForTable(supportLoops, supportLayer.fillXIntersectionsTable, self.interfaceStep)
261
263 'Extend the support segments.'
264 xIntersectionsTableKeys = xIntersectionsTable.keys()
265 for xIntersectionsTableKey in xIntersectionsTableKeys:
266 lineSegments = euclidean.getSegmentsFromXIntersections(xIntersectionsTable[ xIntersectionsTableKey ], xIntersectionsTableKey)
267 xIntersectionIndexList = []
268 loopXIntersections = []
269 euclidean.addXIntersectionsFromLoops(loops, loopXIntersections, xIntersectionsTableKey)
270 for lineSegmentIndex in xrange(len(lineSegments)):
271 lineSegment = lineSegments[ lineSegmentIndex ]
272 extendedLineSegment = getExtendedLineSegment(radius, lineSegment, loopXIntersections)
273 if extendedLineSegment != None:
274 euclidean.addXIntersectionIndexesFromSegment(lineSegmentIndex, extendedLineSegment, xIntersectionIndexList)
275 xIntersections = euclidean.getJoinOfXIntersectionIndexes(xIntersectionIndexList)
276 if len(xIntersections) > 0:
277 xIntersectionsTable[ xIntersectionsTableKey ] = xIntersections
278 else:
279 del xIntersectionsTable[ xIntersectionsTableKey ]
280
282 'Get the cross hatch point line.'
283 if not crossHatchPointLineTable.has_key(y):
284 crossHatchPointLineTable[ y ] = {}
285 return crossHatchPointLineTable[ y ]
286
288 'Get endpoints from the y intersections.'
289 endpoints = []
290 for yIntersectionIndex in xrange(0, len(yIntersections), 2):
291 firstY = yIntersections[ yIntersectionIndex ]
292 secondY = yIntersections[ yIntersectionIndex + 1 ]
293 if firstY != secondY:
294 firstComplex = complex(x, firstY)
295 secondComplex = complex(x, secondY)
296 endpointFirst = euclidean.Endpoint()
297 endpointSecond = euclidean.Endpoint().getFromOtherPoint(endpointFirst, secondComplex)
298 endpointFirst.getFromOtherPoint(endpointSecond, firstComplex)
299 endpoints.append(endpointFirst)
300 endpoints.append(endpointSecond)
301 return endpoints
302
303 -def getExtendedLineSegment(extensionDistance, lineSegment, loopXIntersections):
304 'Get extended line segment.'
305 pointBegin = lineSegment[0].point
306 pointEnd = lineSegment[1].point
307 segment = pointEnd - pointBegin
308 segmentLength = abs(segment)
309 if segmentLength <= 0.0:
310 logger.error('This should never happen in getExtendedLineSegment in raft, the segment should have a length greater than zero. %s', lineSegment)
311 return None
312 segmentExtend = segment * extensionDistance / segmentLength
313 lineSegment[0].point -= segmentExtend
314 lineSegment[1].point += segmentExtend
315 for loopXIntersection in loopXIntersections:
316 setExtendedPoint(lineSegment[0], pointBegin, loopXIntersection)
317 setExtendedPoint(lineSegment[1], pointEnd, loopXIntersection)
318 return lineSegment
319
328
330 'Get vertical endpoints.'
331 interfaceSegmentsTableKeys = horizontalSegmentsTable.keys()
332 interfaceSegmentsTableKeys.sort()
333 verticalTableTable = {}
334 for interfaceSegmentsTableKey in interfaceSegmentsTableKeys:
335 interfaceSegments = horizontalSegmentsTable[interfaceSegmentsTableKey]
336 for interfaceSegment in interfaceSegments:
337 begin = int(round(interfaceSegment[0].point.real / verticalStep))
338 end = int(round(interfaceSegment[1].point.real / verticalStep))
339 for stepIndex in xrange(begin, end + 1):
340 if stepIndex not in verticalTableTable:
341 verticalTableTable[stepIndex] = {}
342 verticalTableTable[stepIndex][interfaceSegmentsTableKey] = None
343 verticalTableTableKeys = verticalTableTable.keys()
344 verticalTableTableKeys.sort()
345 verticalEndpoints = []
346 for verticalTableTableKey in verticalTableTableKeys:
347 verticalTable = verticalTableTable[verticalTableTableKey]
348 verticalTableKeys = verticalTable.keys()
349 verticalTableKeys.sort()
350 xIntersections = []
351 for verticalTableKey in verticalTableKeys:
352 y = verticalTableKey * horizontalStep
353 if verticalTableKey - 1 not in verticalTableKeys:
354 xIntersections.append(y - verticalOverhang)
355 if verticalTableKey + 1 not in verticalTableKeys:
356 xIntersections.append(y + verticalOverhang)
357 for segment in euclidean.getSegmentsFromXIntersections(xIntersections, verticalTableTableKey * verticalStep):
358 for endpoint in segment:
359 endpoint.point = complex(endpoint.point.imag, endpoint.point.real)
360 verticalEndpoints.append(endpoint)
361 return verticalEndpoints
362
363 -def setExtendedPoint(lineSegmentEnd, pointOriginal, x):
364 'Set the point in the extended line segment.'
365 if x > min(lineSegmentEnd.point.real, pointOriginal.real) and x < max(lineSegmentEnd.point.real, pointOriginal.real):
366 lineSegmentEnd.point = complex(x, pointOriginal.imag)
367
369 'Support loops with segment tables.'
371 self.supportLoops = supportLoops
372 self.supportSegmentTable = {}
373 self.xIntersectionsTable = {}
374
376 'Get the string representation of this loop layer.'
377 return '%s' % (self.supportLoops)
378