OctoPrint/Cura/cura_sf/fabmetheus_utilities/euclidean.py

2504 lines
94 KiB
Python

"""
Euclidean is a collection of python utilities for complex numbers, paths, polygons & Vector3s.
To use euclidean, install python 2.x on your machine, which is avaliable from http://www.python.org/download/
Then in the folder which euclidean is in, type 'python' in a shell to run the python interpreter. Finally type 'import euclidean' to import these utilities and 'from vector3 import Vector3' to import the Vector3 class.
Below are examples of euclidean use.
>>> from euclidean import *
>>> origin=complex()
>>> right=complex(1.0,0.0)
>>> back=complex(0.0,1.0)
>>> getMaximum(right,back)
1.0, 1.0
>>> polygon=[origin, right, back]
>>> getLoopLength(polygon)
3.4142135623730949
>>> getAreaLoop(polygon)
0.5
"""
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 xml_simple_writer
import sys
import math
import random
if sys.version_info[0] < 3:
import cStringIO
else:
import io as cStringIO
__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'
globalGoldenAngle = 3.8832220774509332 # (math.sqrt(5.0) - 1.0) * math.pi
globalGoldenRatio = 1.6180339887498948482045868 # math.sqrt(1.25) + 0.5
globalTau = math.pi + math.pi # http://tauday.com/
def addElementToListDictionary(element, key, listDictionary):
'Add an element to the list table.'
if key in listDictionary:
listDictionary[key].append(element)
else:
listDictionary[key] = [element]
def addElementToListDictionaryIfNotThere(element, key, listDictionary):
'Add the value to the lists.'
if key in listDictionary:
elements = listDictionary[key]
if element not in elements:
elements.append(element)
else:
listDictionary[key] = [element]
def addElementToPixelList( element, pixelDictionary, x, y ):
'Add an element to the pixel list.'
addElementToListDictionary( element, (x, y), pixelDictionary )
def addElementToPixelListFromPoint( element, pixelDictionary, point ):
'Add an element to the pixel list.'
addElementToPixelList( element, pixelDictionary, int( round( point.real ) ), int( round( point.imag ) ) )
def addHorizontallyBoundedPoint(begin, center, end, horizontalBegin, horizontalEnd, path):
'Add point if it is within the horizontal bounds.'
if center.real >= horizontalEnd and center.real <= horizontalBegin:
path.append(center)
return
if end != None:
if center.real > horizontalBegin and end.real <= horizontalBegin:
centerMinusEnd = center - end
along = (center.real - horizontalBegin) / centerMinusEnd.real
path.append(center - along * centerMinusEnd)
return
if begin != None:
if center.real < horizontalEnd and begin.real >= horizontalEnd:
centerMinusBegin = center - begin
along = (center.real - horizontalEnd) / centerMinusBegin.real
path.append(center - along * centerMinusBegin)
def addListToListTable( elementList, key, listDictionary ):
'Add a list to the list table.'
if key in listDictionary:
listDictionary[key] += elementList
else:
listDictionary[key] = elementList
def addLoopToPixelTable( loop, pixelDictionary, width ):
'Add loop to the pixel table.'
for pointIndex in xrange(len(loop)):
pointBegin = loop[pointIndex]
pointEnd = loop[(pointIndex + 1) % len(loop)]
addValueSegmentToPixelTable( pointBegin, pointEnd, pixelDictionary, None, width )
def addNestedRingBeginning(distanceFeedRate, loop, z):
'Add nested ring beginning to gcode output.'
distanceFeedRate.addLine('(<nestedRing>)')
distanceFeedRate.addLine('(<boundaryPerimeter>)')
for point in loop:
pointVector3 = Vector3(point.real, point.imag, z)
distanceFeedRate.addLine(distanceFeedRate.getBoundaryLine(pointVector3))
def addPathToPixelTable( path, pixelDictionary, value, width ):
'Add path to the pixel table.'
for pointIndex in xrange( len(path) - 1 ):
pointBegin = path[pointIndex]
pointEnd = path[pointIndex + 1]
addValueSegmentToPixelTable( pointBegin, pointEnd, pixelDictionary, value, width )
def addPixelTableToPixelTable( fromPixelTable, intoPixelTable ):
'Add from pixel table to the into pixel table.'
for fromPixelTableKey in fromPixelTable.keys():
intoPixelTable[ fromPixelTableKey ] = fromPixelTable[ fromPixelTableKey ]
def addPixelToPixelTableWithSteepness( isSteep, pixelDictionary, value, x, y ):
'Add pixels to the pixel table with steepness.'
if isSteep:
pixelDictionary[(y, x)] = value
else:
pixelDictionary[(x, y)] = value
def addPointToPath( path, pixelDictionary, point, value, width ):
'Add a point to a path and the pixel table.'
path.append(point)
if len(path) < 2:
return
begin = path[-2]
addValueSegmentToPixelTable( begin, point, pixelDictionary, value, width )
def addSegmentToPixelTable( beginComplex, endComplex, pixelDictionary, shortenDistanceBegin, shortenDistanceEnd, width ):
'Add line segment to the pixel table.'
if abs( beginComplex - endComplex ) <= 0.0:
return
beginComplex /= width
endComplex /= width
if shortenDistanceBegin > 0.0:
endMinusBeginComplex = endComplex - beginComplex
endMinusBeginComplexLength = abs( endMinusBeginComplex )
if endMinusBeginComplexLength < shortenDistanceBegin:
return
beginComplex = beginComplex + endMinusBeginComplex * shortenDistanceBegin / endMinusBeginComplexLength
if shortenDistanceEnd > 0.0:
beginMinusEndComplex = beginComplex - endComplex
beginMinusEndComplexLength = abs( beginMinusEndComplex )
if beginMinusEndComplexLength < 0.0:
return
endComplex = endComplex + beginMinusEndComplex * shortenDistanceEnd / beginMinusEndComplexLength
deltaX = endComplex.real - beginComplex.real
deltaY = endComplex.imag - beginComplex.imag
isSteep = abs( deltaY ) > abs( deltaX )
if isSteep:
beginComplex = complex( beginComplex.imag, beginComplex.real )
endComplex = complex( endComplex.imag, endComplex.real )
if beginComplex.real > endComplex.real:
endComplex, beginComplex = beginComplex, endComplex
deltaX = endComplex.real - beginComplex.real
deltaY = endComplex.imag - beginComplex.imag
if deltaX > 0.0:
gradient = deltaY / deltaX
else:
gradient = 0.0
print('Warning, deltaX in addSegmentToPixelTable in euclidean is 0.')
print(beginComplex)
print(endComplex)
print(shortenDistanceBegin)
print(shortenDistanceEnd)
print(width)
xBegin = int(round(beginComplex.real))
xEnd = int(round(endComplex.real))
yIntersection = beginComplex.imag - beginComplex.real * gradient
if isSteep:
pixelDictionary[( int( round( beginComplex.imag ) ), xBegin)] = None
pixelDictionary[( int( round( endComplex.imag ) ), xEnd )] = None
for x in xrange( xBegin + 1, xEnd ):
y = int( math.floor( yIntersection + x * gradient ) )
pixelDictionary[(y, x)] = None
pixelDictionary[(y + 1, x)] = None
else:
pixelDictionary[(xBegin, int( round( beginComplex.imag ) ) )] = None
pixelDictionary[(xEnd, int( round( endComplex.imag ) ) )] = None
for x in xrange( xBegin + 1, xEnd ):
y = int( math.floor( yIntersection + x * gradient ) )
pixelDictionary[(x, y)] = None
pixelDictionary[(x, y + 1)] = None
def addSquareTwoToPixelDictionary(pixelDictionary, point, value, width):
'Add square with two pixels around the center to pixel dictionary.'
point /= width
x = int(round(point.real))
y = int(round(point.imag))
for xStep in xrange(x - 2, x + 3):
for yStep in xrange(y - 2, y + 3):
pixelDictionary[(xStep, yStep)] = value
def addToThreadsFromLoop(extrusionHalfWidth, gcodeType, loop, oldOrderedLocation, skein):
'Add to threads from the last location from loop.'
loop = getLoopStartingClosest(extrusionHalfWidth, oldOrderedLocation.dropAxis(), loop)
oldOrderedLocation.x = loop[0].real
oldOrderedLocation.y = loop[0].imag
gcodeTypeStart = gcodeType
if isWiddershins(loop):
skein.distanceFeedRate.addLine('(<%s> outer )' % gcodeType)
else:
skein.distanceFeedRate.addLine('(<%s> inner )' % gcodeType)
skein.addGcodeFromThreadZ(loop + [loop[0]], oldOrderedLocation.z)
skein.distanceFeedRate.addLine('(</%s>)' % gcodeType)
def addToThreadsRemove(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence):
'Add to threads from the last location from nested rings.'
while len(nestedRings) > 0:
getTransferClosestNestedRing(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence)
def addValueSegmentToPixelTable( beginComplex, endComplex, pixelDictionary, value, width ):
'Add line segment to the pixel table.'
if abs( beginComplex - endComplex ) <= 0.0:
return
beginComplex /= width
endComplex /= width
deltaX = endComplex.real - beginComplex.real
deltaY = endComplex.imag - beginComplex.imag
isSteep = abs( deltaY ) > abs( deltaX )
if isSteep:
beginComplex = complex( beginComplex.imag, beginComplex.real )
endComplex = complex( endComplex.imag, endComplex.real )
if beginComplex.real > endComplex.real:
endComplex, beginComplex = beginComplex, endComplex
deltaX = endComplex.real - beginComplex.real
deltaY = endComplex.imag - beginComplex.imag
if deltaX > 0.0:
gradient = deltaY / deltaX
else:
gradient = 0.0
print('Warning, deltaX in addValueSegmentToPixelTable in euclidean is 0.')
print(beginComplex)
print(value)
print(endComplex)
print(width)
xBegin = int(round(beginComplex.real))
xEnd = int(round(endComplex.real))
yIntersection = beginComplex.imag - beginComplex.real * gradient
if isSteep:
pixelDictionary[(int( round( beginComplex.imag ) ), xBegin)] = value
pixelDictionary[(int( round( endComplex.imag ) ), xEnd)] = value
for x in xrange( xBegin + 1, xEnd ):
y = int( math.floor( yIntersection + x * gradient ) )
pixelDictionary[(y, x)] = value
pixelDictionary[(y + 1, x)] = value
else:
pixelDictionary[(xBegin, int( round( beginComplex.imag ) ))] = value
pixelDictionary[(xEnd, int( round( endComplex.imag ) ))] = value
for x in xrange( xBegin + 1, xEnd ):
y = int( math.floor( yIntersection + x * gradient ) )
pixelDictionary[(x, y)] = value
pixelDictionary[(x, y + 1)] = value
def addValueToOutput(depth, keyInput, output, value):
'Add value to the output.'
depthStart = ' ' * depth
output.write('%s%s:' % (depthStart, keyInput))
if value.__class__ == dict:
output.write('\n')
keys = value.keys()
keys.sort()
for key in keys:
addValueToOutput(depth + 1, key, output, value[key])
return
if value.__class__ == list:
output.write('\n')
for elementIndex, element in enumerate(value):
addValueToOutput(depth + 1, elementIndex, output, element)
return
output.write(' %s\n' % value)
def addXIntersectionIndexesFromLoopListsY( loopLists, xIntersectionIndexList, y ):
'Add the x intersection indexes for the loop lists.'
for loopListIndex in xrange( len(loopLists) ):
loopList = loopLists[ loopListIndex ]
addXIntersectionIndexesFromLoopsY( loopList, loopListIndex, xIntersectionIndexList, y )
def addXIntersectionIndexesFromLoopsY( loops, solidIndex, xIntersectionIndexList, y ):
'Add the x intersection indexes for the loops.'
for loop in loops:
addXIntersectionIndexesFromLoopY( loop, solidIndex, xIntersectionIndexList, y )
def addXIntersectionIndexesFromLoopY( loop, solidIndex, xIntersectionIndexList, y ):
'Add the x intersection indexes for a loop.'
for pointIndex in xrange(len(loop)):
pointFirst = loop[pointIndex]
pointSecond = loop[(pointIndex + 1) % len(loop)]
xIntersection = getXIntersectionIfExists( pointFirst, pointSecond, y )
if xIntersection != None:
xIntersectionIndexList.append( XIntersectionIndex( solidIndex, xIntersection ) )
def addXIntersectionIndexesFromSegment( index, segment, xIntersectionIndexList ):
'Add the x intersection indexes from the segment.'
for endpoint in segment:
xIntersectionIndexList.append( XIntersectionIndex( index, endpoint.point.real ) )
def addXIntersectionIndexesFromSegments( index, segments, xIntersectionIndexList ):
'Add the x intersection indexes from the segments.'
for segment in segments:
addXIntersectionIndexesFromSegment( index, segment, xIntersectionIndexList )
def addXIntersectionIndexesFromXIntersections( index, xIntersectionIndexList, xIntersections ):
'Add the x intersection indexes from the XIntersections.'
for xIntersection in xIntersections:
xIntersectionIndexList.append( XIntersectionIndex( index, xIntersection ) )
def addXIntersections( loop, xIntersections, y ):
'Add the x intersections for a loop.'
for pointIndex in xrange(len(loop)):
pointFirst = loop[pointIndex]
pointSecond = loop[(pointIndex + 1) % len(loop)]
xIntersection = getXIntersectionIfExists( pointFirst, pointSecond, y )
if xIntersection != None:
xIntersections.append( xIntersection )
def addXIntersectionsFromLoopForTable(loop, xIntersectionsTable, width):
'Add the x intersections for a loop into a table.'
for pointIndex in xrange(len(loop)):
pointBegin = loop[pointIndex]
pointEnd = loop[(pointIndex + 1) % len(loop)]
if pointBegin.imag > pointEnd.imag:
pointOriginal = pointBegin
pointBegin = pointEnd
pointEnd = pointOriginal
fillBegin = int( math.ceil( pointBegin.imag / width ) )
fillEnd = int( math.ceil( pointEnd.imag / width ) )
if fillEnd > fillBegin:
secondMinusFirstComplex = pointEnd - pointBegin
secondMinusFirstImaginaryOverReal = secondMinusFirstComplex.real / secondMinusFirstComplex.imag
beginRealMinusImaginary = pointBegin.real - pointBegin.imag * secondMinusFirstImaginaryOverReal
for fillLine in xrange( fillBegin, fillEnd ):
y = fillLine * width
xIntersection = y * secondMinusFirstImaginaryOverReal + beginRealMinusImaginary
addElementToListDictionary( xIntersection, fillLine, xIntersectionsTable )
def addXIntersectionsFromLoops(loops, xIntersections, y):
'Add the x intersections for the loops.'
for loop in loops:
addXIntersections(loop, xIntersections, y)
def addXIntersectionsFromLoopsForTable(loops, xIntersectionsTable, width):
'Add the x intersections for a loop into a table.'
for loop in loops:
addXIntersectionsFromLoopForTable(loop, xIntersectionsTable, width)
def compareSegmentLength( endpoint, otherEndpoint ):
'Get comparison in order to sort endpoints in ascending order of segment length.'
if endpoint.segmentLength > otherEndpoint.segmentLength:
return 1
if endpoint.segmentLength < otherEndpoint.segmentLength:
return - 1
return 0
def concatenateRemovePath(connectedPaths, pathIndex, paths, pixelDictionary, segments, sharpestProduct, width):
'Get connected paths from paths.'
bottomSegment = segments[pathIndex]
path = paths[pathIndex]
if bottomSegment == None:
connectedPaths.append(path)
return
endpoints = getEndpointsFromSegments(segments[pathIndex + 1 :])
bottomSegmentEndpoint = bottomSegment[0]
nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath(endpoints, bottomSegmentEndpoint.path, pixelDictionary, sharpestProduct, width)
if nextEndpoint == None:
bottomSegmentEndpoint = bottomSegment[1]
nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath(endpoints, bottomSegmentEndpoint.path, pixelDictionary, sharpestProduct, width)
if nextEndpoint == None:
connectedPaths.append(path)
return
if len(bottomSegmentEndpoint.path) > 0 and len(nextEndpoint.path) > 0:
bottomEnd = bottomSegmentEndpoint.path[-1]
nextBegin = nextEndpoint.path[-1]
nextMinusBottomNormalized = getNormalized(nextBegin - bottomEnd)
if len( bottomSegmentEndpoint.path ) > 1:
bottomPenultimate = bottomSegmentEndpoint.path[-2]
if getDotProduct(getNormalized(bottomPenultimate - bottomEnd), nextMinusBottomNormalized) > 0.99:
connectedPaths.append(path)
return
if len( nextEndpoint.path ) > 1:
nextPenultimate = nextEndpoint.path[-2]
if getDotProduct(getNormalized(nextPenultimate - nextBegin), - nextMinusBottomNormalized) > 0.99:
connectedPaths.append(path)
return
nextEndpoint.path.reverse()
concatenatedPath = bottomSegmentEndpoint.path + nextEndpoint.path
paths[nextEndpoint.pathIndex] = concatenatedPath
segments[nextEndpoint.pathIndex] = getSegmentFromPath(concatenatedPath, nextEndpoint.pathIndex)
addValueSegmentToPixelTable(bottomSegmentEndpoint.point, nextEndpoint.point, pixelDictionary, None, width)
def getAngleAroundZAxisDifference( subtractFromVec3, subtractVec3 ):
'Get the angle around the Z axis difference between a pair of Vector3s.'
subtractVectorMirror = complex( subtractVec3.x , - subtractVec3.y )
differenceVector = getRoundZAxisByPlaneAngle( subtractVectorMirror, subtractFromVec3 )
return math.atan2( differenceVector.y, differenceVector.x )
def getAngleDifferenceByComplex( subtractFromComplex, subtractComplex ):
'Get the angle between a pair of normalized complexes.'
subtractComplexMirror = complex( subtractComplex.real , - subtractComplex.imag )
differenceComplex = subtractComplexMirror * subtractFromComplex
return math.atan2( differenceComplex.imag, differenceComplex.real )
def getAreaLoop(loop):
'Get the area of a complex polygon.'
areaLoopDouble = 0.0
for pointIndex, point in enumerate(loop):
pointEnd = loop[(pointIndex + 1) % len(loop)]
areaLoopDouble += point.real * pointEnd.imag - pointEnd.real * point.imag
return 0.5 * areaLoopDouble
def getAreaLoopAbsolute(loop):
'Get the absolute area of a complex polygon.'
return abs(getAreaLoop(loop))
def getAreaLoops(loops):
'Get the area of a list of complex polygons.'
areaLoops = 0.0
for loop in loops:
areaLoops += getAreaLoop(loop)
return areaLoops
def getAreaVector3LoopAbsolute(loop):
'Get the absolute area of a vector3 polygon.'
return getAreaLoopAbsolute(getComplexPath(loop))
def getAroundLoop(begin, end, loop):
'Get an arc around a loop.'
aroundLoop = []
if end <= begin:
end += len(loop)
for pointIndex in xrange(begin, end):
aroundLoop.append(loop[pointIndex % len(loop)])
return aroundLoop
def getAwayPath(path, radius):
'Get a path with only the points that are far enough away from each other, except for the last point.'
if len(path) < 2:
return path
lastPoint = path[-1]
awayPath = getAwayPoints(path, radius)
if len(awayPath) == 0:
return [lastPoint]
if abs(lastPoint - awayPath[-1]) > 0.001 * radius:
awayPath.append(lastPoint)
return awayPath
def getAwayPoints(points, radius):
'Get a path with only the points that are far enough away from each other.'
awayPoints = []
oneOverOverlapDistance = 1000.0 / radius
pixelDictionary = {}
for point in points:
x = int(point.real * oneOverOverlapDistance)
y = int(point.imag * oneOverOverlapDistance)
if not getSquareIsOccupied(pixelDictionary, x, y):
awayPoints.append(point)
pixelDictionary[(x, y)] = None
return awayPoints
def getBooleanFromDictionary(defaultBoolean, dictionary, key):
'Get boolean from the dictionary and key.'
if key not in dictionary:
return defaultBoolean
return getBooleanFromValue(dictionary[key])
def getBooleanFromValue(value):
'Get boolean from the word.'
firstCharacter = str(value).lower().lstrip()[: 1]
return firstCharacter == 't' or firstCharacter == '1'
def getBottomByPath(path):
'Get the bottom of the path.'
bottom = 987654321987654321.0
for point in path:
bottom = min(bottom, point.z)
return bottom
def getBottomByPaths(paths):
'Get the bottom of the paths.'
bottom = 987654321987654321.0
for path in paths:
for point in path:
bottom = min(bottom, point.z)
return bottom
def getClippedAtEndLoopPath( clip, loopPath ):
'Get a clipped loop path.'
if clip <= 0.0:
return loopPath
loopPathLength = getPathLength(loopPath)
clip = min( clip, 0.3 * loopPathLength )
lastLength = 0.0
pointIndex = 0
totalLength = 0.0
clippedLength = loopPathLength - clip
while totalLength < clippedLength and pointIndex < len(loopPath) - 1:
firstPoint = loopPath[pointIndex]
secondPoint = loopPath[pointIndex + 1]
pointIndex += 1
lastLength = totalLength
totalLength += abs(firstPoint - secondPoint)
remainingLength = clippedLength - lastLength
clippedLoopPath = loopPath[ : pointIndex ]
ultimateClippedPoint = loopPath[pointIndex]
penultimateClippedPoint = clippedLoopPath[-1]
segment = ultimateClippedPoint - penultimateClippedPoint
segmentLength = abs(segment)
if segmentLength <= 0.0:
return clippedLoopPath
newUltimatePoint = penultimateClippedPoint + segment * remainingLength / segmentLength
return clippedLoopPath + [newUltimatePoint]
def getClippedLoopPath(clip, loopPath):
'Get a clipped loop path.'
if clip <= 0.0:
return loopPath
loopPathLength = getPathLength(loopPath)
clip = min(clip, 0.3 * loopPathLength)
lastLength = 0.0
pointIndex = 0
totalLength = 0.0
while totalLength < clip and pointIndex < len(loopPath) - 1:
firstPoint = loopPath[pointIndex]
secondPoint = loopPath[pointIndex + 1]
pointIndex += 1
lastLength = totalLength
totalLength += abs(firstPoint - secondPoint)
remainingLength = clip - lastLength
clippedLoopPath = loopPath[pointIndex :]
ultimateClippedPoint = clippedLoopPath[0]
penultimateClippedPoint = loopPath[pointIndex - 1]
segment = ultimateClippedPoint - penultimateClippedPoint
segmentLength = abs(segment)
loopPath = clippedLoopPath
if segmentLength > 0.0:
newUltimatePoint = penultimateClippedPoint + segment * remainingLength / segmentLength
loopPath = [newUltimatePoint] + loopPath
return getClippedAtEndLoopPath(clip, loopPath)
def getClippedSimplifiedLoopPath(clip, loopPath, radius):
'Get a clipped and simplified loop path.'
return getSimplifiedPath(getClippedLoopPath(clip, loopPath), radius)
def getClosestDistanceIndexToLine(point, loop):
'Get the distance squared to the closest segment of the loop and index of that segment.'
smallestDistance = 987654321987654321.0
closestDistanceIndex = None
for pointIndex in xrange(len(loop)):
segmentBegin = loop[pointIndex]
segmentEnd = loop[(pointIndex + 1) % len(loop)]
distance = getDistanceToPlaneSegment(segmentBegin, segmentEnd, point)
if distance < smallestDistance:
smallestDistance = distance
closestDistanceIndex = DistanceIndex(distance, pointIndex)
return closestDistanceIndex
def getClosestPointOnSegment(segmentBegin, segmentEnd, point):
'Get the closest point on the segment.'
segmentDifference = segmentEnd - segmentBegin
if abs(segmentDifference) <= 0.0:
return segmentBegin
pointMinusSegmentBegin = point - segmentBegin
beginPlaneDot = getDotProduct(pointMinusSegmentBegin, segmentDifference)
differencePlaneDot = getDotProduct(segmentDifference, segmentDifference)
intercept = beginPlaneDot / differencePlaneDot
intercept = max(intercept, 0.0)
intercept = min(intercept, 1.0)
return segmentBegin + segmentDifference * intercept
def getComplexByCommaString( valueCommaString ):
'Get the commaString as a complex.'
try:
splitLine = valueCommaString.replace(',', ' ').split()
return complex( float( splitLine[0] ), float(splitLine[1]) )
except:
pass
return None
def getComplexByWords(words, wordIndex=0):
'Get the complex by the first two words.'
try:
return complex(float(words[wordIndex]), float(words[wordIndex + 1]))
except:
pass
return None
def getComplexDefaultByDictionary( defaultComplex, dictionary, key ):
'Get the value as a complex.'
if key in dictionary:
return complex( dictionary[key].strip().replace('(', '').replace(')', '') )
return defaultComplex
def getComplexDefaultByDictionaryKeys( defaultComplex, dictionary, keyX, keyY ):
'Get the value as a complex.'
x = getFloatDefaultByDictionary( defaultComplex.real, dictionary, keyX )
y = getFloatDefaultByDictionary( defaultComplex.real, dictionary, keyY )
return complex(x, y)
def getComplexPath(vector3Path):
'Get the complex path from the vector3 path.'
complexPath = []
for point in vector3Path:
complexPath.append(point.dropAxis())
return complexPath
def getComplexPathByMultiplier(multiplier, path):
'Get the multiplied complex path.'
complexPath = []
for point in path:
complexPath.append(multiplier * point)
return complexPath
def getComplexPaths(vector3Paths):
'Get the complex paths from the vector3 paths.'
complexPaths = []
for vector3Path in vector3Paths:
complexPaths.append(getComplexPath(vector3Path))
return complexPaths
def getComplexPolygon(center, radius, sides, startAngle=0.0):
'Get the complex polygon.'
complexPolygon = []
sideAngle = 2.0 * math.pi / float(sides)
for side in xrange(abs(sides)):
unitPolar = getWiddershinsUnitPolar(startAngle)
complexPolygon.append(unitPolar * radius + center)
startAngle += sideAngle
return complexPolygon
def getComplexPolygonByComplexRadius(radius, sides, startAngle=0.0):
'Get the complex polygon.'
complexPolygon = []
sideAngle = 2.0 * math.pi / float(sides)
for side in xrange(abs(sides)):
unitPolar = getWiddershinsUnitPolar(startAngle)
complexPolygon.append(complex(unitPolar.real * radius.real, unitPolar.imag * radius.imag))
startAngle += sideAngle
return complexPolygon
def getComplexPolygonByStartEnd(endAngle, radius, sides, startAngle=0.0):
'Get the complex polygon by start and end angle.'
angleExtent = endAngle - startAngle
sideAngle = 2.0 * math.pi / float(sides)
sides = int(math.ceil(abs(angleExtent / sideAngle)))
sideAngle = angleExtent / float(sides)
complexPolygon = []
for side in xrange(abs(sides) + 1):
unitPolar = getWiddershinsUnitPolar(startAngle)
complexPolygon.append(unitPolar * radius)
startAngle += sideAngle
return getLoopWithoutCloseEnds(0.000001 * radius, complexPolygon)
def getConcatenatedList(originalLists):
'Get the lists as one concatenated list.'
concatenatedList = []
for originalList in originalLists:
concatenatedList += originalList
return concatenatedList
def getConnectedPaths(paths, pixelDictionary, sharpestProduct, width):
'Get connected paths from paths.'
if len(paths) < 2:
return paths
connectedPaths = []
segments = []
for pathIndex in xrange(len(paths)):
path = paths[pathIndex]
segments.append(getSegmentFromPath(path, pathIndex))
for pathIndex in xrange(0, len(paths) - 1):
concatenateRemovePath(connectedPaths, pathIndex, paths, pixelDictionary, segments, sharpestProduct, width)
connectedPaths.append(paths[-1])
return connectedPaths
def getCrossProduct(firstComplex, secondComplex):
'Get z component cross product of a pair of complexes.'
return firstComplex.real * secondComplex.imag - firstComplex.imag * secondComplex.real
def getDecimalPlacesCarried(extraDecimalPlaces, value):
'Get decimal places carried by the decimal places of the value plus the extraDecimalPlaces.'
return max(0, 1 + int(math.ceil(extraDecimalPlaces - math.log10(value))))
def getDiagonalFlippedLoop(loop):
'Get loop flipped over the dialogonal, in other words with the x and y swapped.'
diagonalFlippedLoop = []
for point in loop:
diagonalFlippedLoop.append( complex( point.imag, point.real ) )
return diagonalFlippedLoop
def getDiagonalFlippedLoops(loops):
'Get loops flipped over the dialogonal, in other words with the x and y swapped.'
diagonalFlippedLoops = []
for loop in loops:
diagonalFlippedLoops.append( getDiagonalFlippedLoop(loop) )
return diagonalFlippedLoops
def getDictionaryString(dictionary):
'Get the dictionary string.'
output = cStringIO.StringIO()
keys = dictionary.keys()
keys.sort()
for key in keys:
addValueToOutput(0, key, output, dictionary[key])
return output.getvalue()
def getDistanceToLine(begin, end, point):
'Get the distance from a vector3 point to an infinite line.'
pointMinusBegin = point - begin
if begin == end:
return abs(pointMinusBegin)
endMinusBegin = end - begin
return abs(endMinusBegin.cross(pointMinusBegin)) / abs(endMinusBegin)
def getDistanceToLineByPath(begin, end, path):
'Get the maximum distance from a path to an infinite line.'
distanceToLine = -987654321.0
for point in path:
distanceToLine = max(getDistanceToLine(begin, end, point), distanceToLine)
return distanceToLine
def getDistanceToLineByPaths(begin, end, paths):
'Get the maximum distance from paths to an infinite line.'
distanceToLine = -987654321.0
for path in paths:
distanceToLine = max(getDistanceToLineByPath(begin, end, path), distanceToLine)
return distanceToLine
def getDistanceToPlaneSegment( segmentBegin, segmentEnd, point ):
'Get the distance squared from a point to the x & y components of a segment.'
segmentDifference = segmentEnd - segmentBegin
pointMinusSegmentBegin = point - segmentBegin
beginPlaneDot = getDotProduct( pointMinusSegmentBegin, segmentDifference )
if beginPlaneDot <= 0.0:
return abs( point - segmentBegin ) * abs( point - segmentBegin )
differencePlaneDot = getDotProduct( segmentDifference, segmentDifference )
if differencePlaneDot <= beginPlaneDot:
return abs( point - segmentEnd ) * abs( point - segmentEnd )
intercept = beginPlaneDot / differencePlaneDot
interceptPerpendicular = segmentBegin + segmentDifference * intercept
return abs( point - interceptPerpendicular ) * abs( point - interceptPerpendicular )
def getDotProduct(firstComplex, secondComplex):
'Get the dot product of a pair of complexes.'
return firstComplex.real * secondComplex.real + firstComplex.imag * secondComplex.imag
def getDotProductPlusOne( firstComplex, secondComplex ):
'Get the dot product plus one of the x and y components of a pair of Vector3s.'
return 1.0 + getDotProduct( firstComplex, secondComplex )
def getDurationString( seconds ):
'Get the duration string.'
secondsRounded = int( round( seconds ) )
durationString = getPluralString( secondsRounded % 60, 'second')
if seconds < 60:
return durationString
durationString = '%s %s' % ( getPluralString( ( secondsRounded / 60 ) % 60, 'minute'), durationString )
if seconds < 3600:
return durationString
return '%s %s' % ( getPluralString( secondsRounded / 3600, 'hour'), durationString )
def getEndpointFromPath( path, pathIndex ):
'Get endpoint segment from a path.'
begin = path[-1]
end = path[-2]
endpointBegin = Endpoint()
endpointEnd = Endpoint().getFromOtherPoint( endpointBegin, end )
endpointBegin.getFromOtherPoint( endpointEnd, begin )
endpointBegin.path = path
endpointBegin.pathIndex = pathIndex
return endpointBegin
def getEndpointsFromSegments( segments ):
'Get endpoints from segments.'
endpoints = []
for segment in segments:
for endpoint in segment:
endpoints.append( endpoint )
return endpoints
def getEndpointsFromSegmentTable( segmentTable ):
'Get the endpoints from the segment table.'
endpoints = []
segmentTableKeys = segmentTable.keys()
segmentTableKeys.sort()
for segmentTableKey in segmentTableKeys:
for segment in segmentTable[ segmentTableKey ]:
for endpoint in segment:
endpoints.append( endpoint )
return endpoints
def getEnumeratorKeys(enumerator, keys):
'Get enumerator keys.'
if len(keys) == 1:
return keys[0]
return getEnumeratorKeysExceptForOneArgument(enumerator, keys)
def getEnumeratorKeysAlwaysList(enumerator, keys):
'Get enumerator keys.'
if keys.__class__ != list:
return [keys]
if len(keys) == 1:
return keys
return getEnumeratorKeysExceptForOneArgument(enumerator, keys)
def getEnumeratorKeysExceptForOneArgument(enumerator, keys):
'Get enumerator keys, except when there is one argument.'
if len(keys) == 0:
return range(0, len(enumerator))
beginIndex = keys[0]
endIndex = keys[1]
if len(keys) == 2:
if beginIndex == None:
beginIndex = 0
if endIndex == None:
endIndex = len(enumerator)
return range(beginIndex, endIndex)
step = keys[2]
beginIndexDefault = 0
endIndexDefault = len(enumerator)
if step < 0:
beginIndexDefault = endIndexDefault - 1
endIndexDefault = -1
if beginIndex == None:
beginIndex = beginIndexDefault
if endIndex == None:
endIndex = endIndexDefault
return range(beginIndex, endIndex, step)
def getFillOfSurroundings(nestedRings, penultimateFillLoops):
'Get extra fill loops of nested rings.'
fillOfSurroundings = []
for nestedRing in nestedRings:
fillOfSurroundings += nestedRing.getFillLoops(penultimateFillLoops)
return fillOfSurroundings
def getFlattenedNestedRings(nestedRings):
'Get flattened nested rings.'
flattenedNestedRings = []
for nestedRing in nestedRings:
nestedRing.addFlattenedNestedRings(flattenedNestedRings)
return flattenedNestedRings
def getFloatDefaultByDictionary( defaultFloat, dictionary, key ):
'Get the value as a float.'
evaluatedFloat = None
if key in dictionary:
evaluatedFloat = getFloatFromValue(dictionary[key])
if evaluatedFloat == None:
return defaultFloat
return evaluatedFloat
def getFloatFromValue(value):
'Get the value as a float.'
try:
return float(value)
except:
pass
return None
def getFourSignificantFigures(number):
'Get number rounded to four significant figures as a string.'
if number == None:
return None
absoluteNumber = abs(number)
if absoluteNumber >= 100.0:
return getRoundedToPlacesString( 2, number )
if absoluteNumber < 0.000000001:
return getRoundedToPlacesString( 13, number )
return getRoundedToPlacesString( 3 - math.floor( math.log10( absoluteNumber ) ), number )
def getHalfSimplifiedLoop( loop, radius, remainder ):
'Get the loop with half of the points inside the channel removed.'
if len(loop) < 2:
return loop
channelRadius = abs(radius * .01)
simplified = []
addIndex = 0
if remainder == 1:
addIndex = len(loop) - 1
for pointIndex in xrange(len(loop)):
point = loop[pointIndex]
if pointIndex % 2 == remainder or pointIndex == addIndex:
simplified.append(point)
elif not isWithinChannel( channelRadius, pointIndex, loop ):
simplified.append(point)
return simplified
def getHalfSimplifiedPath(path, radius, remainder):
'Get the path with half of the points inside the channel removed.'
if len(path) < 2:
return path
channelRadius = abs(radius * .01)
simplified = [path[0]]
for pointIndex in xrange(1, len(path) - 1):
point = path[pointIndex]
if pointIndex % 2 == remainder:
simplified.append(point)
elif not isWithinChannel(channelRadius, pointIndex, path):
simplified.append(point)
simplified.append(path[-1])
return simplified
def getHorizontallyBoundedPath(horizontalBegin, horizontalEnd, path):
'Get horizontally bounded path.'
horizontallyBoundedPath = []
for pointIndex, point in enumerate(path):
begin = None
previousIndex = pointIndex - 1
if previousIndex >= 0:
begin = path[previousIndex]
end = None
nextIndex = pointIndex + 1
if nextIndex < len(path):
end = path[nextIndex]
addHorizontallyBoundedPoint(begin, point, end, horizontalBegin, horizontalEnd, horizontallyBoundedPath)
return horizontallyBoundedPath
def getIncrementFromRank( rank ):
'Get the increment from the rank which is 0 at 1 and increases by three every power of ten.'
rankZone = int( math.floor( rank / 3 ) )
rankModulo = rank % 3
powerOfTen = pow( 10, rankZone )
moduloMultipliers = ( 1, 2, 5 )
return float( powerOfTen * moduloMultipliers[ rankModulo ] )
def getInsidesAddToOutsides( loops, outsides ):
'Add loops to either the insides or outsides.'
insides = []
for loopIndex in xrange( len(loops) ):
loop = loops[loopIndex]
if isInsideOtherLoops( loopIndex, loops ):
insides.append(loop)
else:
outsides.append(loop)
return insides
def getIntermediateLocation( alongWay, begin, end ):
'Get the intermediate location between begin and end.'
return begin * ( 1.0 - alongWay ) + end * alongWay
def getIntersectionOfXIntersectionIndexes( totalSolidSurfaceThickness, xIntersectionIndexList ):
'Get x intersections from surrounding layers.'
xIntersectionList = []
solidTable = {}
solid = False
xIntersectionIndexList.sort()
for xIntersectionIndex in xIntersectionIndexList:
toggleHashtable(solidTable, xIntersectionIndex.index, '')
oldSolid = solid
solid = len(solidTable) >= totalSolidSurfaceThickness
if oldSolid != solid:
xIntersectionList.append(xIntersectionIndex.x)
return xIntersectionList
def getIntersectionOfXIntersectionsTables(xIntersectionsTables):
'Get the intersection of the XIntersections tables.'
if len(xIntersectionsTables) == 0:
return {}
intersectionOfXIntersectionsTables = {}
firstIntersectionTable = xIntersectionsTables[0]
for firstIntersectionTableKey in firstIntersectionTable.keys():
xIntersectionIndexList = []
for xIntersectionsTableIndex in xrange(len(xIntersectionsTables)):
xIntersectionsTable = xIntersectionsTables[xIntersectionsTableIndex]
if firstIntersectionTableKey in xIntersectionsTable:
addXIntersectionIndexesFromXIntersections(xIntersectionsTableIndex, xIntersectionIndexList, xIntersectionsTable[firstIntersectionTableKey])
xIntersections = getIntersectionOfXIntersectionIndexes(len(xIntersectionsTables), xIntersectionIndexList)
if len(xIntersections) > 0:
intersectionOfXIntersectionsTables[firstIntersectionTableKey] = xIntersections
return intersectionOfXIntersectionsTables
def getIntFromValue(value):
'Get the value as an int.'
try:
return int(value)
except:
pass
return None
def getIsInFilledRegion(loops, point):
'Determine if the point is in the filled region of the loops.'
return getNumberOfIntersectionsToLeftOfLoops(loops, point) % 2 == 1
def getIsInFilledRegionByPaths(loops, paths):
'Determine if the point of any path is in the filled region of the loops.'
for path in paths:
if len(path) > 0:
if getIsInFilledRegion(loops, path[0]):
return True
return False
def getIsRadianClose(firstRadian, secondRadian):
'Determine if the firstRadian is close to the secondRadian.'
return abs(math.pi - abs(math.pi - ((firstRadian - secondRadian) % (math.pi + math.pi) ))) < 0.000001
def getIsWiddershinsByVector3( polygon ):
'Determine if the polygon goes round in the widdershins direction.'
return isWiddershins( getComplexPath( polygon ) )
def getJoinOfXIntersectionIndexes( xIntersectionIndexList ):
'Get joined x intersections from surrounding layers.'
xIntersections = []
solidTable = {}
solid = False
xIntersectionIndexList.sort()
for xIntersectionIndex in xIntersectionIndexList:
toggleHashtable(solidTable, xIntersectionIndex.index, '')
oldSolid = solid
solid = len(solidTable) > 0
if oldSolid != solid:
xIntersections.append(xIntersectionIndex.x)
return xIntersections
def getLargestLoop(loops):
'Get largest loop from loops.'
largestArea = -987654321.0
largestLoop = []
for loop in loops:
loopArea = abs(getAreaLoopAbsolute(loop))
if loopArea > largestArea:
largestArea = loopArea
largestLoop = loop
return largestLoop
def getLeftPoint(points):
'Get the leftmost complex point in the points.'
leftmost = 987654321.0
leftPointComplex = None
for pointComplex in points:
if pointComplex.real < leftmost:
leftmost = pointComplex.real
leftPointComplex = pointComplex
return leftPointComplex
def getLeftPointIndex(points):
'Get the index of the leftmost complex point in the points.'
if len(points) < 1:
return None
leftPointIndex = 0
for pointIndex in xrange( len(points) ):
if points[pointIndex].real < points[ leftPointIndex ].real:
leftPointIndex = pointIndex
return leftPointIndex
def getListTableElements( listDictionary ):
'Get all the element in a list table.'
listDictionaryElements = []
for listDictionaryValue in listDictionary.values():
listDictionaryElements += listDictionaryValue
return listDictionaryElements
def getLoopCentroid(polygonComplex):
'Get the area of a complex polygon using http://en.wikipedia.org/wiki/Centroid.'
polygonDoubleArea = 0.0
polygonTorque = 0.0
for pointIndex in xrange( len(polygonComplex) ):
pointBegin = polygonComplex[pointIndex]
pointEnd = polygonComplex[ (pointIndex + 1) % len(polygonComplex) ]
doubleArea = pointBegin.real * pointEnd.imag - pointEnd.real * pointBegin.imag
doubleCenter = complex( pointBegin.real + pointEnd.real, pointBegin.imag + pointEnd.imag )
polygonDoubleArea += doubleArea
polygonTorque += doubleArea * doubleCenter
torqueMultiplier = 0.333333333333333333333333 / polygonDoubleArea
return polygonTorque * torqueMultiplier
def getLoopConvex(points):
'Get convex hull of points using gift wrap algorithm.'
loopConvex = []
pointSet = set()
for point in points:
if point not in pointSet:
pointSet.add(point)
loopConvex.append(point)
if len(loopConvex) < 4:
return loopConvex
leftPoint = getLeftPoint(loopConvex)
lastPoint = leftPoint
pointSet.remove(leftPoint)
loopConvex = [leftPoint]
lastSegment = complex(0.0, 1.0)
while True:
greatestDotProduct = -9.9
greatestPoint = None
greatestSegment = None
if len(loopConvex) > 2:
nextSegment = getNormalized(leftPoint - lastPoint)
if abs(nextSegment) > 0.0:
greatestDotProduct = getDotProduct(nextSegment, lastSegment)
for point in pointSet:
nextSegment = getNormalized(point - lastPoint)
if abs(nextSegment) > 0.0:
dotProduct = getDotProduct(nextSegment, lastSegment)
if dotProduct > greatestDotProduct:
greatestDotProduct = dotProduct
greatestPoint = point
greatestSegment = nextSegment
if greatestPoint == None:
return loopConvex
lastPoint = greatestPoint
loopConvex.append(greatestPoint)
pointSet.remove(greatestPoint)
lastSegment = greatestSegment
return loopConvex
def getLoopConvexCentroid(polygonComplex):
'Get centroid of the convex hull of a complex polygon.'
return getLoopCentroid( getLoopConvex(polygonComplex) )
def getLoopInsideContainingLoop( containingLoop, loops ):
'Get a loop that is inside the containing loop.'
for loop in loops:
if loop != containingLoop:
if isPathInsideLoop( containingLoop, loop ):
return loop
return None
def getLoopLength( polygon ):
'Get the length of a polygon perimeter.'
polygonLength = 0.0
for pointIndex in xrange( len( polygon ) ):
point = polygon[pointIndex]
secondPoint = polygon[ (pointIndex + 1) % len( polygon ) ]
polygonLength += abs( point - secondPoint )
return polygonLength
def getLoopStartingClosest(extrusionHalfWidth, location, loop):
'Add to threads from the last location from loop.'
closestIndex = getClosestDistanceIndexToLine(location, loop).index
loop = getAroundLoop(closestIndex, closestIndex, loop)
closestPoint = getClosestPointOnSegment(loop[0], loop[1], location)
if abs(closestPoint - loop[0]) > extrusionHalfWidth and abs(closestPoint - loop[1]) > extrusionHalfWidth:
loop = [closestPoint] + loop[1 :] + [loop[0]]
elif abs(closestPoint - loop[0]) > abs(closestPoint - loop[1]):
loop = loop[1 :] + [loop[0]]
return loop
def getLoopWithoutCloseEnds(close, loop):
'Get loop without close ends.'
if len(loop) < 2:
return loop
if abs(loop[0] - loop[-1]) > close:
return loop
return loop[: -1]
def getLoopWithoutCloseSequentialPoints(close, loop):
'Get loop without close sequential points.'
if len(loop) < 2:
return loop
lastPoint = loop[-1]
loopWithoutCloseSequentialPoints = []
for point in loop:
if abs(point - lastPoint) > close:
loopWithoutCloseSequentialPoints.append(point)
lastPoint = point
return loopWithoutCloseSequentialPoints
def getMaximum(firstComplex, secondComplex):
'Get a complex with each component the maximum of the respective components of a pair of complexes.'
return complex(max(firstComplex.real, secondComplex.real), max(firstComplex.imag, secondComplex.imag))
def getMaximumByComplexPath(path):
'Get a complex with each component the maximum of the respective components of a complex path.'
maximum = complex(-987654321987654321.0, -987654321987654321.0)
for point in path:
maximum = getMaximum(maximum, point)
return maximum
def getMaximumByComplexPaths(paths):
'Get a complex with each component the maximum of the respective components of complex paths.'
maximum = complex(-987654321987654321.0, -987654321987654321.0)
for path in paths:
for point in path:
maximum = getMaximum(maximum, point)
return maximum
def getMaximumByVector3Path(path):
'Get a vector3 with each component the maximum of the respective components of a vector3 path.'
maximum = Vector3(-987654321987654321.0, -987654321987654321.0, -987654321987654321.0)
for point in path:
maximum.maximize(point)
return maximum
def getMaximumByVector3Paths(paths):
'Get a complex with each component the maximum of the respective components of a complex path.'
maximum = Vector3(-987654321987654321.0, -987654231987654321.0, -987654321987654321.0)
for path in paths:
for point in path:
maximum.maximize(point)
return maximum
def getMaximumSpan(loop):
'Get the maximum span of the loop.'
extent = getMaximumByComplexPath(loop) - getMinimumByComplexPath(loop)
return max(extent.real, extent.imag)
def getMinimum(firstComplex, secondComplex):
'Get a complex with each component the minimum of the respective components of a pair of complexes.'
return complex(min(firstComplex.real, secondComplex.real), min(firstComplex.imag, secondComplex.imag))
def getMinimumByComplexPath(path):
'Get a complex with each component the minimum of the respective components of a complex path.'
minimum = complex(987654321987654321.0, 987654321987654321.0)
for point in path:
minimum = getMinimum(minimum, point)
return minimum
def getMinimumByComplexPaths(paths):
'Get a complex with each component the minimum of the respective components of complex paths.'
minimum = complex(987654321987654321.0, 987654321987654321.0)
for path in paths:
for point in path:
minimum = getMinimum(minimum, point)
return minimum
def getMinimumByVector3Path(path):
'Get a vector3 with each component the minimum of the respective components of a vector3 path.'
minimum = Vector3(987654321987654321.0, 987654321987654321.0, 987654321987654321.0)
for point in path:
minimum.minimize(point)
return minimum
def getMinimumByVector3Paths(paths):
'Get a complex with each component the minimum of the respective components of a complex path.'
minimum = Vector3(987654321987654321.0, 987654321987654321.0, 987654321987654321.0)
for path in paths:
for point in path:
minimum.minimize(point)
return minimum
def getMirrorPath(path):
"Get mirror path."
close = 0.001 * getPathLength(path)
for pointIndex in xrange(len(path) - 1, -1, -1):
point = path[pointIndex]
flipPoint = complex(-point.real, point.imag)
if abs(flipPoint - path[-1]) > close:
path.append(flipPoint)
return path
def getNormal(begin, center, end):
'Get normal.'
centerMinusBegin = (center - begin).getNormalized()
endMinusCenter = (end - center).getNormalized()
return centerMinusBegin.cross(endMinusCenter)
def getNormalByPath(path):
'Get normal by path.'
totalNormal = Vector3()
for pointIndex, point in enumerate(path):
center = path[(pointIndex + 1) % len(path)]
end = path[(pointIndex + 2) % len(path)]
totalNormal += getNormalWeighted(point, center, end)
return totalNormal.getNormalized()
def getNormalized(complexNumber):
'Get the normalized complex.'
complexNumberLength = abs(complexNumber)
if complexNumberLength > 0.0:
return complexNumber / complexNumberLength
return complexNumber
def getNormalWeighted(begin, center, end):
'Get weighted normal.'
return (center - begin).cross(end - center)
def getNumberOfIntersectionsToLeft(loop, point):
'Get the number of intersections through the loop for the line going left.'
numberOfIntersectionsToLeft = 0
for pointIndex in xrange(len(loop)):
firstPointComplex = loop[pointIndex]
secondPointComplex = loop[(pointIndex + 1) % len(loop)]
xIntersection = getXIntersectionIfExists(firstPointComplex, secondPointComplex, point.imag)
if xIntersection != None:
if xIntersection < point.real:
numberOfIntersectionsToLeft += 1
return numberOfIntersectionsToLeft
def getNumberOfIntersectionsToLeftOfLoops(loops, point):
'Get the number of intersections through the loop for the line starting from the left point and going left.'
totalNumberOfIntersectionsToLeft = 0
for loop in loops:
totalNumberOfIntersectionsToLeft += getNumberOfIntersectionsToLeft(loop, point)
return totalNumberOfIntersectionsToLeft
def getOrderedNestedRings(nestedRings):
'Get ordered nestedRings from nestedRings.'
insides = []
orderedNestedRings = []
for loopIndex in xrange(len(nestedRings)):
nestedRing = nestedRings[loopIndex]
otherLoops = []
for beforeIndex in xrange(loopIndex):
otherLoops.append(nestedRings[beforeIndex].boundary)
for afterIndex in xrange(loopIndex + 1, len(nestedRings)):
otherLoops.append(nestedRings[afterIndex].boundary)
if isPathEntirelyInsideLoops(otherLoops, nestedRing.boundary):
insides.append(nestedRing)
else:
orderedNestedRings.append(nestedRing)
for outside in orderedNestedRings:
outside.getFromInsideSurroundings(insides)
return orderedNestedRings
def getPathCopy(path):
'Get path copy.'
pathCopy = []
for point in path:
pathCopy.append(point.copy())
return pathCopy
def getPathLength(path):
'Get the length of a path ( an open polyline ).'
pathLength = 0.0
for pointIndex in xrange( len(path) - 1 ):
firstPoint = path[pointIndex]
secondPoint = path[pointIndex + 1]
pathLength += abs(firstPoint - secondPoint)
return pathLength
def getPathsFromEndpoints(endpoints, maximumConnectionLength, pixelDictionary, sharpestProduct, width):
'Get paths from endpoints.'
if len(endpoints) < 2:
return []
endpoints = endpoints[:] # so that the first two endpoints aren't removed when used again
for beginningEndpoint in endpoints[: : 2]:
beginningPoint = beginningEndpoint.point
addSegmentToPixelTable(beginningPoint, beginningEndpoint.otherEndpoint.point, pixelDictionary, 0, 0, width)
endpointFirst = endpoints[0]
endpoints.remove(endpointFirst)
otherEndpoint = endpointFirst.otherEndpoint
endpoints.remove(otherEndpoint)
nextEndpoint = None
path = []
paths = [path]
if len(endpoints) > 1:
nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, sharpestProduct, width)
if nextEndpoint != None:
if abs(nextEndpoint.point - endpointFirst.point) < abs(nextEndpoint.point - otherEndpoint.point):
endpointFirst = endpointFirst.otherEndpoint
otherEndpoint = endpointFirst.otherEndpoint
addPointToPath(path, pixelDictionary, endpointFirst.point, None, width)
addPointToPath(path, pixelDictionary, otherEndpoint.point, len(paths) - 1, width)
oneOverEndpointWidth = 1.0 / maximumConnectionLength
endpointTable = {}
for endpoint in endpoints:
addElementToPixelListFromPoint(endpoint, endpointTable, endpoint.point * oneOverEndpointWidth)
while len(endpointTable) > 0:
if len(endpointTable) == 1:
if len(endpointTable.values()[0]) < 2:
return []
endpoints = getSquareValuesFromPoint(endpointTable, otherEndpoint.point * oneOverEndpointWidth)
nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, sharpestProduct, width)
if nextEndpoint == None:
path = []
paths.append(path)
endpoints = getListTableElements(endpointTable)
nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
# this commented code should be faster than the getListTableElements code, but it isn't, someday a spiral algorithim could be tried
# endpoints = getSquareValuesFromPoint( endpointTable, otherEndpoint.point * oneOverEndpointWidth )
# nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
# if nextEndpoint == None:
# endpoints = []
# for endpointTableValue in endpointTable.values():
# endpoints.append( endpointTableValue[0] )
# nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
# endpoints = getSquareValuesFromPoint( endpointTable, nextEndpoint.point * oneOverEndpointWidth )
# nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
addPointToPath(path, pixelDictionary, nextEndpoint.point, len(paths) - 1, width)
removeElementFromPixelListFromPoint(nextEndpoint, endpointTable, nextEndpoint.point * oneOverEndpointWidth)
otherEndpoint = nextEndpoint.otherEndpoint
addPointToPath(path, pixelDictionary, otherEndpoint.point, len(paths) - 1, width)
removeElementFromPixelListFromPoint(otherEndpoint, endpointTable, otherEndpoint.point * oneOverEndpointWidth)
return paths
def getPlaneDot( vec3First, vec3Second ):
'Get the dot product of the x and y components of a pair of Vector3s.'
return vec3First.x * vec3Second.x + vec3First.y * vec3Second.y
def getPluralString( number, suffix ):
'Get the plural string.'
if number == 1:
return '1 %s' % suffix
return '%s %ss' % ( number, suffix )
def getPointPlusSegmentWithLength( length, point, segment ):
'Get point plus a segment scaled to a given length.'
return segment * length / abs(segment) + point
def getPointsByHorizontalDictionary(width, xIntersectionsDictionary):
'Get points from the horizontalXIntersectionsDictionary.'
points = []
xIntersectionsDictionaryKeys = xIntersectionsDictionary.keys()
xIntersectionsDictionaryKeys.sort()
for xIntersectionsDictionaryKey in xIntersectionsDictionaryKeys:
for xIntersection in xIntersectionsDictionary[xIntersectionsDictionaryKey]:
points.append(complex(xIntersection, xIntersectionsDictionaryKey * width))
return points
def getPointsByVerticalDictionary(width, xIntersectionsDictionary):
'Get points from the verticalXIntersectionsDictionary.'
points = []
xIntersectionsDictionaryKeys = xIntersectionsDictionary.keys()
xIntersectionsDictionaryKeys.sort()
for xIntersectionsDictionaryKey in xIntersectionsDictionaryKeys:
for xIntersection in xIntersectionsDictionary[xIntersectionsDictionaryKey]:
points.append(complex(xIntersectionsDictionaryKey * width, xIntersection))
return points
def getRadiusArealizedMultiplier(sides):
'Get the radius multiplier for a polygon of equal area.'
return math.sqrt(globalTau / sides / math.sin(globalTau / sides))
def getRandomComplex(begin, end):
'Get random complex.'
endMinusBegin = end - begin
return begin + complex(random.random() * endMinusBegin.real, random.random() * endMinusBegin.imag)
def getRank(width):
'Get the rank which is 0 at 1 and increases by three every power of ten.'
return int(math.floor(3.0 * math.log10(width)))
def getRotatedComplexes(planeAngle, points):
'Get points rotated by the plane angle'
rotatedComplexes = []
for point in points:
rotatedComplexes.append(planeAngle * point)
return rotatedComplexes
def getRotatedComplexLists(planeAngle, pointLists):
'Get point lists rotated by the plane angle'
rotatedComplexLists = []
for pointList in pointLists:
rotatedComplexLists.append(getRotatedComplexes(planeAngle, pointList))
return rotatedComplexLists
def getRotatedWiddershinsQuarterAroundZAxis(vector3):
'Get Vector3 rotated a quarter widdershins turn around Z axis.'
return Vector3(-vector3.y, vector3.x, vector3.z)
def getRoundedPoint(point):
'Get point with each component rounded.'
return Vector3(round(point.x), round( point.y ), round(point.z))
def getRoundedToPlaces(decimalPlaces, number):
'Get number rounded to a number of decimal places.'
decimalPlacesRounded = max(1, int(round(decimalPlaces)))
return round(number, decimalPlacesRounded)
def getRoundedToPlacesString(decimalPlaces, number):
'Get number rounded to a number of decimal places as a string, without exponential formatting.'
roundedToPlaces = getRoundedToPlaces(decimalPlaces, number)
roundedToPlacesString = str(roundedToPlaces)
if 'e' in roundedToPlacesString:
return ('%.15f' % roundedToPlaces).rstrip('0')
return roundedToPlacesString
def getRoundedToThreePlaces(number):
'Get number rounded to three places as a string.'
return str(round(number, 3))
def getRoundZAxisByPlaneAngle( planeAngle, vector3 ):
'Get Vector3 rotated by a plane angle.'
return Vector3( vector3.x * planeAngle.real - vector3.y * planeAngle.imag, vector3.x * planeAngle.imag + vector3.y * planeAngle.real, vector3.z )
def getSegmentFromPath( path, pathIndex ):
'Get endpoint segment from a path.'
if len(path) < 2:
return None
begin = path[-1]
end = path[-2]
forwardEndpoint = getEndpointFromPath( path, pathIndex )
reversePath = path[:]
reversePath.reverse()
reverseEndpoint = getEndpointFromPath( reversePath, pathIndex )
return ( forwardEndpoint, reverseEndpoint )
def getSegmentFromPoints( begin, end ):
'Get endpoint segment from a pair of points.'
endpointFirst = Endpoint()
endpointSecond = Endpoint().getFromOtherPoint( endpointFirst, end )
endpointFirst.getFromOtherPoint( endpointSecond, begin )
return ( endpointFirst, endpointSecond )
def getSegmentsFromXIntersectionIndexes( xIntersectionIndexList, y ):
'Get endpoint segments from the x intersection indexes.'
xIntersections = getXIntersectionsFromIntersections( xIntersectionIndexList )
return getSegmentsFromXIntersections( xIntersections, y )
def getSegmentsFromXIntersections( xIntersections, y ):
'Get endpoint segments from the x intersections.'
segments = []
end = len( xIntersections )
if len( xIntersections ) % 2 == 1:
end -= 1
for xIntersectionIndex in xrange( 0, end, 2 ):
firstX = xIntersections[ xIntersectionIndex ]
secondX = xIntersections[ xIntersectionIndex + 1 ]
if firstX != secondX:
segments.append( getSegmentFromPoints( complex( firstX, y ), complex( secondX, y ) ) )
return segments
def getSimplifiedLoop( loop, radius ):
'Get loop with points inside the channel removed.'
if len(loop) < 2:
return loop
simplificationMultiplication = 256
simplificationRadius = radius / float( simplificationMultiplication )
maximumIndex = len(loop) * simplificationMultiplication
pointIndex = 1
while pointIndex < maximumIndex:
oldLoopLength = len(loop)
loop = getHalfSimplifiedLoop( loop, simplificationRadius, 0 )
loop = getHalfSimplifiedLoop( loop, simplificationRadius, 1 )
simplificationRadius += simplificationRadius
if oldLoopLength == len(loop):
if simplificationRadius > radius:
return getAwayPoints( loop, radius )
else:
simplificationRadius *= 1.5
simplificationRadius = min( simplificationRadius, radius )
pointIndex += pointIndex
return getAwayPoints( loop, radius )
def getSimplifiedLoops( loops, radius ):
'Get the simplified loops.'
simplifiedLoops = []
for loop in loops:
simplifiedLoops.append( getSimplifiedLoop( loop, radius ) )
return simplifiedLoops
def getSimplifiedPath(path, radius):
'Get path with points inside the channel removed.'
if len(path) < 2:
return path
simplificationMultiplication = 256
simplificationRadius = radius / float(simplificationMultiplication)
maximumIndex = len(path) * simplificationMultiplication
pointIndex = 1
while pointIndex < maximumIndex:
oldPathLength = len(path)
path = getHalfSimplifiedPath(path, simplificationRadius, 0)
path = getHalfSimplifiedPath(path, simplificationRadius, 1)
simplificationRadius += simplificationRadius
if oldPathLength == len(path):
if simplificationRadius > radius:
return getAwayPath(path, radius)
else:
simplificationRadius *= 1.5
simplificationRadius = min(simplificationRadius, radius)
pointIndex += pointIndex
return getAwayPath(path, radius)
def getSquareIsOccupied( pixelDictionary, x, y ):
'Determine if a square around the x and y pixel coordinates is occupied.'
squareValues = []
for xStep in xrange(x - 1, x + 2):
for yStep in xrange(y - 1, y + 2):
if (xStep, yStep) in pixelDictionary:
return True
return False
def getSquareLoopWiddershins(beginComplex, endComplex):
'Get a square loop from the beginning to the end and back.'
loop = [beginComplex, complex(endComplex.real, beginComplex.imag), endComplex]
loop.append(complex(beginComplex.real, endComplex.imag))
return loop
def getSquareValues( pixelDictionary, x, y ):
'Get a list of the values in a square around the x and y pixel coordinates.'
squareValues = []
for xStep in xrange(x - 1, x + 2):
for yStep in xrange(y - 1, y + 2):
stepKey = (xStep, yStep)
if stepKey in pixelDictionary:
squareValues += pixelDictionary[ stepKey ]
return squareValues
def getSquareValuesFromPoint( pixelDictionary, point ):
'Get a list of the values in a square around the point.'
return getSquareValues(pixelDictionary, int(round(point.real)), int(round(point.imag)))
def getStepKeyFromPoint(point):
'Get step key for the point.'
return (int(round(point.real)), int(round(point.imag)))
def getThreeSignificantFigures(number):
'Get number rounded to three significant figures as a string.'
absoluteNumber = abs(number)
if absoluteNumber >= 10.0:
return getRoundedToPlacesString( 1, number )
if absoluteNumber < 0.000000001:
return getRoundedToPlacesString( 12, number )
return getRoundedToPlacesString( 1 - math.floor( math.log10( absoluteNumber ) ), number )
def getTopPath(path):
'Get the top of the path.'
top = -987654321987654321.0
for point in path:
top = max(top, point.z)
return top
def getTopPaths(paths):
'Get the top of the paths.'
top = -987654321987654321.0
for path in paths:
for point in path:
top = max(top, point.z)
return top
def getTransferClosestNestedRing(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence):
'Get and transfer the closest remaining nested ring.'
if len(nestedRings) > 0:
oldOrderedLocation.z = nestedRings[0].z
closestDistance = 987654321987654321.0
closestNestedRing = None
for remainingNestedRing in nestedRings:
distance = getClosestDistanceIndexToLine(oldOrderedLocation.dropAxis(), remainingNestedRing.boundary).distance
if distance < closestDistance:
closestDistance = distance
closestNestedRing = remainingNestedRing
nestedRings.remove(closestNestedRing)
closestNestedRing.addToThreads(extrusionHalfWidth, oldOrderedLocation, skein, threadSequence)
return closestNestedRing
def getTransferredNestedRings( insides, loop ):
'Get transferred paths from inside nested rings.'
transferredSurroundings = []
for insideIndex in xrange( len( insides ) - 1, - 1, - 1 ):
insideSurrounding = insides[ insideIndex ]
if isPathInsideLoop( loop, insideSurrounding.boundary ):
transferredSurroundings.append( insideSurrounding )
del insides[ insideIndex ]
return transferredSurroundings
def getTransferredPaths( insides, loop ):
'Get transferred paths from inside paths.'
transferredPaths = []
for insideIndex in xrange( len( insides ) - 1, - 1, - 1 ):
inside = insides[ insideIndex ]
if isPathInsideLoop( loop, inside ):
transferredPaths.append( inside )
del insides[ insideIndex ]
return transferredPaths
def getTranslatedComplexPath(path, translateComplex):
'Get the translated complex path.'
translatedComplexPath = []
for point in path:
translatedComplexPath.append(point + translateComplex)
return translatedComplexPath
def getVector3Path(complexPath, z=0.0):
'Get the vector3 path from the complex path.'
vector3Path = []
for complexPoint in complexPath:
vector3Path.append(Vector3(complexPoint.real, complexPoint.imag, z))
return vector3Path
def getVector3Paths(complexPaths, z=0.0):
'Get the vector3 paths from the complex paths.'
vector3Paths = []
for complexPath in complexPaths:
vector3Paths.append(getVector3Path(complexPath, z))
return vector3Paths
def getWiddershinsUnitPolar(angle):
'Get polar complex from counterclockwise angle from 1, 0.'
return complex(math.cos(angle), math.sin(angle))
def getXIntersectionIfExists( beginComplex, endComplex, y ):
'Get the x intersection if it exists.'
if ( y > beginComplex.imag ) == ( y > endComplex.imag ):
return None
endMinusBeginComplex = endComplex - beginComplex
return ( y - beginComplex.imag ) / endMinusBeginComplex.imag * endMinusBeginComplex.real + beginComplex.real
def getXIntersectionsFromIntersections( xIntersectionIndexList ):
'Get x intersections from the x intersection index list, in other words subtract non negative intersections from negatives.'
xIntersections = []
fill = False
solid = False
solidTable = {}
xIntersectionIndexList.sort()
for solidX in xIntersectionIndexList:
if solidX.index >= 0:
toggleHashtable( solidTable, solidX.index, '' )
else:
fill = not fill
oldSolid = solid
solid = ( len( solidTable ) == 0 and fill )
if oldSolid != solid:
xIntersections.append( solidX.x )
return xIntersections
def getXYComplexFromVector3(vector3):
'Get an xy complex from a vector3 if it exists, otherwise return None.'
if vector3 == None:
return None
return vector3.dropAxis()
def getYIntersectionIfExists( beginComplex, endComplex, x ):
'Get the y intersection if it exists.'
if ( x > beginComplex.real ) == ( x > endComplex.real ):
return None
endMinusBeginComplex = endComplex - beginComplex
return ( x - beginComplex.real ) / endMinusBeginComplex.real * endMinusBeginComplex.imag + beginComplex.imag
def getZComponentCrossProduct( vec3First, vec3Second ):
'Get z component cross product of a pair of Vector3s.'
return vec3First.x * vec3Second.y - vec3First.y * vec3Second.x
def isInsideOtherLoops( loopIndex, loops ):
'Determine if a loop in a list is inside another loop in that list.'
return isPathInsideLoops( loops[ : loopIndex ] + loops[loopIndex + 1 :], loops[loopIndex] )
def isLineIntersectingInsideXSegment( beginComplex, endComplex, segmentFirstX, segmentSecondX, y ):
'Determine if the line is crossing inside the x segment.'
xIntersection = getXIntersectionIfExists( beginComplex, endComplex, y )
if xIntersection == None:
return False
if xIntersection < min( segmentFirstX, segmentSecondX ):
return False
return xIntersection <= max( segmentFirstX, segmentSecondX )
def isLineIntersectingLoop( loop, pointBegin, pointEnd ):
'Determine if the line is intersecting loops.'
normalizedSegment = pointEnd - pointBegin
normalizedSegmentLength = abs( normalizedSegment )
if normalizedSegmentLength > 0.0:
normalizedSegment /= normalizedSegmentLength
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
pointBeginRotated = segmentYMirror * pointBegin
pointEndRotated = segmentYMirror * pointEnd
if isLoopIntersectingInsideXSegment( loop, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ):
return True
return False
def isLineIntersectingLoops( loops, pointBegin, pointEnd ):
'Determine if the line is intersecting loops.'
normalizedSegment = pointEnd - pointBegin
normalizedSegmentLength = abs( normalizedSegment )
if normalizedSegmentLength > 0.0:
normalizedSegment /= normalizedSegmentLength
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
pointBeginRotated = segmentYMirror * pointBegin
pointEndRotated = segmentYMirror * pointEnd
if isLoopListIntersectingInsideXSegment( loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ):
return True
return False
def isLoopIntersectingInsideXSegment( loop, segmentFirstX, segmentSecondX, segmentYMirror, y ):
'Determine if the loop is intersecting inside the x segment.'
rotatedLoop = getRotatedComplexes( segmentYMirror, loop )
for pointIndex in xrange( len( rotatedLoop ) ):
pointFirst = rotatedLoop[pointIndex]
pointSecond = rotatedLoop[ (pointIndex + 1) % len( rotatedLoop ) ]
if isLineIntersectingInsideXSegment( pointFirst, pointSecond, segmentFirstX, segmentSecondX, y ):
return True
return False
def isLoopIntersectingLoop( loop, otherLoop ):
'Determine if the loop is intersecting the other loop.'
for pointIndex in xrange(len(loop)):
pointBegin = loop[pointIndex]
pointEnd = loop[(pointIndex + 1) % len(loop)]
if isLineIntersectingLoop( otherLoop, pointBegin, pointEnd ):
return True
return False
def isLoopIntersectingLoops( loop, otherLoops ):
'Determine if the loop is intersecting other loops.'
for pointIndex in xrange(len(loop)):
pointBegin = loop[pointIndex]
pointEnd = loop[(pointIndex + 1) % len(loop)]
if isLineIntersectingLoops( otherLoops, pointBegin, pointEnd ):
return True
return False
def isLoopListIntersecting(loops):
'Determine if a loop in the list is intersecting the other loops.'
for loopIndex in xrange(len(loops) - 1):
loop = loops[loopIndex]
if isLoopIntersectingLoops(loop, loops[loopIndex + 1 :]):
return True
return False
def isLoopListIntersectingInsideXSegment( loopList, segmentFirstX, segmentSecondX, segmentYMirror, y ):
'Determine if the loop list is crossing inside the x segment.'
for alreadyFilledLoop in loopList:
if isLoopIntersectingInsideXSegment( alreadyFilledLoop, segmentFirstX, segmentSecondX, segmentYMirror, y ):
return True
return False
def isPathEntirelyInsideLoop(loop, path):
'Determine if a path is entirely inside another loop.'
for point in path:
if not isPointInsideLoop(loop, point):
return False
return True
def isPathEntirelyInsideLoops(loops, path):
'Determine if a path is entirely inside another loop in a list.'
for loop in loops:
if isPathEntirelyInsideLoop(loop, path):
return True
return False
def isPathInsideLoop(loop, path):
'Determine if a path is inside another loop.'
return isPointInsideLoop(loop, getLeftPoint(path))
def isPathInsideLoops(loops, path):
'Determine if a path is inside another loop in a list.'
for loop in loops:
if isPathInsideLoop(loop, path):
return True
return False
def isPixelTableIntersecting( bigTable, littleTable, maskTable = {} ):
'Add path to the pixel table.'
littleTableKeys = littleTable.keys()
for littleTableKey in littleTableKeys:
if littleTableKey not in maskTable:
if littleTableKey in bigTable:
return True
return False
def isPointInsideLoop(loop, point):
'Determine if a point is inside another loop.'
return getNumberOfIntersectionsToLeft(loop, point) % 2 == 1
def isSegmentCompletelyInX( segment, xFirst, xSecond ):
'Determine if the segment overlaps within x.'
segmentFirstX = segment[0].point.real
segmentSecondX = segment[1].point.real
if max( segmentFirstX, segmentSecondX ) > max( xFirst, xSecond ):
return False
return min( segmentFirstX, segmentSecondX ) >= min( xFirst, xSecond )
def isWiddershins(polygonComplex):
'Determine if the complex polygon goes round in the widdershins direction.'
return getAreaLoop(polygonComplex) > 0.0
def isWithinChannel( channelRadius, pointIndex, loop ):
'Determine if the the point is within the channel between two adjacent points.'
point = loop[pointIndex]
behindSegmentComplex = loop[(pointIndex + len(loop) - 1) % len(loop)] - point
behindSegmentComplexLength = abs( behindSegmentComplex )
if behindSegmentComplexLength < channelRadius:
return True
aheadSegmentComplex = loop[(pointIndex + 1) % len(loop)] - point
aheadSegmentComplexLength = abs( aheadSegmentComplex )
if aheadSegmentComplexLength < channelRadius:
return True
behindSegmentComplex /= behindSegmentComplexLength
aheadSegmentComplex /= aheadSegmentComplexLength
absoluteZ = getDotProductPlusOne( aheadSegmentComplex, behindSegmentComplex )
if behindSegmentComplexLength * absoluteZ < channelRadius:
return True
return aheadSegmentComplexLength * absoluteZ < channelRadius
def isXSegmentIntersectingPath( path, segmentFirstX, segmentSecondX, segmentYMirror, y ):
'Determine if a path is crossing inside the x segment.'
rotatedPath = getRotatedComplexes( segmentYMirror, path )
for pointIndex in xrange( len( rotatedPath ) - 1 ):
pointFirst = rotatedPath[pointIndex]
pointSecond = rotatedPath[pointIndex + 1]
if isLineIntersectingInsideXSegment( pointFirst, pointSecond, segmentFirstX, segmentSecondX, y ):
return True
return False
def isXSegmentIntersectingPaths( paths, segmentFirstX, segmentSecondX, segmentYMirror, y ):
'Determine if a path list is crossing inside the x segment.'
for path in paths:
if isXSegmentIntersectingPath( path, segmentFirstX, segmentSecondX, segmentYMirror, y ):
return True
return False
def joinSegmentTables( fromTable, intoTable ):
'Join both segment tables and put the join into the intoTable.'
intoTableKeys = intoTable.keys()
fromTableKeys = fromTable.keys()
joinedKeyTable = {}
concatenatedTableKeys = intoTableKeys + fromTableKeys
for concatenatedTableKey in concatenatedTableKeys:
joinedKeyTable[ concatenatedTableKey ] = None
joinedKeys = joinedKeyTable.keys()
joinedKeys.sort()
for joinedKey in joinedKeys:
xIntersectionIndexList = []
if joinedKey in intoTable:
addXIntersectionIndexesFromSegments( 0, intoTable[ joinedKey ], xIntersectionIndexList )
if joinedKey in fromTable:
addXIntersectionIndexesFromSegments( 1, fromTable[ joinedKey ], xIntersectionIndexList )
xIntersections = getJoinOfXIntersectionIndexes( xIntersectionIndexList )
lineSegments = getSegmentsFromXIntersections( xIntersections, joinedKey )
if len( lineSegments ) > 0:
intoTable[ joinedKey ] = lineSegments
else:
print('This should never happen, there are no line segments in joinSegments in euclidean')
def joinXIntersectionsTables( fromTable, intoTable ):
'Join both XIntersections tables and put the join into the intoTable.'
joinedKeyTable = {}
concatenatedTableKeys = fromTable.keys() + intoTable.keys()
for concatenatedTableKey in concatenatedTableKeys:
joinedKeyTable[ concatenatedTableKey ] = None
for joinedKey in joinedKeyTable.keys():
xIntersectionIndexList = []
if joinedKey in intoTable:
addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, intoTable[ joinedKey ] )
if joinedKey in fromTable:
addXIntersectionIndexesFromXIntersections( 1, xIntersectionIndexList, fromTable[ joinedKey ] )
xIntersections = getJoinOfXIntersectionIndexes( xIntersectionIndexList )
if len( xIntersections ) > 0:
intoTable[ joinedKey ] = xIntersections
else:
print('This should never happen, there are no line segments in joinSegments in euclidean')
def overwriteDictionary(fromDictionary, keys, toDictionary):
'Overwrite the dictionary.'
for key in keys:
if key in fromDictionary:
toDictionary[key] = fromDictionary[key]
def removeElementFromDictionary(dictionary, key):
'Remove element from the dictionary.'
if key in dictionary:
del dictionary[key]
def removeElementFromListTable(element, key, listDictionary):
'Remove an element from the list table.'
if key not in listDictionary:
return
elementList = listDictionary[key]
if len( elementList ) < 2:
del listDictionary[key]
return
if element in elementList:
elementList.remove(element)
def removeElementFromPixelListFromPoint( element, pixelDictionary, point ):
'Remove an element from the pixel list.'
stepKey = getStepKeyFromPoint(point)
removeElementFromListTable( element, stepKey, pixelDictionary )
def removeElementsFromDictionary(dictionary, keys):
'Remove list from the dictionary.'
for key in keys:
removeElementFromDictionary(dictionary, key)
def removePixelTableFromPixelTable( pixelDictionaryToBeRemoved, pixelDictionaryToBeRemovedFrom ):
'Remove pixel from the pixel table.'
removeElementsFromDictionary( pixelDictionaryToBeRemovedFrom, pixelDictionaryToBeRemoved.keys() )
def removePrefixFromDictionary( dictionary, prefix ):
'Remove the attributes starting with the prefix from the dictionary.'
for key in dictionary.keys():
if key.startswith( prefix ):
del dictionary[key]
def removeTrueFromDictionary(dictionary, key):
'Remove key from the dictionary in the value is true.'
if key in dictionary:
if getBooleanFromValue(dictionary[key]):
del dictionary[key]
def removeTrueListFromDictionary( dictionary, keys ):
'Remove list from the dictionary in the value is true.'
for key in keys:
removeTrueFromDictionary( dictionary, key )
def subtractXIntersectionsTable( subtractFromTable, subtractTable ):
'Subtract the subtractTable from the subtractFromTable.'
subtractFromTableKeys = subtractFromTable.keys()
subtractFromTableKeys.sort()
for subtractFromTableKey in subtractFromTableKeys:
xIntersectionIndexList = []
addXIntersectionIndexesFromXIntersections( - 1, xIntersectionIndexList, subtractFromTable[ subtractFromTableKey ] )
if subtractFromTableKey in subtractTable:
addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, subtractTable[ subtractFromTableKey ] )
xIntersections = getXIntersectionsFromIntersections( xIntersectionIndexList )
if len( xIntersections ) > 0:
subtractFromTable[ subtractFromTableKey ] = xIntersections
else:
del subtractFromTable[ subtractFromTableKey ]
def swapList( elements, indexBegin, indexEnd ):
'Swap the list elements.'
elements[ indexBegin ], elements[ indexEnd ] = elements[ indexEnd ], elements[ indexBegin ]
def toggleHashtable( hashtable, key, value ):
'Toggle a hashtable between having and not having a key.'
if key in hashtable:
del hashtable[key]
else:
hashtable[key] = value
def transferClosestFillLoop(extrusionHalfWidth, oldOrderedLocation, remainingFillLoops, skein):
'Transfer the closest remaining fill loop.'
closestDistance = 987654321987654321.0
closestFillLoop = None
for remainingFillLoop in remainingFillLoops:
distance = getClosestDistanceIndexToLine(oldOrderedLocation.dropAxis(), remainingFillLoop).distance
if distance < closestDistance:
closestDistance = distance
closestFillLoop = remainingFillLoop
newClosestFillLoop = getLoopInsideContainingLoop(closestFillLoop, remainingFillLoops)
while newClosestFillLoop != None:
closestFillLoop = newClosestFillLoop
newClosestFillLoop = getLoopInsideContainingLoop(closestFillLoop, remainingFillLoops)
remainingFillLoops.remove(closestFillLoop)
addToThreadsFromLoop(extrusionHalfWidth, 'loop', closestFillLoop[:], oldOrderedLocation, skein)
def transferClosestPath( oldOrderedLocation, remainingPaths, skein ):
'Transfer the closest remaining path.'
closestDistance = 987654321987654321.0
closestPath = None
oldOrderedLocationComplex = oldOrderedLocation.dropAxis()
for remainingPath in remainingPaths:
distance = min( abs( oldOrderedLocationComplex - remainingPath[0] ), abs( oldOrderedLocationComplex - remainingPath[-1] ) )
if distance < closestDistance:
closestDistance = distance
closestPath = remainingPath
remainingPaths.remove( closestPath )
skein.addGcodeFromThreadZ( closestPath, oldOrderedLocation.z )
oldOrderedLocation.x = closestPath[-1].real
oldOrderedLocation.y = closestPath[-1].imag
def transferClosestPaths(oldOrderedLocation, remainingPaths, skein):
'Transfer the closest remaining paths.'
while len(remainingPaths) > 0:
transferClosestPath(oldOrderedLocation, remainingPaths, skein)
def transferPathsToNestedRings(nestedRings, paths):
'Transfer paths to nested rings.'
for nestedRing in nestedRings:
nestedRing.transferPaths(paths)
def translateVector3Path(path, translateVector3):
'Translate the vector3 path.'
for point in path:
point.setToVector3(point + translateVector3)
def translateVector3Paths(paths, translateVector3):
'Translate the vector3 paths.'
for path in paths:
translateVector3Path(path, translateVector3)
def unbuckleBasis( basis, maximumUnbuckling, normal ):
'Unbuckle space.'
normalDot = basis.dot( normal )
dotComplement = math.sqrt( 1.0 - normalDot * normalDot )
unbuckling = maximumUnbuckling
if dotComplement > 0.0:
unbuckling = min( 1.0 / dotComplement, maximumUnbuckling )
basis.setToVector3( basis * unbuckling )
class DistanceIndex:
'A class to hold the distance and the index of the loop.'
def __init__(self, distance, index):
'Initialize.'
self.distance = distance
self.index = index
def __repr__(self):
'Get the string representation of this distance index.'
return '%s, %s' % (self.distance, self.index)
class Endpoint:
'The endpoint of a segment.'
def __repr__(self):
'Get the string representation of this Endpoint.'
return 'Endpoint %s, %s' % ( self.point, self.otherEndpoint.point )
def getClosestEndpoint( self, endpoints ):
'Get closest endpoint.'
smallestDistance = 987654321987654321.0
closestEndpoint = None
for endpoint in endpoints:
distance = abs( self.point - endpoint.point )
if distance < smallestDistance:
smallestDistance = distance
closestEndpoint = endpoint
return closestEndpoint
def getClosestMiss(self, endpoints, path, pixelDictionary, sharpestProduct, width):
'Get the closest endpoint which the segment to that endpoint misses the other extrusions.'
pathMaskTable = {}
smallestDistance = 987654321.0
penultimateMinusPoint = complex(0.0, 0.0)
if len(path) > 1:
penultimatePoint = path[-2]
addSegmentToPixelTable(penultimatePoint, self.point, pathMaskTable, 0, 0, width)
penultimateMinusPoint = penultimatePoint - self.point
if abs(penultimateMinusPoint) > 0.0:
penultimateMinusPoint /= abs(penultimateMinusPoint)
for endpoint in endpoints:
endpoint.segment = endpoint.point - self.point
endpoint.segmentLength = abs(endpoint.segment)
if endpoint.segmentLength <= 0.0:
return endpoint
endpoints.sort(compareSegmentLength)
for endpoint in endpoints[: 15]: # increasing the number of searched endpoints increases the search time, with 20 fill took 600 seconds for cilinder.gts, with 10 fill took 533 seconds
normalizedSegment = endpoint.segment / endpoint.segmentLength
isOverlappingSelf = getDotProduct(penultimateMinusPoint, normalizedSegment) > sharpestProduct
if not isOverlappingSelf:
if len(path) > 2:
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
pointRotated = segmentYMirror * self.point
endpointPointRotated = segmentYMirror * endpoint.point
if isXSegmentIntersectingPath(path[max(0, len(path) - 21) : -1], pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag):
isOverlappingSelf = True
if not isOverlappingSelf:
totalMaskTable = pathMaskTable.copy()
addSegmentToPixelTable(endpoint.point, endpoint.otherEndpoint.point, totalMaskTable, 0, 0, width)
segmentTable = {}
addSegmentToPixelTable(self.point, endpoint.point, segmentTable, 0, 0, width)
if not isPixelTableIntersecting(pixelDictionary, segmentTable, totalMaskTable):
return endpoint
return None
def getClosestMissCheckEndpointPath(self, endpoints, path, pixelDictionary, sharpestProduct, width):
'Get the closest endpoint which the segment to that endpoint misses the other extrusions, also checking the path of the endpoint.'
pathMaskTable = {}
smallestDistance = 987654321.0
penultimateMinusPoint = complex(0.0, 0.0)
if len(path) > 1:
penultimatePoint = path[-2]
addSegmentToPixelTable(penultimatePoint, self.point, pathMaskTable, 0, 0, width)
penultimateMinusPoint = penultimatePoint - self.point
if abs(penultimateMinusPoint) > 0.0:
penultimateMinusPoint /= abs(penultimateMinusPoint)
for endpoint in endpoints:
endpoint.segment = endpoint.point - self.point
endpoint.segmentLength = abs(endpoint.segment)
if endpoint.segmentLength <= 0.0:
return endpoint
endpoints.sort( compareSegmentLength )
for endpoint in endpoints[ : 15 ]: # increasing the number of searched endpoints increases the search time, with 20 fill took 600 seconds for cilinder.gts, with 10 fill took 533 seconds
normalizedSegment = endpoint.segment / endpoint.segmentLength
isOverlappingSelf = getDotProduct(penultimateMinusPoint, normalizedSegment) > sharpestProduct
if not isOverlappingSelf:
if len(path) > 2:
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
pointRotated = segmentYMirror * self.point
endpointPointRotated = segmentYMirror * endpoint.point
if isXSegmentIntersectingPath(path[ max(0, len(path) - 21) : -1], pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag):
isOverlappingSelf = True
endpointPath = endpoint.path
if len(endpointPath) > 2:
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
pointRotated = segmentYMirror * self.point
endpointPointRotated = segmentYMirror * endpoint.point
if isXSegmentIntersectingPath(endpointPath, pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag):
isOverlappingSelf = True
if not isOverlappingSelf:
totalMaskTable = pathMaskTable.copy()
addSegmentToPixelTable(endpoint.point, endpoint.otherEndpoint.point, totalMaskTable, 0, 0, width)
segmentTable = {}
addSegmentToPixelTable(self.point, endpoint.point, segmentTable, 0, 0, width)
if not isPixelTableIntersecting(pixelDictionary, segmentTable, totalMaskTable):
return endpoint
return None
def getFromOtherPoint( self, otherEndpoint, point ):
'Initialize from other endpoint.'
self.otherEndpoint = otherEndpoint
self.point = point
return self
class LoopLayer:
'Loops with a z.'
def __init__(self, z):
'Initialize.'
self.loops = []
self.z = z
def __repr__(self):
'Get the string representation of this loop layer.'
return '%s, %s' % (self.z, self.loops)
class NestedRing:
'A nested ring.'
def __init__(self):
'Initialize.'
self.boundary = []
self.innerNestedRings = None
def __repr__(self):
'Get the string representation of this nested ring.'
return str(self.__dict__)
def addFlattenedNestedRings(self, flattenedNestedRings):
'Add flattened nested rings.'
flattenedNestedRings.append(self)
for innerNestedRing in self.innerNestedRings:
flattenedNestedRings += getFlattenedNestedRings(innerNestedRing.innerNestedRings)
def getFromInsideSurroundings(self, inputSurroundingInsides):
'Initialize from inside nested rings.'
transferredSurroundings = getTransferredNestedRings(inputSurroundingInsides, self.boundary)
self.innerNestedRings = getOrderedNestedRings(transferredSurroundings)
return self
class NestedBand(NestedRing):
'A loop that surrounds paths.'
def __init__(self):
'Initialize.'
NestedRing.__init__(self)
self.edgePaths = []
self.extraLoops = []
self.infillBoundaries = []
self.infillPaths = []
# self.lastExistingFillLoops = None
self.lastFillLoops = None
self.loop = None
self.penultimateFillLoops = []
self.z = None
def __repr__(self):
'Get the string representation of this nested ring.'
stringRepresentation = 'boundary\n%s\n' % self.boundary
stringRepresentation += 'loop\n%s\n' % self.loop
stringRepresentation += 'inner nested rings\n%s\n' % self.innerNestedRings
stringRepresentation += 'infillPaths\n'
for infillPath in self.infillPaths:
stringRepresentation += 'infillPath\n%s\n' % infillPath
stringRepresentation += 'edgePaths\n'
for edgePath in self.edgePaths:
stringRepresentation += 'edgePath\n%s\n' % edgePath
return stringRepresentation + '\n'
def addPerimeterInner(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
'Add to the edge and the inner island.'
if self.loop == None:
skein.distanceFeedRate.addLine('(<edgePath>)')
transferClosestPaths(oldOrderedLocation, self.edgePaths[:], skein)
skein.distanceFeedRate.addLine('(</edgePath>)')
else:
addToThreadsFromLoop(extrusionHalfWidth, 'edge', self.loop[:], oldOrderedLocation, skein)
skein.distanceFeedRate.addLine('(</boundaryPerimeter>)')
addToThreadsRemove(extrusionHalfWidth, self.innerNestedRings[:], oldOrderedLocation, skein, threadSequence)
def addToBoundary(self, vector3):
'Add vector3 to boundary.'
self.boundary.append(vector3.dropAxis())
self.z = vector3.z
def addToLoop(self, vector3):
'Add vector3 to loop.'
if self.loop == None:
self.loop = []
self.loop.append(vector3.dropAxis())
self.z = vector3.z
def addToThreads(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
'Add to paths from the last location.'
addNestedRingBeginning(skein.distanceFeedRate, self.boundary, self.z)
threadFunctionDictionary = {
'infill' : self.transferInfillPaths, 'loops' : self.transferClosestFillLoops, 'edge' : self.addPerimeterInner}
for threadType in threadSequence:
threadFunctionDictionary[threadType](extrusionHalfWidth, oldOrderedLocation, skein, threadSequence)
skein.distanceFeedRate.addLine('(</nestedRing>)')
def getFillLoops(self, penultimateFillLoops):
'Get last fill loops from the outside loop and the loops inside the inside loops.'
fillLoops = self.getLoopsToBeFilled()[:]
surroundingBoundaries = self.getSurroundingBoundaries()
withinLoops = []
if penultimateFillLoops == None:
penultimateFillLoops = self.penultimateFillLoops
if penultimateFillLoops == None:
print('Warning, penultimateFillLoops == None in getFillLoops in NestedBand in euclidean.')
return fillLoops
for penultimateFillLoop in penultimateFillLoops:
if len(penultimateFillLoop) > 2:
if getIsInFilledRegion(surroundingBoundaries, penultimateFillLoop[0]):
withinLoops.append(penultimateFillLoop)
if not getIsInFilledRegionByPaths(self.penultimateFillLoops, fillLoops):
fillLoops += self.penultimateFillLoops
for nestedRing in self.innerNestedRings:
fillLoops += getFillOfSurroundings(nestedRing.innerNestedRings, penultimateFillLoops)
return fillLoops
#
# def getLastExistingFillLoops(self):
# 'Get last existing fill loops.'
# lastExistingFillLoops = self.lastExistingFillLoops[:]
# for nestedRing in self.innerNestedRings:
# lastExistingFillLoops += nestedRing.getLastExistingFillLoops()
# return lastExistingFillLoops
def getLoopsToBeFilled(self):
'Get last fill loops from the outside loop and the loops inside the inside loops.'
if self.lastFillLoops == None:
return self.getSurroundingBoundaries()
return self.lastFillLoops
def getSurroundingBoundaries(self):
'Get the boundary of the surronding loop plus any boundaries of the innerNestedRings.'
surroundingBoundaries = [self.boundary]
for nestedRing in self.innerNestedRings:
surroundingBoundaries.append(nestedRing.boundary)
return surroundingBoundaries
def transferClosestFillLoops(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
'Transfer closest fill loops.'
if len( self.extraLoops ) < 1:
return
remainingFillLoops = self.extraLoops[:]
while len( remainingFillLoops ) > 0:
transferClosestFillLoop(extrusionHalfWidth, oldOrderedLocation, remainingFillLoops, skein)
def transferInfillPaths(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
'Transfer the infill paths.'
if len(self.infillBoundaries) == 0 and len(self.infillPaths) == 0:
return
skein.distanceFeedRate.addLine('(<infill>)')
for infillBoundary in self.infillBoundaries:
skein.distanceFeedRate.addLine('(<infillBoundary>)')
for infillPoint in infillBoundary:
infillPointVector3 = Vector3(infillPoint.real, infillPoint.imag, self.z)
skein.distanceFeedRate.addLine(skein.distanceFeedRate.getInfillBoundaryLine(infillPointVector3))
skein.distanceFeedRate.addLine('(</infillBoundary>)')
transferClosestPaths(oldOrderedLocation, self.infillPaths[:], skein)
skein.distanceFeedRate.addLine('(</infill>)')
def transferPaths(self, paths):
'Transfer paths.'
for nestedRing in self.innerNestedRings:
transferPathsToNestedRings(nestedRing.innerNestedRings, paths)
self.infillPaths = getTransferredPaths(paths, self.boundary)
class PathZ:
'Complex path with a z.'
def __init__( self, z ):
self.path = []
self.z = z
def __repr__(self):
'Get the string representation of this path z.'
return '%s, %s' % ( self.z, self.path )
class ProjectiveSpace:
'Class to define a projective space.'
def __init__( self, basisX = Vector3(1.0, 0.0, 0.0), basisY = Vector3( 0.0, 1.0, 0.0 ), basisZ = Vector3(0.0, 0.0, 1.0) ):
'Initialize the basis vectors.'
self.basisX = basisX
self.basisY = basisY
self.basisZ = basisZ
def __repr__(self):
'Get the string representation of this ProjectivePlane.'
return '%s, %s, %s' % ( self.basisX, self.basisY, self.basisZ )
def getByBasisXZ( self, basisX, basisZ ):
'Get by x basis x and y basis.'
self.basisX = basisX
self.basisZ = basisZ
self.basisX.normalize()
self.basisY = basisZ.cross(self.basisX)
self.basisY.normalize()
return self
def getByBasisZFirst(self, basisZ, firstVector3):
'Get by basisZ and first.'
self.basisZ = basisZ
self.basisY = basisZ.cross(firstVector3)
self.basisY.normalize()
self.basisX = self.basisY.cross(self.basisZ)
self.basisX.normalize()
return self
def getByBasisZTop(self, basisZ, top):
'Get by basisZ and top.'
return self.getByBasisXZ(top.cross(basisZ), basisZ)
def getByLatitudeLongitude( self, viewpointLatitude, viewpointLongitude ):
'Get by latitude and longitude.'
longitudeComplex = getWiddershinsUnitPolar( math.radians( 90.0 - viewpointLongitude ) )
viewpointLatitudeRatio = getWiddershinsUnitPolar( math.radians( viewpointLatitude ) )
basisZ = Vector3( viewpointLatitudeRatio.imag * longitudeComplex.real, viewpointLatitudeRatio.imag * longitudeComplex.imag, viewpointLatitudeRatio.real )
return self.getByBasisXZ( Vector3( - longitudeComplex.imag, longitudeComplex.real, 0.0 ), basisZ )
def getByTilt( self, tilt ):
'Get by latitude and longitude.'
xPlaneAngle = getWiddershinsUnitPolar( tilt.real )
self.basisX = Vector3( xPlaneAngle.real, 0.0, xPlaneAngle.imag )
yPlaneAngle = getWiddershinsUnitPolar( tilt.imag )
self.basisY = Vector3( 0.0, yPlaneAngle.real, yPlaneAngle.imag )
self.basisZ = self.basisX.cross(self.basisY)
return self
def getComplexByComplex( self, pointComplex ):
'Get complex by complex point.'
return self.basisX.dropAxis() * pointComplex.real + self.basisY.dropAxis() * pointComplex.imag
def getCopy(self):
'Get copy.'
return ProjectiveSpace( self.basisX, self.basisY, self.basisZ )
def getDotComplex(self, point):
'Get the dot complex.'
return complex( point.dot(self.basisX), point.dot(self.basisY) )
def getDotVector3(self, point):
'Get the dot vector3.'
return Vector3(point.dot(self.basisX), point.dot(self.basisY), point.dot(self.basisZ))
def getNextSpace( self, nextNormal ):
'Get next space by next normal.'
nextSpace = self.getCopy()
nextSpace.normalize()
dotNext = nextSpace.basisZ.dot( nextNormal )
if dotNext > 0.999999:
return nextSpace
if dotNext < - 0.999999:
nextSpace.basisX = - nextSpace.basisX
return nextSpace
crossNext = nextSpace.basisZ.cross( nextNormal )
oldBasis = ProjectiveSpace().getByBasisZTop( nextSpace.basisZ, crossNext )
newBasis = ProjectiveSpace().getByBasisZTop( nextNormal, crossNext )
nextSpace.basisX = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisX ) )
nextSpace.basisY = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisY ) )
nextSpace.basisZ = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisZ ) )
nextSpace.normalize()
return nextSpace
def getSpaceByXYScaleAngle( self, angle, scale ):
'Get space by angle and scale.'
spaceByXYScaleRotation = ProjectiveSpace()
planeAngle = getWiddershinsUnitPolar(angle)
spaceByXYScaleRotation.basisX = self.basisX * scale.real * planeAngle.real + self.basisY * scale.imag * planeAngle.imag
spaceByXYScaleRotation.basisY = - self.basisX * scale.real * planeAngle.imag + self.basisY * scale.imag * planeAngle.real
spaceByXYScaleRotation.basisZ = self.basisZ
return spaceByXYScaleRotation
def getVector3ByPoint(self, point):
'Get vector3 by point.'
return self.basisX * point.x + self.basisY * point.y + self.basisZ * point.z
def normalize(self):
'Normalize.'
self.basisX.normalize()
self.basisY.normalize()
self.basisZ.normalize()
def unbuckle( self, maximumUnbuckling, normal ):
'Unbuckle space.'
unbuckleBasis( self.basisX, maximumUnbuckling, normal )
unbuckleBasis( self.basisY, maximumUnbuckling, normal )
class XIntersectionIndex:
'A class to hold the x intersection position and the index of the loop which intersected.'
def __init__( self, index, x ):
'Initialize.'
self.index = index
self.x = x
def __cmp__(self, other):
'Get comparison in order to sort x intersections in ascending order of x.'
if self.x < other.x:
return - 1
return int( self.x > other.x )
def __eq__(self, other):
'Determine whether this XIntersectionIndex is identical to other one.'
if other == None:
return False
if other.__class__ != self.__class__:
return False
return self.index == other.index and self.x == other.x
def __ne__(self, other):
'Determine whether this XIntersectionIndex is not identical to other one.'
return not self.__eq__(other)
def __repr__(self):
'Get the string representation of this x intersection.'
return 'XIntersectionIndex index %s; x %s ' % ( self.index, self.x )