Package fabmetheus_utilities :: Module intercircle
[hide private]
[frames] | no frames]

Source Code for Module fabmetheus_utilities.intercircle

  1  """ 
  2  Intercircle is a collection of utilities for intersecting circles, used to get smooth loops around a collection of points and inset & outset loops. 
  3   
  4  """ 
  5   
  6  from fabmetheus_utilities.vector3 import Vector3 
  7  from fabmetheus_utilities import euclidean 
  8  import math 
  9   
 10  __author__ = 'Enrique Perez (perez_enrique@yahoo.com)' 
 11  __date__ = '$Date: 2008/21/04 $' 
 12  __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' 
 13   
 14   
 15  globalDecreasingRadiusMultipliers = [1.0, 0.55, 0.35, 0.2] 
 16   
 17   
18 -def addCircleIntersectionLoop(circleIntersectionLoop, circleIntersections):
19 'Add a circle intersection loop.' 20 firstCircleIntersection = circleIntersectionLoop[0] 21 circleIntersectionAhead = firstCircleIntersection 22 for circleIntersectionIndex in xrange(len(circleIntersections) + 1): 23 circleIntersectionAhead = circleIntersectionAhead.getCircleIntersectionAhead() 24 if circleIntersectionAhead == firstCircleIntersection or circleIntersectionAhead == None: 25 firstCircleIntersection.steppedOn = True 26 return 27 circleIntersectionAhead.addToList(circleIntersectionLoop) 28 firstCircleIntersection.steppedOn = True 29 print( 'addCircleIntersectionLoop would have gone into an endless loop, this should never happen.' ) 30 print( 'circleIntersectionLoop' ) 31 for circleIntersection in circleIntersectionLoop: 32 print(circleIntersection) 33 print(circleIntersection.circleNodeAhead) 34 print(circleIntersection.circleNodeBehind) 35 print('firstCircleIntersection') 36 print(firstCircleIntersection) 37 print('circleIntersections') 38 for circleIntersection in circleIntersections: 39 print(circleIntersection)
40
41 -def addEndCap(begin, end, points, radius):
42 'Get circular end cap.' 43 beginMinusEnd = begin - end 44 beginMinusEndLength = abs(beginMinusEnd) 45 if beginMinusEndLength <= 0.0: 46 points.append(begin) 47 return 48 beginMinusEnd *= radius / beginMinusEndLength 49 perpendicular = complex(-beginMinusEnd.imag, beginMinusEnd.real) 50 numberOfSides = 20 # to end up with close to unit length corners, 5 * 4 51 numberOfPositiveSides = numberOfSides / 2 52 totalAngle = 0.0 53 angle = euclidean.globalTau / float(numberOfSides) 54 # dotProductMultiplier to compensate for the corner outset in addInsetPointFromClockwiseTriple 55 dotProductMultiplier = 2.0 - 1.0 / math.cos(0.5 * angle) 56 for sideIndex in xrange(numberOfPositiveSides + 1): 57 circumferentialPoint = math.sin(totalAngle) * beginMinusEnd + math.cos(totalAngle) * perpendicular 58 points.append(begin + circumferentialPoint * dotProductMultiplier) 59 totalAngle += angle
60
61 -def addHalfPath(path, points, radius, thresholdRatio=0.9):
62 'Add the points from every point on a half path and between points.' 63 lessThanRadius = 0.7853 * radius 64 for pointIndex in xrange(len(path) - 1): 65 begin = path[pointIndex] 66 center = path[pointIndex + 1] 67 centerBegin = getWiddershinsByLength(begin, center, radius) 68 if centerBegin != None: 69 addPointsFromSegment(begin + centerBegin, center + centerBegin, points, lessThanRadius, thresholdRatio) 70 endIndex = pointIndex + 2 71 if endIndex < len(path): 72 end = path[endIndex] 73 centerEnd = getWiddershinsByLength(center, end, radius) 74 if centerBegin != None and centerEnd != None: 75 centerPerpendicular = 0.5 * (centerBegin + centerEnd) 76 points.append(center + centerPerpendicular) 77 if euclidean.getCrossProduct(centerBegin, centerEnd) < 0.0: 78 points.append(center + centerBegin) 79 points.append(center + centerEnd) 80 else: 81 points.append(center) 82 addEndCap(path[0], path[1], points, radius)
83
84 -def addInsetPointFromClockwiseTriple(begin, center, end, loop, radius):
85 'Get inset point with possible intersection from clockwise triple, out from widdershins loop.' 86 centerMinusBegin = center - begin 87 centerMinusBeginLength = abs(centerMinusBegin) 88 centerMinusBeginClockwise = None 89 if centerMinusBeginLength > 0.0: 90 centerMinusBeginClockwise = complex(centerMinusBegin.imag, -centerMinusBegin.real) / centerMinusBeginLength 91 endMinusCenter = end - center 92 endMinusCenterLength = abs(endMinusCenter) 93 endMinusCenterClockwise = None 94 if endMinusCenterLength > 0.0: 95 endMinusCenterClockwise = complex(endMinusCenter.imag, -endMinusCenter.real) / endMinusCenterLength 96 if centerMinusBeginClockwise == None and endMinusCenterClockwise == None: 97 return 98 if centerMinusBeginClockwise == None: 99 loop.append(center + endMinusCenterClockwise * radius) 100 return 101 if endMinusCenterClockwise == None: 102 loop.append(center + centerMinusBeginClockwise * radius) 103 return 104 centerClockwise = 0.5 * (centerMinusBeginClockwise + endMinusCenterClockwise) 105 dotProduct = euclidean.getDotProduct(centerMinusBeginClockwise, centerClockwise) 106 loop.append(center + centerClockwise * radius / max(0.4, abs(dotProduct))) # 0.4 to avoid pointy corners
107
108 -def addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ):
109 'Add orbits with the extruder off.' 110 timeInOrbit = 0.0 111 while timeInOrbit < temperatureChangeTime: 112 for point in loop: 113 distanceFeedRate.addGcodeMovementZWithFeedRate( 60.0 * orbitalFeedRatePerSecond, point, z ) 114 timeInOrbit += euclidean.getLoopLength(loop) / orbitalFeedRatePerSecond
115
116 -def addOrbitsIfLarge( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ):
117 'Add orbits with the extruder off if the orbits are large enough.' 118 if orbitsAreLarge( loop, temperatureChangeTime ): 119 addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z )
120
121 -def addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio=0.9 ):
122 'Add point complexes between the endpoints of a segment.' 123 if radius <= 0.0: 124 print('This should never happen, radius should never be zero or less in addPointsFromSegment in intercircle.') 125 thresholdRadius = radius * thresholdRatio # a higher number would be faster but would leave bigger dangling loops and extra dangling loops. 126 thresholdDiameter = thresholdRadius + thresholdRadius 127 segment = pointEnd - pointBegin 128 segmentLength = abs(segment) 129 extraCircles = int( math.floor( segmentLength / thresholdDiameter ) ) 130 if extraCircles < 1: 131 return 132 if segmentLength == 0.0: 133 print('This should never happen, segmentLength = 0.0 in intercircle.') 134 print('pointBegin') 135 print( pointBegin ) 136 print( pointEnd ) 137 return 138 if extraCircles < 2: 139 lengthIncrement = segmentLength / ( float(extraCircles) + 1.0 ) 140 segment *= lengthIncrement / segmentLength 141 pointBegin += segment 142 else: 143 pointBegin += segment * thresholdDiameter / segmentLength 144 remainingLength = segmentLength - thresholdDiameter - thresholdDiameter 145 lengthIncrement = remainingLength / ( float(extraCircles) - 1.0 ) 146 segment *= lengthIncrement / segmentLength 147 for circleIndex in xrange(extraCircles): 148 points.append( pointBegin ) 149 pointBegin += segment
150
151 -def directLoop(isWiddershins, loop):
152 'Direct the loop.' 153 if euclidean.isWiddershins(loop) != isWiddershins: 154 loop.reverse()
155
156 -def directLoops(isWiddershins, loops):
157 'Direct the loops.' 158 for loop in loops: 159 directLoop(isWiddershins, loop)
160
161 -def directLoopLists(isWiddershins, loopLists):
162 'Direct the loop lists.' 163 for loopList in loopLists: 164 directLoops(isWiddershins, loopList)
165
166 -def getAroundsFromLoop(loop, radius, thresholdRatio=0.9):
167 'Get the arounds from the loop.' 168 return getAroundsFromPoints(getPointsFromLoop(loop, 1.01 * abs(radius), thresholdRatio), radius)
169
170 -def getAroundsFromLoops( loops, radius, thresholdRatio=0.9 ):
171 'Get the arounds from the loops.' 172 return getAroundsFromPoints(getPointsFromLoops(loops, 1.01 * abs(radius), thresholdRatio), radius)
173
174 -def getAroundsFromPath(path, radius, thresholdRatio=0.9):
175 'Get the arounds from the path.' 176 radius = abs(radius) 177 points = getPointsFromPath(path, radius, thresholdRatio) 178 return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9)
179
180 -def getAroundsFromPathPoints(points, radius, thresholdRatio=0.9):
181 'Get the arounds from the path.' 182 centers = getCentersFromPoints(points, 0.8 * radius) 183 arounds = [] 184 for center in centers: 185 if euclidean.isWiddershins(center): 186 arounds.append(euclidean.getSimplifiedPath(center, radius)) 187 return arounds
188
189 -def getAroundsFromPaths(paths, radius, thresholdRatio=0.9):
190 'Get the arounds from the path.' 191 radius = abs(radius) 192 points = [] 193 for path in paths: 194 points += getPointsFromPath(path, radius, thresholdRatio) 195 return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9)
196
197 -def getAroundsFromPoints( points, radius ):
198 'Get the arounds from the points.' 199 arounds = [] 200 radius = abs(radius) 201 centers = getCentersFromPoints( points, radius ) 202 for center in centers: 203 inset = getSimplifiedInsetFromClockwiseLoop( center, radius ) 204 if isLargeSameDirection( inset, center, radius ): 205 arounds.append(inset) 206 return arounds
207
208 -def getCentersFromCircleNodes( circleNodes, radius ):
209 'Get the complex centers of the circle intersection loops from circle nodes.' 210 if len( circleNodes ) < 2: 211 return [] 212 circleIntersections = getCircleIntersectionsFromCircleNodes( circleNodes ) 213 circleIntersectionLoops = getCircleIntersectionLoops( circleIntersections ) 214 centers = [] 215 for circleIntersectionLoop in circleIntersectionLoops: 216 loop = [] 217 for circleIntersection in circleIntersectionLoop: 218 loop.append(circleIntersection.circleNodeAhead.actualPoint) 219 centers.append( loop ) 220 return centers
221
222 -def getCentersFromLoop( loop, radius ):
223 'Get the centers of the loop.' 224 circleNodes = getCircleNodesFromLoop( loop, radius ) 225 return getCentersFromCircleNodes( circleNodes, radius )
226
227 -def getCentersFromLoopDirection( isWiddershins, loop, radius ):
228 'Get the centers of the loop which go around in the given direction.' 229 centers = getCentersFromLoop( loop, radius ) 230 return getLoopsFromLoopsDirection( isWiddershins, centers )
231
232 -def getCentersFromPoints(points, radius):
233 'Get the centers from the points.' 234 circleNodes = getCircleNodesFromPoints(points, abs(radius)) 235 return getCentersFromCircleNodes(circleNodes, abs(radius))
236
237 -def getCircleIntersectionsFromCircleNodes(circleNodes):
238 'Get all the circle intersections which exist between all the circle nodes.' 239 if len( circleNodes ) < 1: 240 return [] 241 circleIntersections = [] 242 index = 0 243 pixelTable = {} 244 for circleNode in circleNodes: 245 euclidean.addElementToPixelListFromPoint(circleNode, pixelTable, circleNode.dividedPoint) 246 accumulatedCircleNodeTable = {} 247 for circleNodeIndex in xrange(len(circleNodes)): 248 circleNodeBehind = circleNodes[circleNodeIndex] 249 circleNodeIndexMinusOne = circleNodeIndex - 1 250 if circleNodeIndexMinusOne >= 0: 251 circleNodeAdditional = circleNodes[circleNodeIndexMinusOne] 252 euclidean.addElementToPixelListFromPoint(circleNodeAdditional, accumulatedCircleNodeTable, 0.5 * circleNodeAdditional.dividedPoint) 253 withinNodes = circleNodeBehind.getWithinNodes(accumulatedCircleNodeTable) 254 for circleNodeAhead in withinNodes: 255 circleIntersectionForward = CircleIntersection(circleNodeAhead, index, circleNodeBehind) 256 if not circleIntersectionForward.isWithinCircles(pixelTable): 257 circleIntersections.append(circleIntersectionForward) 258 circleNodeBehind.circleIntersections.append(circleIntersectionForward) 259 index += 1 260 circleIntersectionBackward = CircleIntersection(circleNodeBehind, index, circleNodeAhead) 261 if not circleIntersectionBackward.isWithinCircles(pixelTable): 262 circleIntersections.append(circleIntersectionBackward) 263 circleNodeAhead.circleIntersections.append(circleIntersectionBackward) 264 index += 1 265 return circleIntersections
266
267 -def getCircleIntersectionLoops( circleIntersections ):
268 'Get all the loops going through the circle intersections.' 269 circleIntersectionLoops = [] 270 for circleIntersection in circleIntersections: 271 if not circleIntersection.steppedOn: 272 circleIntersectionLoop = [ circleIntersection ] 273 circleIntersectionLoops.append( circleIntersectionLoop ) 274 addCircleIntersectionLoop( circleIntersectionLoop, circleIntersections ) 275 return circleIntersectionLoops
276
277 -def getCircleNodesFromLoop(loop, radius, thresholdRatio=0.9):
278 'Get the circle nodes from every point on a loop and between points.' 279 radius = abs(radius) 280 points = getPointsFromLoop( loop, radius, thresholdRatio ) 281 return getCircleNodesFromPoints( points, radius )
282
283 -def getCircleNodesFromPoints(points, radius):
284 'Get the circle nodes from a path.' 285 circleNodes = [] 286 oneOverRadius = 1.000001 / radius # to avoid problem of accidentally integral radius 287 points = euclidean.getAwayPoints(points, radius) 288 for point in points: 289 circleNodes.append(CircleNode(oneOverRadius, point)) 290 return circleNodes
291
292 -def getInsetLoopsFromLoop(loop, radius, thresholdRatio=0.9):
293 'Get the inset loops, which might overlap.' 294 isInset = radius > 0 295 insetLoops = [] 296 isLoopWiddershins = euclidean.isWiddershins(loop) 297 arounds = getAroundsFromLoop(loop, radius, thresholdRatio) 298 for around in arounds: 299 leftPoint = euclidean.getLeftPoint(around) 300 shouldBeWithin = (isInset == isLoopWiddershins) 301 if euclidean.isPointInsideLoop(loop, leftPoint) == shouldBeWithin: 302 if isLoopWiddershins != euclidean.isWiddershins(around): 303 around.reverse() 304 insetLoops.append(around) 305 return insetLoops
306
307 -def getInsetLoopsFromLoops(inset, loops):
308 'Get the inset loops, which might overlap.' 309 insetLoops = [] 310 for loop in loops: 311 insetLoops += getInsetLoopsFromLoop(loop, inset) 312 return insetLoops
313
314 -def getInsetLoopsFromVector3Loop(loop, radius, thresholdRatio=0.9):
315 'Get the inset loops from vector3 loop, which might overlap.' 316 if len(loop) < 2: 317 return [loop] 318 loopComplex = euclidean.getComplexPath(loop) 319 loopComplexes = getInsetLoopsFromLoop(loopComplex, radius) 320 return euclidean.getVector3Paths(loopComplexes, loop[0].z)
321
322 -def getInsetSeparateLoopsFromLoops(inset, loops, thresholdRatio=0.9):
323 'Get the separate inset loops.' 324 isInset = inset > 0 325 insetSeparateLoops = [] 326 radius = abs(inset) 327 arounds = getAroundsFromLoops(loops, radius, thresholdRatio) 328 for around in arounds: 329 leftPoint = euclidean.getLeftPoint(around) 330 if isInset == euclidean.getIsInFilledRegion(loops, leftPoint): 331 if isInset: 332 around.reverse() 333 insetSeparateLoops.append(around) 334 return insetSeparateLoops
335
336 -def getLargestCenterOutsetLoopFromLoop(loop, radius, thresholdRatio=0.9):
337 'Get the largest circle outset loop from the loop.' 338 radius = abs(radius) 339 points = getPointsFromLoop(loop, 1.01 * radius, thresholdRatio) 340 centers = getCentersFromPoints(points, radius) 341 largestCenterOutset = None 342 largestOutsetArea = -987654321.0 343 for center in centers: 344 outset = getSimplifiedInsetFromClockwiseLoop(center, radius) 345 if isLargeSameDirection(outset, center, radius): 346 if euclidean.isPathInsideLoop(loop, outset) != euclidean.isWiddershins(loop): 347 centerOutset = CenterOutset(center, outset) 348 outsetArea = abs(euclidean.getAreaLoop(outset)) 349 if outsetArea > largestOutsetArea: 350 largestOutsetArea = outsetArea 351 largestCenterOutset = centerOutset 352 if largestCenterOutset == None: 353 return None 354 largestCenterOutset.center = euclidean.getSimplifiedLoop(largestCenterOutset.center, radius) 355 return largestCenterOutset
356
357 -def getLargestCenterOutsetLoopFromLoopRegardless(loop, radius):
358 'Get the largest circle outset loop from the loop, even if the radius has to be shrunk and even if there is still no outset loop.' 359 global globalDecreasingRadiusMultipliers 360 for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers: 361 decreasingRadius = radius * decreasingRadiusMultiplier 362 largestCenterOutsetLoop = getLargestCenterOutsetLoopFromLoop(loop, decreasingRadius) 363 if largestCenterOutsetLoop != None: 364 return largestCenterOutsetLoop 365 return CenterOutset(loop, loop)
366
367 -def getLargestInsetLoopFromLoop(loop, radius):
368 'Get the largest inset loop from the loop.' 369 loops = getInsetLoopsFromLoop(loop, radius) 370 return euclidean.getLargestLoop(loops)
371
372 -def getLargestInsetLoopFromLoopRegardless( loop, radius ):
373 'Get the largest inset loop from the loop, even if the radius has to be shrunk and even if there is still no inset loop.' 374 global globalDecreasingRadiusMultipliers 375 for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers: 376 decreasingRadius = radius * decreasingRadiusMultiplier 377 largestInsetLoop = getLargestInsetLoopFromLoop( loop, decreasingRadius ) 378 if len( largestInsetLoop ) > 0: 379 return largestInsetLoop 380 print('This should never happen, there should always be a largestInsetLoop in getLargestInsetLoopFromLoopRegardless in intercircle.') 381 print(loop) 382 return loop
383
384 -def getLoopsFromLoopsDirection( isWiddershins, loops ):
385 'Get the loops going round in a given direction.' 386 directionalLoops = [] 387 for loop in loops: 388 if euclidean.isWiddershins(loop) == isWiddershins: 389 directionalLoops.append(loop) 390 return directionalLoops
391
392 -def getPointsFromLoop(loop, radius, thresholdRatio=0.9):
393 'Get the points from every point on a loop and between points.' 394 radius = abs(radius) 395 points = [] 396 for pointIndex in xrange(len(loop)): 397 pointBegin = loop[pointIndex] 398 pointEnd = loop[(pointIndex + 1) % len(loop)] 399 points.append( pointBegin ) 400 addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio ) 401 return points
402
403 -def getPointsFromLoops(loops, radius, thresholdRatio=0.9):
404 'Get the points from every point on a loop and between points.' 405 points = [] 406 for loop in loops: 407 points += getPointsFromLoop(loop, radius, thresholdRatio) 408 return points
409
410 -def getPointsFromPath(path, radius, thresholdRatio=0.9):
411 'Get the points from every point on a path and between points.' 412 if len(path) < 1: 413 return [] 414 if len(path) < 2: 415 return path 416 radius = abs(radius) 417 points = [] 418 addHalfPath(path, points, radius, thresholdRatio) 419 addHalfPath(path[: : -1], points, radius, thresholdRatio) 420 return points
421
422 -def getSimplifiedInsetFromClockwiseLoop(loop, radius):
423 'Get loop inset from clockwise loop, out from widdershins loop.' 424 inset = [] 425 for pointIndex, begin in enumerate(loop): 426 center = loop[(pointIndex + 1) % len(loop)] 427 end = loop[(pointIndex + 2) % len(loop)] 428 addInsetPointFromClockwiseTriple(begin, center, end, inset, radius) 429 return getWithoutIntersections(euclidean.getSimplifiedLoop(inset, radius))
430
431 -def getWiddershinsByLength(begin, end, length):
432 'Get the widdershins by length.' 433 endMinusBegin = end - begin 434 endMinusBeginLength = abs(endMinusBegin) 435 if endMinusBeginLength <= 0.0: 436 return None 437 endMinusBegin *= length / endMinusBeginLength 438 return complex(-endMinusBegin.imag, endMinusBegin.real)
439
440 -def getWithoutIntersections( loop ):
441 'Get loop without intersections.' 442 lastLoopLength = len( loop ) 443 while lastLoopLength > 3: 444 removeIntersection( loop ) 445 if len( loop ) == lastLoopLength: 446 return loop 447 lastLoopLength = len( loop ) 448 return loop
449
450 -def isLargeSameDirection( inset, loop, radius ):
451 'Determine if the inset is in the same direction as the loop and it is large enough.' 452 if euclidean.isWiddershins(inset) != euclidean.isWiddershins(loop): 453 return False 454 return euclidean.getMaximumSpan(inset) > 2.01 * abs(radius)
455
456 -def isLoopIntersectingLoop( anotherLoop, loop ):
457 'Determine if the a loop is intersecting another loop.' 458 for pointIndex in xrange(len(loop)): 459 pointFirst = loop[pointIndex] 460 pointSecond = loop[(pointIndex + 1) % len(loop)] 461 segment = pointFirst - pointSecond 462 normalizedSegment = euclidean.getNormalized(segment) 463 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) 464 segmentFirstPoint = segmentYMirror * pointFirst 465 segmentSecondPoint = segmentYMirror * pointSecond 466 if euclidean.isLoopIntersectingInsideXSegment( anotherLoop, segmentFirstPoint.real, segmentSecondPoint.real, segmentYMirror, segmentFirstPoint.imag ): 467 return True 468 return False
469
470 -def orbitsAreLarge( loop, temperatureChangeTime ):
471 'Determine if the orbits are large enough.' 472 if len(loop) < 1: 473 print('Zero length loop which was skipped over, this should never happen.') 474 return False 475 return temperatureChangeTime > 1.5
476
477 -def removeIntersection( loop ):
478 'Get loop without the first intersection.' 479 for pointIndex, ahead in enumerate(loop): 480 behind = loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ] 481 behindEnd = loop[ ( pointIndex + len( loop ) - 2 ) % len( loop ) ] 482 behindMidpoint = 0.5 * ( behind + behindEnd ) 483 aheadEnd = loop[ (pointIndex + 1) % len( loop ) ] 484 aheadMidpoint = 0.5 * ( ahead + aheadEnd ) 485 normalizedSegment = behind - behindMidpoint 486 normalizedSegmentLength = abs( normalizedSegment ) 487 if normalizedSegmentLength > 0.0: 488 normalizedSegment /= normalizedSegmentLength 489 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) 490 behindRotated = segmentYMirror * behind 491 behindMidpointRotated = segmentYMirror * behindMidpoint 492 aheadRotated = segmentYMirror * ahead 493 aheadMidpointRotated = segmentYMirror * aheadMidpoint 494 y = behindRotated.imag 495 xIntersection = euclidean.getXIntersectionIfExists( aheadRotated, aheadMidpointRotated, y ) 496 if xIntersection != None: 497 if xIntersection > min( behindMidpointRotated.real, behindRotated.real ) and xIntersection < max( behindMidpointRotated.real, behindRotated.real ): 498 intersectionPoint = normalizedSegment * complex( xIntersection, y ) 499 loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ] = intersectionPoint 500 del loop[pointIndex] 501 return
502 503
504 -class BoundingLoop:
505 'A class to hold a bounding loop composed of a minimum complex, a maximum complex and an outset loop.'
506 - def __eq__(self, other):
507 'Determine whether this bounding loop is identical to other one.' 508 if other == None: 509 return False 510 return self.minimum == other.minimum and self.maximum == other.maximum and self.loop == other.loop
511
512 - def __repr__(self):
513 'Get the string representation of this bounding loop.' 514 return '%s, %s, %s' % ( self.minimum, self.maximum, self.loop )
515
516 - def getFromLoop( self, loop ):
517 'Get the bounding loop from a path.' 518 self.loop = loop 519 self.maximum = euclidean.getMaximumByComplexPath(loop) 520 self.minimum = euclidean.getMinimumByComplexPath(loop) 521 return self
522
523 - def getOutsetBoundingLoop( self, outsetDistance ):
524 'Outset the bounding rectangle and loop by a distance.' 525 outsetBoundingLoop = BoundingLoop() 526 outsetBoundingLoop.maximum = self.maximum + complex( outsetDistance, outsetDistance ) 527 outsetBoundingLoop.minimum = self.minimum - complex( outsetDistance, outsetDistance ) 528 greaterThanOutsetDistance = 1.1 * outsetDistance 529 centers = getCentersFromLoopDirection( True, self.loop, greaterThanOutsetDistance ) 530 outsetBoundingLoop.loop = getSimplifiedInsetFromClockwiseLoop( centers[0], outsetDistance ) 531 return outsetBoundingLoop
532
533 - def isEntirelyInsideAnother( self, anotherBoundingLoop ):
534 'Determine if this bounding loop is entirely inside another bounding loop.' 535 if self.minimum.imag < anotherBoundingLoop.minimum.imag or self.minimum.real < anotherBoundingLoop.minimum.real: 536 return False 537 if self.maximum.imag > anotherBoundingLoop.maximum.imag or self.maximum.real > anotherBoundingLoop.maximum.real: 538 return False 539 for point in self.loop: 540 if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 0: 541 return False 542 return not isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles
543
544 - def isOverlappingAnother( self, anotherBoundingLoop ):
545 'Determine if this bounding loop is intersecting another bounding loop.' 546 if self.isRectangleMissingAnother( anotherBoundingLoop ): 547 return False 548 for point in self.loop: 549 if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 1: 550 return True 551 for point in anotherBoundingLoop.loop: 552 if euclidean.getNumberOfIntersectionsToLeft( self.loop, point ) % 2 == 1: 553 return True 554 return isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles
555
556 - def isOverlappingAnotherInList( self, boundingLoops ):
557 'Determine if this bounding loop is intersecting another bounding loop in a list.' 558 for boundingLoop in boundingLoops: 559 if self.isOverlappingAnother( boundingLoop ): 560 return True 561 return False
562
563 - def isRectangleMissingAnother( self, anotherBoundingLoop ):
564 'Determine if the rectangle of this bounding loop is missing the rectangle of another bounding loop.' 565 if self.maximum.imag < anotherBoundingLoop.minimum.imag or self.maximum.real < anotherBoundingLoop.minimum.real: 566 return True 567 return self.minimum.imag > anotherBoundingLoop.maximum.imag or self.minimum.real > anotherBoundingLoop.maximum.real
568 569
570 -class CenterOutset:
571 'A class to hold a center and an outset.'
572 - def __init__(self, center, outset):
573 'Set the center and outset.' 574 self.center = center 575 self.outset = outset
576
577 - def __repr__(self):
578 'Get the string representation of this CenterOutset.' 579 return '%s\n%s' % (self.center, self.outset)
580 581
582 -class CircleIntersection:
583 'An intersection of two complex circles.'
584 - def __init__( self, circleNodeAhead, index, circleNodeBehind ):
585 self.aheadMinusBehind = 0.5 * ( circleNodeAhead.dividedPoint - circleNodeBehind.dividedPoint ) 586 self.circleNodeAhead = circleNodeAhead 587 self.circleNodeBehind = circleNodeBehind 588 self.index = index 589 self.steppedOn = False 590 demichordWidth = math.sqrt( 1.0 - self.aheadMinusBehind.real * self.aheadMinusBehind.real - self.aheadMinusBehind.imag * self.aheadMinusBehind.imag ) 591 rotatedClockwiseQuarter = complex( self.aheadMinusBehind.imag, - self.aheadMinusBehind.real ) 592 rotatedClockwiseQuarterLength = abs( rotatedClockwiseQuarter ) 593 if rotatedClockwiseQuarterLength == 0: 594 print('this should never happen, rotatedClockwiseQuarter in getDemichord in intercircle is 0') 595 print( circleNodeAhead.dividedPoint ) 596 print( circleNodeBehind.dividedPoint ) 597 self.demichord = 0.0 598 else: 599 self.demichord = rotatedClockwiseQuarter * demichordWidth / rotatedClockwiseQuarterLength 600 self.positionRelativeToBehind = self.aheadMinusBehind + self.demichord
601
602 - def __repr__(self):
603 'Get the string representation of this CircleIntersection.' 604 return '%s, %s, %s, %s' % (self.index, self.getAbsolutePosition(), self.circleNodeBehind, self.circleNodeAhead)
605
606 - def addToList( self, circleIntersectionPath ):
607 'Add this to the circle intersection path, setting stepped on to be true.' 608 self.steppedOn = True 609 circleIntersectionPath.append(self)
610
611 - def getAbsolutePosition(self):
612 'Get the absolute position.' 613 return self.positionRelativeToBehind + self.circleNodeBehind.dividedPoint
614
616 'Get the first circle intersection on the circle node ahead.' 617 circleIntersections = self.circleNodeAhead.circleIntersections 618 circleIntersectionAhead = None 619 largestDot = -912345678.0 620 for circleIntersection in circleIntersections: 621 if not circleIntersection.steppedOn: 622 circleIntersectionRelativeToMidpoint = euclidean.getNormalized(circleIntersection.positionRelativeToBehind + self.aheadMinusBehind) 623 dot = euclidean.getDotProduct(self.demichord, circleIntersectionRelativeToMidpoint) 624 if dot > largestDot: 625 largestDot = dot 626 circleIntersectionAhead = circleIntersection 627 if circleIntersectionAhead == None: 628 print('Warning, circleIntersectionAhead in getCircleIntersectionAhead in intercircle is None for:') 629 print(self.circleNodeAhead.dividedPoint) 630 print('circleIntersectionsAhead') 631 for circleIntersection in circleIntersections: 632 print(circleIntersection.circleNodeAhead.dividedPoint) 633 print('circleIntersectionsBehind') 634 for circleIntersection in self.circleNodeBehind.circleIntersections: 635 print(circleIntersection.circleNodeAhead.dividedPoint) 636 print('This may lead to a loop not being sliced.') 637 print('If this is a problem, you may as well send a bug report, even though I probably can not fix this particular problem.') 638 return circleIntersectionAhead
639
640 - def isWithinCircles(self, pixelTable):
641 'Determine if this circle intersection is within the circle node circles.' 642 absolutePosition = self.getAbsolutePosition() 643 squareValues = euclidean.getSquareValuesFromPoint(pixelTable, absolutePosition) 644 for squareValue in squareValues: 645 if abs(squareValue.dividedPoint - absolutePosition) < 1.0: 646 if squareValue != self.circleNodeAhead and squareValue != self.circleNodeBehind: 647 return True 648 return False
649 650
651 -class CircleNode:
652 'A complex node of complex circle intersections.'
653 - def __init__(self, oneOverRadius, point):
654 self.actualPoint = point 655 self.circleIntersections = [] 656 self.dividedPoint = point * oneOverRadius
657 # self.index = index # when debugging bring back index 658
659 - def __repr__(self):
660 'Get the string representation of this CircleNode.' 661 # return '%s, %s, %s' % (self.index, self.dividedPoint, len(self.circleIntersections)) # when debugging bring back index 662 return '%s, %s' % (self.dividedPoint, len(self.circleIntersections))
663
664 - def getWithinNodes(self, pixelTable):
665 'Get the nodes this circle node is within.' 666 withinNodes = [] 667 squareValues = euclidean.getSquareValuesFromPoint(pixelTable, 0.5 * self.dividedPoint) 668 for squareValue in squareValues: 669 if abs(self.dividedPoint - squareValue.dividedPoint) < 2.0: 670 withinNodes.append(squareValue) 671 return withinNodes
672