1 """
2 Comb the extrusion hair of a gcode file. Modifies the travel paths so the nozzle does not go over empty spaces, thus reducing the strings that may build up.
3
4 Note: comb is called during gcode generation, not through the usual plugin channel. This is because the travel calculations are made at the last minute.
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 import logging
17 import math
18
19 name = __name__
20 logger = logging.getLogger(name)
21
23 "A class to comb a skein of extrusions."
25 'Initialize'
26 self.betweenTable = {}
27 self.z = layer.z
28
29 self.perimeterWidth = layer.runtimeParameters.perimeterWidth
30 self.combInset = 0.7 * self.perimeterWidth
31 self.betweenInset = 0.4 * self.perimeterWidth
32 self.uTurnWidth = 0.5 * self.betweenInset
33 self.travelFeedRateMinute = layer.runtimeParameters.travelFeedRateMinute
34
35 self.boundaries = []
36 perimeters = []
37 layer.getPerimeterPaths(perimeters)
38 for perimeter in perimeters:
39 x = []
40 for boundaryPoint in perimeter.boundaryPoints:
41 x.append(boundaryPoint.dropAxis())
42 self.boundaries.append(x)
43
45 "Set betweens for the layer."
46 if self.z in self.betweenTable:
47 return self.betweenTable[ self.z ]
48 if len(self.boundaries) == 0:
49 return []
50 self.betweenTable[ self.z ] = []
51 for boundaryLoop in self.boundaries:
52 self.betweenTable[ self.z ] += intercircle.getInsetLoopsFromLoop(boundaryLoop, self.betweenInset)
53 return self.betweenTable[ self.z ]
54
56 "Determine if the point on the line is at least as far from the loop as the center point."
57 if begin == end:
58 print('this should never happen but it does not really matter, begin == end in getIsAsFarAndNotIntersecting in comb.')
59 print(begin)
60 return True
61 return not euclidean.isLineIntersectingLoops(self.getBetweens(), begin, end)
62
63 - def getIsRunningJumpPathAdded(self, betweens, end, lastPoint, nearestEndMinusLastSegment, pathAround, penultimatePoint, runningJumpSpace):
64 "Add a running jump path if possible, and return if it was added."
65 jumpStartPoint = lastPoint - nearestEndMinusLastSegment * runningJumpSpace
66 if euclidean.isLineIntersectingLoops(betweens, penultimatePoint, jumpStartPoint):
67 return False
68 pathAround[-1] = jumpStartPoint
69 return True
70
72 "Get both paths along the loop from the point nearest to the begin to the point nearest to the end."
73 nearestBeginDistanceIndex = euclidean.getNearestDistanceIndex(begin, loop)
74 nearestEndDistanceIndex = euclidean.getNearestDistanceIndex(end, loop)
75 beginIndex = (nearestBeginDistanceIndex.index + 1) % len(loop)
76 endIndex = (nearestEndDistanceIndex.index + 1) % len(loop)
77 nearestBegin = euclidean.getNearestPointOnSegment(loop[ nearestBeginDistanceIndex.index ], loop[ beginIndex ], begin)
78 nearestEnd = euclidean.getNearestPointOnSegment(loop[ nearestEndDistanceIndex.index ], loop[ endIndex ], end)
79 clockwisePath = [ nearestBegin ]
80 widdershinsPath = [ nearestBegin ]
81 if nearestBeginDistanceIndex.index != nearestEndDistanceIndex.index:
82 widdershinsPath += euclidean.getAroundLoop(beginIndex, endIndex, loop)
83 clockwisePath += euclidean.getAroundLoop(endIndex, beginIndex, loop)[: :-1]
84 clockwisePath.append(nearestEnd)
85 widdershinsPath.append(nearestEnd)
86 return [ clockwisePath, widdershinsPath ]
87
89 "Add a path between the perimeter and the fill."
90 paths = self.getPathsByIntersectedLoop(points[1], points[2], loop)
91 shortestPath = paths[int(euclidean.getPathLength(paths[1]) < euclidean.getPathLength(paths[0]))]
92 if len(shortestPath) < 2:
93 return shortestPath
94 if abs(points[1] - shortestPath[0]) > abs(points[1] - shortestPath[-1]):
95 shortestPath.reverse()
96 loopWiddershins = euclidean.isWiddershins(loop)
97 pathBetween = []
98 for pointIndex in xrange(len(shortestPath)):
99 center = shortestPath[pointIndex]
100 centerPerpendicular = None
101 beginIndex = pointIndex - 1
102 if beginIndex >= 0:
103 begin = shortestPath[beginIndex]
104 centerPerpendicular = intercircle.getWiddershinsByLength(center, begin, self.combInset)
105 centerEnd = None
106 endIndex = pointIndex + 1
107 if endIndex < len(shortestPath):
108 end = shortestPath[endIndex]
109 centerEnd = intercircle.getWiddershinsByLength(end, center, self.combInset)
110 if centerPerpendicular == None:
111 centerPerpendicular = centerEnd
112 elif centerEnd != None:
113 centerPerpendicular = 0.5 * (centerPerpendicular + centerEnd)
114 between = None
115 if centerPerpendicular == None:
116 between = center
117 if between == None:
118 centerSideWiddershins = center + centerPerpendicular
119 if euclidean.isPointInsideLoop(loop, centerSideWiddershins) == loopWiddershins:
120 between = centerSideWiddershins
121 if between == None:
122 centerSideClockwise = center - centerPerpendicular
123 if euclidean.isPointInsideLoop(loop, centerSideClockwise) == loopWiddershins:
124 between = centerSideClockwise
125 if between == None:
126 between = center
127 pathBetween.append(between)
128 return pathBetween
129
131 "Insert paths between the perimeter and the fill."
132 self.z = z
133 aroundBetweenPath = []
134 points = [begin]
135 lineX = []
136 switchX = []
137 segment = euclidean.getNormalized(end - begin)
138 segmentYMirror = complex(segment.real, -segment.imag)
139 beginRotated = segmentYMirror * begin
140 endRotated = segmentYMirror * end
141 y = beginRotated.imag
142
143 for boundaryIndex in xrange(len(self.boundaries)):
144 boundary = self.boundaries[ boundaryIndex ]
145 boundaryRotated = euclidean.getPointsRoundZAxis(segmentYMirror, boundary)
146 euclidean.addXIntersectionIndexesFromLoopY(boundaryRotated, boundaryIndex, switchX, y)
147 switchX.sort()
148 maximumX = max(beginRotated.real, endRotated.real)
149 minimumX = min(beginRotated.real, endRotated.real)
150 for xIntersection in switchX:
151 if xIntersection.x > minimumX and xIntersection.x < maximumX:
152 point = segment * complex(xIntersection.x, y)
153 points.append(point)
154 lineX.append(xIntersection)
155 points.append(end)
156 lineXIndex = 0
157 while lineXIndex < len(lineX) - 1:
158 lineXFirst = lineX[lineXIndex]
159 lineXSecond = lineX[lineXIndex + 1]
160 loopFirst = self.boundaries[lineXFirst.index]
161 if lineXSecond.index == lineXFirst.index:
162 pathBetween = self.getPathBetween(loopFirst, points[lineXIndex : lineXIndex + 4])
163 pathBetween = self.getSimplifiedAroundPath(points[lineXIndex], points[lineXIndex + 3], loopFirst, pathBetween)
164 aroundBetweenPath += pathBetween
165 lineXIndex += 2
166 else:
167 lineXIndex += 1
168 return aroundBetweenPath
169
174
176 "Get the simplified begin path between the perimeter and the fill."
177 if len(pathAround) < 2:
178 return pathAround
179 pathIndex = 0
180 while pathIndex < len(pathAround) - 1:
181 if not self.getIsAsFarAndNotIntersecting(begin, pathAround[pathIndex + 1]):
182 return pathAround[pathIndex :]
183 pathIndex += 1
184 return pathAround[-1 :]
185
187 "Get the simplified end path between the perimeter and the fill."
188 if len(pathAround) < 2:
189 return pathAround
190 pathIndex = len(pathAround) - 1
191 while pathIndex > 0:
192 if not self.getIsAsFarAndNotIntersecting(end, pathAround[pathIndex - 1]):
193 return pathAround[: pathIndex + 1]
194 pathIndex -= 1
195 return pathAround[: 1]
196