OctoPrint/Cura/cura_sf/fabmetheus_utilities/intercircle.py

726 lines
30 KiB
Python

"""
Intercircle is a collection of utilities for intersecting circles, used to get smooth loops around a collection of points and inset & outset loops.
"""
from __future__ import absolute_import
try:
import psyco
psyco.full()
except:
pass
#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.
import __init__
from fabmetheus_utilities.vector3 import Vector3
from fabmetheus_utilities import euclidean
import math
__author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
__date__ = '$Date: 2008/21/04 $'
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
globalDecreasingRadiusMultipliers = [1.0, 0.55, 0.35, 0.2]
globalIntercircleMultiplier = 1.04 # 1.02 is enough to stop known intersection
def addCircleIntersectionLoop(circleIntersectionLoop, circleIntersections):
'Add a circle intersection loop.'
firstCircleIntersection = circleIntersectionLoop[0]
circleIntersectionAhead = firstCircleIntersection
for circleIntersectionIndex in xrange(len(circleIntersections) + 1):
circleIntersectionAhead = circleIntersectionAhead.getCircleIntersectionAhead()
if circleIntersectionAhead == firstCircleIntersection or circleIntersectionAhead == None:
firstCircleIntersection.steppedOn = True
return
circleIntersectionAhead.addToList(circleIntersectionLoop)
firstCircleIntersection.steppedOn = True
print('Warning, addCircleIntersectionLoop would have gone into an endless loop.')
print('circleIntersectionLoop')
for circleIntersection in circleIntersectionLoop:
print(circleIntersection)
print(circleIntersection.circleNodeAhead)
print(circleIntersection.circleNodeBehind)
print('firstCircleIntersection')
print(firstCircleIntersection)
print('circleIntersections')
for circleIntersection in circleIntersections:
print(circleIntersection)
def addEndCap(begin, end, points, radius):
'Get circular end cap.'
beginMinusEnd = begin - end
beginMinusEndLength = abs(beginMinusEnd)
if beginMinusEndLength <= 0.0:
points.append(begin)
return
beginMinusEnd *= radius / beginMinusEndLength
perpendicular = complex(-beginMinusEnd.imag, beginMinusEnd.real)
numberOfSides = 20 # to end up with close to unit length corners, 5 * 4
numberOfPositiveSides = numberOfSides / 2
totalAngle = 0.0
angle = euclidean.globalTau / float(numberOfSides)
# dotProductMultiplier to compensate for the corner outset in addInsetPointFromClockwiseTriple
dotProductMultiplier = 2.0 - 1.0 / math.cos(0.5 * angle)
for sideIndex in xrange(numberOfPositiveSides + 1):
circumferentialPoint = math.sin(totalAngle) * beginMinusEnd + math.cos(totalAngle) * perpendicular
points.append(begin + circumferentialPoint * dotProductMultiplier)
totalAngle += angle
def addHalfPath(path, points, radius, thresholdRatio=0.9):
'Add the points from every point on a half path and between points.'
lessThanRadius = 0.75 * radius
for pointIndex in xrange(len(path) - 1):
begin = path[pointIndex]
center = path[pointIndex + 1]
centerBegin = getWiddershinsByLength(begin, center, radius)
if centerBegin != None:
addPointsFromSegment(begin + centerBegin, center + centerBegin, points, lessThanRadius, thresholdRatio)
endIndex = pointIndex + 2
if endIndex < len(path):
end = path[endIndex]
centerEnd = getWiddershinsByLength(center, end, radius)
if centerBegin != None and centerEnd != None:
centerPerpendicular = 0.5 * (centerBegin + centerEnd)
points.append(center + centerPerpendicular)
if euclidean.getCrossProduct(centerBegin, centerEnd) < 0.0:
points.append(center + centerBegin)
points.append(center + centerEnd)
else:
points.append(center)
addEndCap(path[0], path[1], points, radius)
def addInsetPointFromClockwiseTriple(begin, center, end, loop, radius):
'Get inset point with possible intersection from clockwise triple, out from widdershins loop.'
centerMinusBegin = center - begin
centerMinusBeginLength = abs(centerMinusBegin)
centerMinusBeginClockwise = None
if centerMinusBeginLength > 0.0:
centerMinusBeginClockwise = complex(centerMinusBegin.imag, -centerMinusBegin.real) / centerMinusBeginLength
endMinusCenter = end - center
endMinusCenterLength = abs(endMinusCenter)
endMinusCenterClockwise = None
if endMinusCenterLength > 0.0:
endMinusCenterClockwise = complex(endMinusCenter.imag, -endMinusCenter.real) / endMinusCenterLength
if centerMinusBeginClockwise == None and endMinusCenterClockwise == None:
return
if centerMinusBeginClockwise == None:
loop.append(center + endMinusCenterClockwise * radius)
return
if endMinusCenterClockwise == None:
loop.append(center + centerMinusBeginClockwise * radius)
return
centerClockwise = 0.5 * (centerMinusBeginClockwise + endMinusCenterClockwise)
dotProduct = euclidean.getDotProduct(centerMinusBeginClockwise, centerClockwise)
loop.append(center + centerClockwise * radius / max(0.4, abs(dotProduct))) # 0.4 to avoid pointy corners
def addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ):
'Add orbits with the extruder off.'
timeInOrbit = 0.0
while timeInOrbit < temperatureChangeTime:
for point in loop:
distanceFeedRate.addGcodeMovementZWithFeedRate( 60.0 * orbitalFeedRatePerSecond, point, z )
timeInOrbit += euclidean.getLoopLength(loop) / orbitalFeedRatePerSecond
def addOrbitsIfLarge( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ):
'Add orbits with the extruder off if the orbits are large enough.'
if orbitsAreLarge( loop, temperatureChangeTime ):
addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z )
def addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio=0.9 ):
'Add point complexes between the endpoints of a segment.'
if radius <= 0.0:
print('This should never happen, radius should never be zero or less in addPointsFromSegment in intercircle.')
thresholdRadius = radius * thresholdRatio # a higher number would be faster but would leave bigger dangling loops and extra dangling loops.
thresholdDiameter = thresholdRadius + thresholdRadius
segment = pointEnd - pointBegin
segmentLength = abs(segment)
extraCircles = int( math.floor( segmentLength / thresholdDiameter ) )
if extraCircles < 1:
return
if segmentLength == 0.0:
print('Warning, segmentLength = 0.0 in intercircle.')
print('pointBegin')
print(pointBegin)
print(pointEnd)
return
if extraCircles < 2:
lengthIncrement = segmentLength / ( float(extraCircles) + 1.0 )
segment *= lengthIncrement / segmentLength
pointBegin += segment
else:
pointBegin += segment * thresholdDiameter / segmentLength
remainingLength = segmentLength - thresholdDiameter - thresholdDiameter
lengthIncrement = remainingLength / ( float(extraCircles) - 1.0 )
segment *= lengthIncrement / segmentLength
for circleIndex in xrange(extraCircles):
points.append( pointBegin )
pointBegin += segment
def directLoop(isWiddershins, loop):
'Direct the loop.'
if euclidean.isWiddershins(loop) != isWiddershins:
loop.reverse()
def directLoopLists(isWiddershins, loopLists):
'Direct the loop lists.'
for loopList in loopLists:
directLoops(isWiddershins, loopList)
def directLoops(isWiddershins, loops):
'Direct the loops.'
for loop in loops:
directLoop(isWiddershins, loop)
def getAroundsFromLoop(loop, radius, thresholdRatio=0.9):
'Get the arounds from the loop.'
return getAroundsFromPoints(getPointsFromLoop(loop, radius, thresholdRatio), radius)
def getAroundsFromLoops( loops, radius, thresholdRatio=0.9 ):
'Get the arounds from the loops.'
return getAroundsFromPoints(getPointsFromLoops(loops, radius, thresholdRatio), radius)
def getAroundsFromPath(path, radius, thresholdRatio=0.9):
'Get the arounds from the path.'
radius = abs(radius)
points = getPointsFromPath(path, radius, thresholdRatio)
return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9)
def getAroundsFromPathPoints(points, radius, thresholdRatio=0.9):
'Get the arounds from the path.'
centers = getCentersFromPoints(points, 0.8 * radius)
arounds = []
for center in centers:
if euclidean.isWiddershins(center):
arounds.append(euclidean.getSimplifiedPath(center, radius))
return arounds
def getAroundsFromPaths(paths, radius, thresholdRatio=0.9):
'Get the arounds from the path.'
radius = abs(radius)
points = []
for path in paths:
points += getPointsFromPath(path, radius, thresholdRatio)
return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9)
def getAroundsFromPoints( points, radius ):
'Get the arounds from the points.'
arounds = []
radius = abs(radius)
centers = getCentersFromPoints(points, globalIntercircleMultiplier * radius)
for center in centers:
inset = getSimplifiedInsetFromClockwiseLoop(center, radius)
if isLargeSameDirection(inset, center, radius):
arounds.append(inset)
return arounds
def getCentersFromCircleNodes( circleNodes, radius ):
'Get the complex centers of the circle intersection loops from circle nodes.'
if len( circleNodes ) < 2:
return []
circleIntersections = getCircleIntersectionsFromCircleNodes( circleNodes )
circleIntersectionLoops = getCircleIntersectionLoops( circleIntersections )
return getCentersFromIntersectionLoops( circleIntersectionLoops, radius )
def getCentersFromIntersectionLoop(circleIntersectionLoop, radius):
'Get the centers from the intersection loop.'
loop = []
for circleIntersection in circleIntersectionLoop:
loop.append(circleIntersection.circleNodeAhead.actualPoint)
return loop
def getCentersFromIntersectionLoops( circleIntersectionLoops, radius ):
'Get the centers from the intersection loops.'
centers = []
for circleIntersectionLoop in circleIntersectionLoops:
centers.append( getCentersFromIntersectionLoop( circleIntersectionLoop, radius ) )
return centers
def getCentersFromLoop( loop, radius ):
'Get the centers of the loop.'
circleNodes = getCircleNodesFromLoop( loop, radius )
return getCentersFromCircleNodes( circleNodes, radius )
def getCentersFromLoopDirection( isWiddershins, loop, radius ):
'Get the centers of the loop which go around in the given direction.'
centers = getCentersFromLoop( loop, radius )
return getLoopsFromLoopsDirection( isWiddershins, centers )
def getCentersFromPoints(points, radius):
'Get the centers from the points.'
circleNodes = getCircleNodesFromPoints(points, abs(radius))
return getCentersFromCircleNodes(circleNodes, abs(radius))
def getCircleIntersectionLoops( circleIntersections ):
'Get all the loops going through the circle intersections.'
circleIntersectionLoops = []
for circleIntersection in circleIntersections:
if not circleIntersection.steppedOn:
circleIntersectionLoop = [ circleIntersection ]
circleIntersectionLoops.append( circleIntersectionLoop )
addCircleIntersectionLoop( circleIntersectionLoop, circleIntersections )
return circleIntersectionLoops
def getCircleIntersectionsFromCircleNodes(circleNodes):
'Get all the circle intersections which exist between all the circle nodes.'
if len( circleNodes ) < 1:
return []
circleIntersections = []
index = 0
pixelTable = {}
for circleNode in circleNodes:
euclidean.addElementToPixelListFromPoint(circleNode, pixelTable, circleNode.dividedPoint)
accumulatedCircleNodeTable = {}
for circleNodeIndex in xrange(len(circleNodes)):
circleNodeBehind = circleNodes[circleNodeIndex]
circleNodeIndexMinusOne = circleNodeIndex - 1
if circleNodeIndexMinusOne >= 0:
circleNodeAdditional = circleNodes[circleNodeIndexMinusOne]
euclidean.addElementToPixelListFromPoint(circleNodeAdditional, accumulatedCircleNodeTable, 0.5 * circleNodeAdditional.dividedPoint)
withinNodes = circleNodeBehind.getWithinNodes(accumulatedCircleNodeTable)
for circleNodeAhead in withinNodes:
circleIntersectionForward = CircleIntersection(circleNodeAhead, index, circleNodeBehind)
if not circleIntersectionForward.isWithinCircles(pixelTable):
circleIntersections.append(circleIntersectionForward)
circleNodeBehind.circleIntersections.append(circleIntersectionForward)
index += 1
circleIntersectionBackward = CircleIntersection(circleNodeBehind, index, circleNodeAhead)
if not circleIntersectionBackward.isWithinCircles(pixelTable):
circleIntersections.append(circleIntersectionBackward)
circleNodeAhead.circleIntersections.append(circleIntersectionBackward)
index += 1
return circleIntersections
def getCircleNodesFromLoop(loop, radius, thresholdRatio=0.9):
'Get the circle nodes from every point on a loop and between points.'
radius = abs(radius)
points = getPointsFromLoop( loop, radius, thresholdRatio )
return getCircleNodesFromPoints( points, radius )
def getCircleNodesFromPoints(points, radius):
'Get the circle nodes from a path.'
if radius == 0.0:
print('Warning, radius is 0 in getCircleNodesFromPoints in intercircle.')
print(points)
return []
circleNodes = []
oneOverRadius = 1.000001 / radius # to avoid problem of accidentally integral radius
points = euclidean.getAwayPoints(points, radius)
for point in points:
circleNodes.append(CircleNode(oneOverRadius, point))
return circleNodes
def getInsetLoopsFromLoop(loop, radius, thresholdRatio=0.9):
'Get the inset loops, which might overlap.'
if radius == 0.0:
return [loop]
isInset = radius > 0
insetLoops = []
isLoopWiddershins = euclidean.isWiddershins(loop)
arounds = getAroundsFromLoop(loop, radius, thresholdRatio)
for around in arounds:
leftPoint = euclidean.getLeftPoint(around)
shouldBeWithin = (isInset == isLoopWiddershins)
if euclidean.isPointInsideLoop(loop, leftPoint) == shouldBeWithin:
if isLoopWiddershins != euclidean.isWiddershins(around):
around.reverse()
insetLoops.append(around)
return insetLoops
def getInsetLoopsFromLoops(loops, radius):
'Get the inset loops, which might overlap.'
insetLoops = []
for loop in loops:
insetLoops += getInsetLoopsFromLoop(loop, radius)
return insetLoops
def getInsetLoopsFromVector3Loop(loop, radius, thresholdRatio=0.9):
'Get the inset loops from vector3 loop, which might overlap.'
if len(loop) < 2:
return [loop]
loopComplex = euclidean.getComplexPath(loop)
loopComplexes = getInsetLoopsFromLoop(loopComplex, radius)
return euclidean.getVector3Paths(loopComplexes, loop[0].z)
def getInsetSeparateLoopsFromLoops(loops, radius, thresholdRatio=0.9):
'Get the separate inset loops.'
if radius == 0.0:
return loops
isInset = radius > 0
insetSeparateLoops = []
arounds = getAroundsFromLoops(loops, abs(radius), thresholdRatio)
for around in arounds:
if isInset == euclidean.getIsInFilledRegion(loops, around[0]):
if isInset:
around.reverse()
insetSeparateLoops.append(around)
return insetSeparateLoops
def getInsetSeparateLoopsFromAroundLoops(loops, radius, radiusAround, thresholdRatio=0.9):
'Get the separate inset loops.'
if radius == 0.0:
return loops
isInset = radius > 0
insetSeparateLoops = []
radius = abs(radius)
radiusAround = max(abs(radiusAround), radius)
points = getPointsFromLoops(loops, radiusAround, thresholdRatio)
centers = getCentersFromPoints(points, globalIntercircleMultiplier * radiusAround)
for center in centers:
inset = getSimplifiedInsetFromClockwiseLoop(center, radius)
if isLargeSameDirection(inset, center, radius):
if isInset == euclidean.getIsInFilledRegion(loops, inset[0]):
if isInset:
inset.reverse()
insetSeparateLoops.append(inset)
return insetSeparateLoops
def getIsLarge(loop, radius):
'Determine if the loop is large enough.'
return euclidean.getMaximumSpan(loop) > 2.01 * abs(radius)
def getLargestCenterOutsetLoopFromLoop(loop, radius, thresholdRatio=0.9):
'Get the largest circle outset loop from the loop.'
if radius == 0.0:
return loop
radius = abs(radius)
points = getPointsFromLoop(loop, radius, thresholdRatio)
centers = getCentersFromPoints(points, globalIntercircleMultiplier * radius)
largestCenterOutset = None
largestOutsetArea = -987654321.0
for center in centers:
outset = getSimplifiedInsetFromClockwiseLoop(center, radius)
if isLargeSameDirection(outset, center, radius):
if euclidean.isPathInsideLoop(loop, outset) != euclidean.isWiddershins(loop):
centerOutset = CenterOutset(center, outset)
outsetArea = abs(euclidean.getAreaLoop(outset))
if outsetArea > largestOutsetArea:
largestOutsetArea = outsetArea
largestCenterOutset = centerOutset
if largestCenterOutset == None:
return None
largestCenterOutset.center = euclidean.getSimplifiedLoop(largestCenterOutset.center, radius)
return largestCenterOutset
def getLargestCenterOutsetLoopFromLoopRegardless(loop, radius):
'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.'
global globalDecreasingRadiusMultipliers
for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers:
decreasingRadius = radius * decreasingRadiusMultiplier
largestCenterOutsetLoop = getLargestCenterOutsetLoopFromLoop(loop, decreasingRadius)
if largestCenterOutsetLoop != None:
return largestCenterOutsetLoop
return CenterOutset(loop, loop)
def getLargestInsetLoopFromLoop(loop, radius):
'Get the largest inset loop from the loop.'
loops = getInsetLoopsFromLoop(loop, radius)
return euclidean.getLargestLoop(loops)
def getLargestInsetLoopFromLoopRegardless( loop, radius ):
'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.'
global globalDecreasingRadiusMultipliers
for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers:
decreasingRadius = radius * decreasingRadiusMultiplier
largestInsetLoop = getLargestInsetLoopFromLoop( loop, decreasingRadius )
if len( largestInsetLoop ) > 0:
return largestInsetLoop
print('Warning, there should always be a largestInsetLoop in getLargestInsetLoopFromLoopRegardless in intercircle.')
print(loop)
return loop
def getLoopsFromLoopsDirection( isWiddershins, loops ):
'Get the loops going round in a given direction.'
directionalLoops = []
for loop in loops:
if euclidean.isWiddershins(loop) == isWiddershins:
directionalLoops.append(loop)
return directionalLoops
def getPointsFromLoop(loop, radius, thresholdRatio=0.9):
'Get the points from every point on a loop and between points.'
if radius == 0.0:
print('Warning, radius is 0 in getPointsFromLoop in intercircle.')
print(loop)
return loop
radius = abs(radius)
points = []
for pointIndex in xrange(len(loop)):
pointBegin = loop[pointIndex]
pointEnd = loop[(pointIndex + 1) % len(loop)]
points.append( pointBegin )
addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio )
return points
def getPointsFromLoops(loops, radius, thresholdRatio=0.9):
'Get the points from every point on a loop and between points.'
points = []
for loop in loops:
points += getPointsFromLoop(loop, radius, thresholdRatio)
return points
def getPointsFromPath(path, radius, thresholdRatio=0.9):
'Get the points from every point on a path and between points.'
if len(path) < 1:
return []
if len(path) < 2:
return path
radius = abs(radius)
points = []
addHalfPath(path, points, radius, thresholdRatio)
addHalfPath(path[: : -1], points, radius, thresholdRatio)
return points
def getSimplifiedInsetFromClockwiseLoop(loop, radius):
'Get loop inset from clockwise loop, out from widdershins loop.'
inset = []
for pointIndex, begin in enumerate(loop):
center = loop[(pointIndex + 1) % len(loop)]
end = loop[(pointIndex + 2) % len(loop)]
addInsetPointFromClockwiseTriple(begin, center, end, inset, radius)
return getWithoutIntersections(euclidean.getSimplifiedLoop(inset, radius))
def getWiddershinsByLength(begin, end, length):
'Get the widdershins by length.'
endMinusBegin = end - begin
endMinusBeginLength = abs(endMinusBegin)
if endMinusBeginLength <= 0.0:
return None
endMinusBegin *= length / endMinusBeginLength
return complex(-endMinusBegin.imag, endMinusBegin.real)
def getWithoutIntersections( loop ):
'Get loop without intersections.'
lastLoopLength = len( loop )
while lastLoopLength > 3:
removeIntersection( loop )
if len( loop ) == lastLoopLength:
return loop
lastLoopLength = len( loop )
return loop
def isLargeSameDirection(inset, loop, radius):
'Determine if the inset is in the same direction as the loop and it is large enough.'
if euclidean.isWiddershins(inset) != euclidean.isWiddershins(loop):
return False
return getIsLarge(inset, radius) and len(inset) > 2
def isLoopIntersectingLoop( anotherLoop, loop ):
'Determine if the a loop is intersecting another loop.'
for pointIndex in xrange(len(loop)):
pointFirst = loop[pointIndex]
pointSecond = loop[(pointIndex + 1) % len(loop)]
segment = pointFirst - pointSecond
normalizedSegment = euclidean.getNormalized(segment)
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
segmentFirstPoint = segmentYMirror * pointFirst
segmentSecondPoint = segmentYMirror * pointSecond
if euclidean.isLoopIntersectingInsideXSegment( anotherLoop, segmentFirstPoint.real, segmentSecondPoint.real, segmentYMirror, segmentFirstPoint.imag ):
return True
return False
def orbitsAreLarge( loop, temperatureChangeTime ):
'Determine if the orbits are large enough.'
if len(loop) < 1:
print('Zero length loop which was skipped over, this should never happen.')
return False
return temperatureChangeTime > 1.5
def removeIntersection( loop ):
'Get loop without the first intersection.'
for pointIndex, ahead in enumerate(loop):
behind = loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ]
behindEnd = loop[ ( pointIndex + len( loop ) - 2 ) % len( loop ) ]
behindMidpoint = 0.5 * ( behind + behindEnd )
aheadEnd = loop[ (pointIndex + 1) % len( loop ) ]
aheadMidpoint = 0.5 * ( ahead + aheadEnd )
normalizedSegment = behind - behindMidpoint
normalizedSegmentLength = abs( normalizedSegment )
if normalizedSegmentLength > 0.0:
normalizedSegment /= normalizedSegmentLength
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
behindRotated = segmentYMirror * behind
behindMidpointRotated = segmentYMirror * behindMidpoint
aheadRotated = segmentYMirror * ahead
aheadMidpointRotated = segmentYMirror * aheadMidpoint
y = behindRotated.imag
xIntersection = euclidean.getXIntersectionIfExists( aheadRotated, aheadMidpointRotated, y )
if xIntersection != None:
if xIntersection > min( behindMidpointRotated.real, behindRotated.real ) and xIntersection < max( behindMidpointRotated.real, behindRotated.real ):
intersectionPoint = normalizedSegment * complex( xIntersection, y )
loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ] = intersectionPoint
del loop[pointIndex]
return
class BoundingLoop:
'A class to hold a bounding loop composed of a minimum complex, a maximum complex and an outset loop.'
def __eq__(self, other):
'Determine whether this bounding loop is identical to other one.'
if other == None:
return False
return self.minimum == other.minimum and self.maximum == other.maximum and self.loop == other.loop
def __repr__(self):
'Get the string representation of this bounding loop.'
return '%s, %s, %s' % ( self.minimum, self.maximum, self.loop )
def getFromLoop( self, loop ):
'Get the bounding loop from a path.'
self.loop = loop
self.maximum = euclidean.getMaximumByComplexPath(loop)
self.minimum = euclidean.getMinimumByComplexPath(loop)
return self
def getOutsetBoundingLoop( self, outsetDistance ):
'Outset the bounding rectangle and loop by a distance.'
outsetBoundingLoop = BoundingLoop()
outsetBoundingLoop.maximum = self.maximum + complex( outsetDistance, outsetDistance )
outsetBoundingLoop.minimum = self.minimum - complex( outsetDistance, outsetDistance )
greaterThanOutsetDistance = 1.1 * outsetDistance
centers = getCentersFromLoopDirection( True, self.loop, greaterThanOutsetDistance )
outsetBoundingLoop.loop = getSimplifiedInsetFromClockwiseLoop( centers[0], outsetDistance )
return outsetBoundingLoop
def isEntirelyInsideAnother( self, anotherBoundingLoop ):
'Determine if this bounding loop is entirely inside another bounding loop.'
if self.minimum.imag < anotherBoundingLoop.minimum.imag or self.minimum.real < anotherBoundingLoop.minimum.real:
return False
if self.maximum.imag > anotherBoundingLoop.maximum.imag or self.maximum.real > anotherBoundingLoop.maximum.real:
return False
for point in self.loop:
if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 0:
return False
return not isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles
def isOverlappingAnother( self, anotherBoundingLoop ):
'Determine if this bounding loop is intersecting another bounding loop.'
if self.isRectangleMissingAnother( anotherBoundingLoop ):
return False
for point in self.loop:
if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 1:
return True
for point in anotherBoundingLoop.loop:
if euclidean.getNumberOfIntersectionsToLeft( self.loop, point ) % 2 == 1:
return True
return isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles
def isOverlappingAnotherInList( self, boundingLoops ):
'Determine if this bounding loop is intersecting another bounding loop in a list.'
for boundingLoop in boundingLoops:
if self.isOverlappingAnother( boundingLoop ):
return True
return False
def isRectangleMissingAnother( self, anotherBoundingLoop ):
'Determine if the rectangle of this bounding loop is missing the rectangle of another bounding loop.'
if self.maximum.imag < anotherBoundingLoop.minimum.imag or self.maximum.real < anotherBoundingLoop.minimum.real:
return True
return self.minimum.imag > anotherBoundingLoop.maximum.imag or self.minimum.real > anotherBoundingLoop.maximum.real
class CenterOutset:
'A class to hold a center and an outset.'
def __init__(self, center, outset):
'Set the center and outset.'
self.center = center
self.outset = outset
def __repr__(self):
'Get the string representation of this CenterOutset.'
return '%s\n%s' % (self.center, self.outset)
class CircleIntersection:
'An intersection of two complex circles.'
def __init__( self, circleNodeAhead, index, circleNodeBehind ):
self.aheadMinusBehind = 0.5 * ( circleNodeAhead.dividedPoint - circleNodeBehind.dividedPoint )
self.circleNodeAhead = circleNodeAhead
self.circleNodeBehind = circleNodeBehind
self.index = index
self.steppedOn = False
demichordWidth = math.sqrt( 1.0 - self.aheadMinusBehind.real * self.aheadMinusBehind.real - self.aheadMinusBehind.imag * self.aheadMinusBehind.imag )
rotatedClockwiseQuarter = complex( self.aheadMinusBehind.imag, - self.aheadMinusBehind.real )
rotatedClockwiseQuarterLength = abs( rotatedClockwiseQuarter )
if rotatedClockwiseQuarterLength == 0:
print('Warning, rotatedClockwiseQuarter in getDemichord in intercircle is 0')
print(circleNodeAhead.dividedPoint)
print(circleNodeBehind.dividedPoint)
self.demichord = 0.0
else:
self.demichord = rotatedClockwiseQuarter * demichordWidth / rotatedClockwiseQuarterLength
self.positionRelativeToBehind = self.aheadMinusBehind + self.demichord
def __repr__(self):
'Get the string representation of this CircleIntersection.'
return '%s, %s, %s, %s' % (self.index, self.getAbsolutePosition(), self.circleNodeBehind, self.circleNodeAhead)
def addToList( self, circleIntersectionPath ):
'Add this to the circle intersection path, setting stepped on to be true.'
self.steppedOn = True
circleIntersectionPath.append(self)
def getAbsolutePosition(self):
'Get the absolute position.'
return self.positionRelativeToBehind + self.circleNodeBehind.dividedPoint
def getCircleIntersectionAhead(self):
'Get the first circle intersection on the circle node ahead.'
circleIntersections = self.circleNodeAhead.circleIntersections
circleIntersectionAhead = None
largestDot = -912345678.0
for circleIntersection in circleIntersections:
if not circleIntersection.steppedOn:
circleIntersectionRelativeToMidpoint = euclidean.getNormalized(circleIntersection.positionRelativeToBehind + self.aheadMinusBehind)
dot = euclidean.getDotProduct(self.demichord, circleIntersectionRelativeToMidpoint)
if dot > largestDot:
largestDot = dot
circleIntersectionAhead = circleIntersection
if circleIntersectionAhead == None:
print('Warning, circleIntersectionAhead in getCircleIntersectionAhead in intercircle is None for:')
print(self.circleNodeAhead.dividedPoint)
print('circleIntersectionsAhead')
for circleIntersection in circleIntersections:
print(circleIntersection.circleNodeAhead.dividedPoint)
print('circleIntersectionsBehind')
for circleIntersection in self.circleNodeBehind.circleIntersections:
print(circleIntersection.circleNodeAhead.dividedPoint)
print('This may lead to a loop not being sliced.')
print('If this is a problem, you may as well send a bug report, even though I probably can not fix this particular problem.')
return circleIntersectionAhead
def isWithinCircles(self, pixelTable):
'Determine if this circle intersection is within the circle node circles.'
absolutePosition = self.getAbsolutePosition()
squareValues = euclidean.getSquareValuesFromPoint(pixelTable, absolutePosition)
for squareValue in squareValues:
if abs(squareValue.dividedPoint - absolutePosition) < 1.0:
if squareValue != self.circleNodeAhead and squareValue != self.circleNodeBehind:
return True
return False
class CircleNode:
'A complex node of complex circle intersections.'
def __init__(self, oneOverRadius, point):
self.actualPoint = point
self.circleIntersections = []
self.dividedPoint = point * oneOverRadius
# self.index = index # when debugging bring back index
def __repr__(self):
'Get the string representation of this CircleNode.'
# return '%s, %s, %s' % (self.index, self.dividedPoint, len(self.circleIntersections)) # when debugging bring back index
return '%s, %s' % (self.dividedPoint, len(self.circleIntersections))
def getWithinNodes(self, pixelTable):
'Get the nodes this circle node is within.'
withinNodes = []
squareValues = euclidean.getSquareValuesFromPoint(pixelTable, 0.5 * self.dividedPoint)
for squareValue in squareValues:
if abs(self.dividedPoint - squareValue.dividedPoint) < 2.0:
withinNodes.append(squareValue)
return withinNodes