1 """
2 Inset will inset the outside outlines by half the perimeter width, and outset the inside outlines by the same amount.
3
4 Credits:
5 Original Author: Enrique Perez (http://skeinforge.com)
6 Contributors: Please see the documentation in Skeinforge
7 Modifed as SFACT: Ahmet Cem Turan (github.com/ahmetcemturan/SFACT)
8
9 License:
10 GNU Affero General Public License http://www.gnu.org/licenses/agpl.html
11 """
12
13 from config import config, config
14 from fabmetheus_utilities import archive, euclidean, intercircle
15 from fabmetheus_utilities.geometry.solids import triangle_mesh
16 from entities import NestedRing, Layer, GcodeCommand, BoundaryPerimeter
17 from multiprocessing import Pool, Manager, Process
18 from utilities import class_pickler
19 import copy_reg
20 import logging
21 import math
22 import multiprocessing
23 import os
24 import sys
25 import types
26 from utilities import memory_tracker
27
28 logger = logging.getLogger(__name__)
29 name = __name__
30
41
43
44 "A class to inset a skein of extrusions."
46 self.slicedModel = slicedModel
47 self.overlapRemovalWidthOverPerimeterWidth = config.getfloat(name, 'overlap.removal.scaler')
48 self.nozzleDiameter = config.getfloat(name, 'nozzle.diameter')
49 self.bridgeWidthMultiplier = config.getfloat(name, 'bridge.width.multiplier.ratio')
50 self.loopOrderAscendingArea = config.getboolean(name, 'loop.order.preferloops')
51 self.layerThickness = self.slicedModel.runtimeParameters.layerThickness
52 self.perimeterWidth = self.slicedModel.runtimeParameters.perimeterWidth
53 self.halfPerimeterWidth = 0.5 * self.perimeterWidth
54 self.overlapRemovalWidth = self.perimeterWidth * (0.7853) * self.overlapRemovalWidthOverPerimeterWidth
55 self.multiprocess = config.getboolean(name, 'multiprocess')
56
58 "Inset the layers"
59
60 if self.multiprocess:
61 manager = Manager()
62 sharedLayers = manager.list(self.slicedModel.layers.values())
63
64 p = Pool()
65 resultLayers = p.map(self.addInsetForLayer, sharedLayers)
66 p.close()
67 p.join()
68
69 for resultLayer in resultLayers:
70 self.slicedModel.layers[resultLayer.z] = resultLayer
71
72 else:
73
74 for x in self.slicedModel.layers.values():
75 self.addInsetForLayer(x)
76
78 halfWidth = self.halfPerimeterWidth * 0.7853
79 if layer.bridgeRotation != None:
80 halfWidth = self.bridgeWidthMultiplier * ((2 * self.nozzleDiameter - self.layerThickness) / 2) * 0.7853
81
82 alreadyFilledArounds = []
83
84 for nestedRing in layer.nestedRings:
85 self.addInset(nestedRing, halfWidth, alreadyFilledArounds)
86 return layer
87
88 - def addInset(self, nestedRing, halfWidth, alreadyFilledArounds):
89 "Add inset to the layer."
90
91
92 for innerNestedRing in nestedRing.innerNestedRings:
93 self.addInset(innerNestedRing, halfWidth, alreadyFilledArounds)
94
95 boundary = [nestedRing.getXYBoundaries()]
96 insetBoundaryPerimeter = intercircle.getInsetLoopsFromLoops(halfWidth, boundary)
97
98 triangle_mesh.sortLoopsInOrderOfArea(not self.loopOrderAscendingArea, insetBoundaryPerimeter)
99
100 for loop in insetBoundaryPerimeter:
101 centerOutset = intercircle.getLargestCenterOutsetLoopFromLoopRegardless(loop, halfWidth)
102
103 "Add the perimeter block remainder of the loop which does not overlap the alreadyFilledArounds loops."
104 if self.overlapRemovalWidthOverPerimeterWidth < 0.1:
105 nestedRing.perimeter.addPath(centerOutset.center + [centerOutset.center[0]])
106 break
107 isIntersectingSelf = isIntersectingItself(centerOutset.center, self.overlapRemovalWidth)
108
109 if isIntersectingWithinLists(centerOutset.center, alreadyFilledArounds) or isIntersectingSelf:
110 self.addGcodeFromPerimeterPaths(nestedRing, isIntersectingSelf, centerOutset.center, alreadyFilledArounds, halfWidth, boundary)
111 else:
112 nestedRing.perimeter.addPath(centerOutset.center + [centerOutset.center[0]])
113 addAlreadyFilledArounds(alreadyFilledArounds, centerOutset.center, self.overlapRemovalWidth)
114
115
117 "Add the perimeter paths to the output."
118 segments = []
119 outlines = []
120 thickOutlines = []
121 allLoopLists = alreadyFilledArounds[:] + [thickOutlines]
122 aroundLists = alreadyFilledArounds
123 for pointIndex in xrange(len(loop)):
124 pointBegin = loop[pointIndex]
125 pointEnd = loop[(pointIndex + 1) % len(loop)]
126 if isIntersectingSelf:
127 if euclidean.isLineIntersectingLoops(outlines, pointBegin, pointEnd):
128 segments += getSegmentsFromLoopListsPoints(allLoopLists, pointBegin, pointEnd)
129 else:
130 segments += getSegmentsFromLoopListsPoints(alreadyFilledArounds, pointBegin, pointEnd)
131 addSegmentOutline(False, outlines, pointBegin, pointEnd, self.overlapRemovalWidth)
132 addSegmentOutline(True, thickOutlines, pointBegin, pointEnd, self.overlapRemovalWidth)
133 else:
134 segments += getSegmentsFromLoopListsPoints(alreadyFilledArounds, pointBegin, pointEnd)
135 perimeterPaths = []
136 path = []
137 muchSmallerThanRadius = 0.1 * halfWidth
138 segments = getInteriorSegments(boundary, segments)
139 for segment in segments:
140 pointBegin = segment[0].point
141 if not isCloseToLast(perimeterPaths, pointBegin, muchSmallerThanRadius):
142 path = [pointBegin]
143 perimeterPaths.append(path)
144 path.append(segment[1].point)
145 if len(perimeterPaths) > 1:
146 firstPath = perimeterPaths[0]
147 lastPath = perimeterPaths[-1]
148 if abs(lastPath[-1] - firstPath[0]) < 0.1 * muchSmallerThanRadius:
149 connectedBeginning = lastPath[:-1] + firstPath
150 perimeterPaths[0] = connectedBeginning
151 perimeterPaths.remove(lastPath)
152 muchGreaterThanRadius = 6.0 * halfWidth
153 for perimeterPath in perimeterPaths:
154 if euclidean.getPathLength(perimeterPath) > muchGreaterThanRadius:
155 nestedRing.perimeter.addPath(perimeterPath)
156
158 "Add already filled loops around loop to alreadyFilledArounds."
159 radius = abs(radius)
160 alreadyFilledLoop = []
161 slightlyGreaterThanRadius = 1.01 * radius
162 muchGreaterThanRadius = 2.5 * radius
163 centers = intercircle.getCentersFromLoop(loop, slightlyGreaterThanRadius)
164 for center in centers:
165 alreadyFilledInset = intercircle.getSimplifiedInsetFromClockwiseLoop(center, radius)
166 if intercircle.isLargeSameDirection(alreadyFilledInset, center, radius):
167 alreadyFilledLoop.append(alreadyFilledInset)
168 if len(alreadyFilledLoop) > 0:
169 alreadyFilledArounds.append(alreadyFilledLoop)
170
172 "Add a diamond or hexagonal outline for a line segment."
173 width = abs(width)
174 exclusionWidth = 0.6 * width
175 slope = 0.2
176 if isThick:
177 slope = 3.0
178 exclusionWidth = 0.8 * width
179 segment = pointEnd - pointBegin
180 segmentLength = abs(segment)
181 if segmentLength == 0.0:
182 return
183 normalizedSegment = segment / segmentLength
184 outline = []
185 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
186 pointBeginRotated = segmentYMirror * pointBegin
187 pointEndRotated = segmentYMirror * pointEnd
188 along = 0.05
189 alongLength = along * segmentLength
190 if alongLength > 0.1 * exclusionWidth:
191 along *= 0.1 * exclusionWidth / alongLength
192 alongEnd = 1.0 - along
193 remainingToHalf = 0.5 - along
194 alongToWidth = exclusionWidth / slope / segmentLength
195 pointBeginIntermediate = euclidean.getIntermediateLocation(along, pointBeginRotated, pointEndRotated)
196 pointEndIntermediate = euclidean.getIntermediateLocation(alongEnd, pointBeginRotated, pointEndRotated)
197 outline.append(pointBeginIntermediate)
198 verticalWidth = complex(0.0, exclusionWidth)
199 if alongToWidth > 0.9 * remainingToHalf:
200 verticalWidth = complex(0.0, slope * remainingToHalf * segmentLength)
201 middle = (pointBeginIntermediate + pointEndIntermediate) * 0.5
202 middleDown = middle - verticalWidth
203 middleUp = middle + verticalWidth
204 outline.append(middleUp)
205 outline.append(pointEndIntermediate)
206 outline.append(middleDown)
207 else:
208 alongOutsideBegin = along + alongToWidth
209 alongOutsideEnd = alongEnd - alongToWidth
210 outsideBeginCenter = euclidean.getIntermediateLocation(alongOutsideBegin, pointBeginRotated, pointEndRotated)
211 outsideBeginCenterDown = outsideBeginCenter - verticalWidth
212 outsideBeginCenterUp = outsideBeginCenter + verticalWidth
213 outsideEndCenter = euclidean.getIntermediateLocation(alongOutsideEnd, pointBeginRotated, pointEndRotated)
214 outsideEndCenterDown = outsideEndCenter - verticalWidth
215 outsideEndCenterUp = outsideEndCenter + verticalWidth
216 outline.append(outsideBeginCenterUp)
217 outline.append(outsideEndCenterUp)
218 outline.append(pointEndIntermediate)
219 outline.append(outsideEndCenterDown)
220 outline.append(outsideBeginCenterDown)
221 outlines.append(euclidean.getPointsRoundZAxis(normalizedSegment, outline))
222
224 'Get segments inside the loops.'
225 interiorSegments = []
226 for segment in segments:
227 center = 0.5 * (segment[0].point + segment[1].point)
228 if euclidean.getIsInFilledRegion(loops, center):
229 interiorSegments.append(segment)
230 return interiorSegments
231
239
240
242 "Get endpoint segments from the beginning and end of a line segment."
243 normalizedSegment = pointEnd - pointBegin
244 normalizedSegmentLength = abs(normalizedSegment)
245 if normalizedSegmentLength == 0.0:
246 return []
247 normalizedSegment /= normalizedSegmentLength
248 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
249 pointBeginRotated = segmentYMirror * pointBegin
250 pointEndRotated = segmentYMirror * pointEnd
251 rotatedLoopLists = []
252 for loopList in loopLists:
253 rotatedLoopList = []
254 rotatedLoopLists.append(rotatedLoopList)
255 for loop in loopList:
256 rotatedLoop = euclidean.getPointsRoundZAxis(segmentYMirror, loop)
257 rotatedLoopList.append(rotatedLoop)
258 xIntersectionIndexList = []
259 xIntersectionIndexList.append(euclidean.XIntersectionIndex(-1, pointBeginRotated.real))
260 xIntersectionIndexList.append(euclidean.XIntersectionIndex(-1, pointEndRotated.real))
261 euclidean.addXIntersectionIndexesFromLoopListsY(rotatedLoopLists, xIntersectionIndexList, pointBeginRotated.imag)
262 segments = euclidean.getSegmentsFromXIntersectionIndexes(xIntersectionIndexList, pointBeginRotated.imag)
263 for segment in segments:
264 for endpoint in segment:
265 endpoint.point *= normalizedSegment
266 return segments
267
269 "Determine if the point is close to the last point of the last path."
270 if len(paths) < 1:
271 return False
272 lastPath = paths[-1]
273 return abs(lastPath[-1] - point) < radius
274
276 "Determine if the loop is intersecting itself."
277 outlines = []
278 for pointIndex in xrange(len(loop)):
279 pointBegin = loop[pointIndex]
280 pointEnd = loop[(pointIndex + 1) % len(loop)]
281 if euclidean.isLineIntersectingLoops(outlines, pointBegin, pointEnd):
282 return True
283 addSegmentOutline(False, outlines, pointBegin, pointEnd, width)
284 return False
285
287 "Determine if the loop is intersecting or is within the loop lists."
288 for loopList in loopLists:
289 if getIsIntersectingWithinList(loop, loopList):
290 return True
291 return False
292
293 copy_reg.pickle(types.MethodType, class_pickler._pickle_method, class_pickler._unpickle_method)
294