725 lines
30 KiB
Python
725 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
|