Updated build script to create win32/linux/macos versions. Fixed the defaults to they work with PLA. Fixed the temperature plugin default "ON" problem. Removed all profiles except for PLA.
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('This should never happen, 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
|