MrDraw/SkeinPyPy/fabmetheus_utilities/geometry/manipulation_paths/overhang.py
daid 77d04ceab8 Removed patches for different skeinforge versions. Only SF48 now.
Updated build script to create win32/linux/macos versions.
Fixed the defaults to they work with PLA.
Fixed the temperature plugin default "ON" problem.
Removed all profiles except for PLA.
2012-02-10 17:20:03 +01:00

397 lines
17 KiB
Python

"""
Add material to support overhang or remove material at the overhang angle.
"""
from __future__ import absolute_import
#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.geometry.creation import lineation
from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting
from fabmetheus_utilities.geometry.geometry_utilities import evaluate
from fabmetheus_utilities.vector3 import Vector3
from fabmetheus_utilities import euclidean
import math
__author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
__credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
__date__ = '$Date: 2008/02/05 $'
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
globalExecutionOrder = 100
def addUnsupportedPointIndexes( alongAway ):
"Add the indexes of the unsupported points."
addedUnsupportedPointIndexes = []
for pointIndex in xrange( len( alongAway.loop ) ):
point = alongAway.loop[pointIndex]
if pointIndex not in alongAway.unsupportedPointIndexes:
if not alongAway.getIsClockwisePointSupported(point):
alongAway.unsupportedPointIndexes.append( pointIndex )
addedUnsupportedPointIndexes.append( pointIndex )
for pointIndex in addedUnsupportedPointIndexes:
point = alongAway.loop[pointIndex]
point.y += alongAway.maximumYPlus
def alterClockwiseSupportedPath( alongAway, elementNode ):
"Get clockwise path with overhangs carved out."
alongAway.bottomPoints = []
alongAway.overhangSpan = setting.getOverhangSpan(elementNode)
maximumY = - 987654321.0
minimumYPointIndex = 0
for pointIndex in xrange( len( alongAway.loop ) ):
point = alongAway.loop[pointIndex]
if point.y < alongAway.loop[ minimumYPointIndex ].y:
minimumYPointIndex = pointIndex
maximumY = max( maximumY, point.y )
alongAway.maximumYPlus = 2.0 * ( maximumY - alongAway.loop[ minimumYPointIndex ].y )
alongAway.loop = euclidean.getAroundLoop( minimumYPointIndex, minimumYPointIndex, alongAway.loop )
overhangClockwise = OverhangClockwise( alongAway )
alongAway.unsupportedPointIndexes = []
oldUnsupportedPointIndexesLength = - 987654321.0
while len( alongAway.unsupportedPointIndexes ) > oldUnsupportedPointIndexesLength:
oldUnsupportedPointIndexesLength = len( alongAway.unsupportedPointIndexes )
addUnsupportedPointIndexes( alongAway )
for pointIndex in alongAway.unsupportedPointIndexes:
point = alongAway.loop[pointIndex]
point.y -= alongAway.maximumYPlus
alongAway.unsupportedPointIndexes.sort()
alongAway.unsupportedPointIndexLists = []
oldUnsupportedPointIndex = - 987654321.0
unsupportedPointIndexList = None
for unsupportedPointIndex in alongAway.unsupportedPointIndexes:
if unsupportedPointIndex > oldUnsupportedPointIndex + 1:
unsupportedPointIndexList = []
alongAway.unsupportedPointIndexLists.append( unsupportedPointIndexList )
oldUnsupportedPointIndex = unsupportedPointIndex
unsupportedPointIndexList.append( unsupportedPointIndex )
alongAway.unsupportedPointIndexLists.reverse()
for unsupportedPointIndexList in alongAway.unsupportedPointIndexLists:
overhangClockwise.alterLoop( unsupportedPointIndexList )
def alterWiddershinsSupportedPath( alongAway, close ):
"Get widdershins path with overhangs filled in."
alongAway.bottomPoints = []
alongAway.minimumY = getMinimumYByPath( alongAway.loop )
for point in alongAway.loop:
if point.y - alongAway.minimumY < close:
alongAway.addToBottomPoints(point)
ascendingYPoints = alongAway.loop[:]
ascendingYPoints.sort( compareYAscending )
overhangWiddershinsLeft = OverhangWiddershinsLeft( alongAway )
overhangWiddershinsRight = OverhangWiddershinsRight( alongAway )
for point in ascendingYPoints:
alterWiddershinsSupportedPathByPoint( alongAway, overhangWiddershinsLeft, overhangWiddershinsRight, point )
def alterWiddershinsSupportedPathByPoint( alongAway, overhangWiddershinsLeft, overhangWiddershinsRight, point ):
"Get widdershins path with overhangs filled in for point."
if alongAway.getIsWiddershinsPointSupported(point):
return
overhangWiddershins = overhangWiddershinsLeft
if overhangWiddershinsRight.getDistance() < overhangWiddershinsLeft.getDistance():
overhangWiddershins = overhangWiddershinsRight
overhangWiddershins.alterLoop()
def compareYAscending( point, pointOther ):
"Get comparison in order to sort points in ascending y."
if point.y < pointOther.y:
return - 1
return int( point.y > pointOther.y )
def getManipulatedPaths(close, elementNode, loop, prefix, sideLength):
"Get path with overhangs removed or filled in."
if len(loop) < 3:
print('Warning, loop has less than three sides in getManipulatedPaths in overhang for:')
print(elementNode)
return [loop]
derivation = OverhangDerivation(elementNode, prefix)
overhangPlaneAngle = euclidean.getWiddershinsUnitPolar(0.5 * math.pi - derivation.overhangRadians)
if derivation.overhangInclinationRadians != 0.0:
overhangInclinationCosine = abs(math.cos(derivation.overhangInclinationRadians))
if overhangInclinationCosine == 0.0:
return [loop]
imaginaryTimesCosine = overhangPlaneAngle.imag * overhangInclinationCosine
overhangPlaneAngle = euclidean.getNormalized(complex(overhangPlaneAngle.real, imaginaryTimesCosine))
alongAway = AlongAway(loop, overhangPlaneAngle)
if euclidean.getIsWiddershinsByVector3(loop):
alterWiddershinsSupportedPath(alongAway, close)
else:
alterClockwiseSupportedPath(alongAway, elementNode)
return [euclidean.getLoopWithoutCloseSequentialPoints(close, alongAway.loop)]
def getMinimumYByPath(path):
"Get path with overhangs removed or filled in."
minimumYByPath = path[0].y
for point in path:
minimumYByPath = min( minimumYByPath, point.y )
return minimumYByPath
def getNewDerivation(elementNode, prefix, sideLength):
'Get new derivation.'
return OverhangDerivation(elementNode, prefix)
def processElementNode(elementNode):
"Process the xml element."
lineation.processElementNodeByFunction(elementNode, getManipulatedPaths)
class AlongAway:
"Class to derive the path along the point and away from the point."
def __init__( self, loop, overhangPlaneAngle ):
"Initialize."
self.loop = loop
self.overhangPlaneAngle = overhangPlaneAngle
self.ySupport = - self.overhangPlaneAngle.imag
def __repr__(self):
"Get the string representation of AlongAway."
return '%s' % ( self.overhangPlaneAngle )
def addToBottomPoints(self, point):
"Add point to bottom points and set y to minimumY."
self.bottomPoints.append(point)
point.y = self.minimumY
def getIsClockwisePointSupported(self, point):
"Determine if the point on the clockwise loop is supported."
self.point = point
self.pointIndex = None
self.awayIndexes = []
numberOfIntersectionsBelow = 0
for pointIndex in xrange( len( self.loop ) ):
begin = self.loop[pointIndex]
end = self.loop[ (pointIndex + 1) % len( self.loop ) ]
if begin != point and end != point:
self.awayIndexes.append( pointIndex )
yIntersection = euclidean.getYIntersectionIfExists( begin.dropAxis(), end.dropAxis(), point.x )
if yIntersection != None:
numberOfIntersectionsBelow += ( yIntersection < point.y )
if begin == point:
self.pointIndex = pointIndex
if numberOfIntersectionsBelow % 2 == 0:
return True
if self.pointIndex == None:
return True
if self.getIsPointSupportedBySegment( self.pointIndex - 1 + len( self.loop ) ):
return True
return self.getIsPointSupportedBySegment( self.pointIndex + 1 )
def getIsPointSupportedBySegment( self, endIndex ):
"Determine if the point on the widdershins loop is supported."
endComplex = self.loop[ ( endIndex % len( self.loop ) ) ].dropAxis()
endMinusPointComplex = euclidean.getNormalized( endComplex - self.point.dropAxis() )
return endMinusPointComplex.imag < self.ySupport
def getIsWiddershinsPointSupported(self, point):
"Determine if the point on the widdershins loop is supported."
if point.y <= self.minimumY:
return True
self.point = point
self.pointIndex = None
self.awayIndexes = []
numberOfIntersectionsBelow = 0
for pointIndex in xrange( len( self.loop ) ):
begin = self.loop[pointIndex]
end = self.loop[ (pointIndex + 1) % len( self.loop ) ]
if begin != point and end != point:
self.awayIndexes.append( pointIndex )
yIntersection = euclidean.getYIntersectionIfExists( begin.dropAxis(), end.dropAxis(), point.x )
if yIntersection != None:
numberOfIntersectionsBelow += ( yIntersection < point.y )
if begin == point:
self.pointIndex = pointIndex
if numberOfIntersectionsBelow % 2 == 1:
return True
if self.pointIndex == None:
return True
if self.getIsPointSupportedBySegment( self.pointIndex - 1 + len( self.loop ) ):
return True
return self.getIsPointSupportedBySegment( self.pointIndex + 1 )
class OverhangClockwise:
"Class to get the intersection up from the point."
def __init__( self, alongAway ):
"Initialize."
self.alongAway = alongAway
self.halfRiseOverWidth = 0.5 * alongAway.overhangPlaneAngle.imag / alongAway.overhangPlaneAngle.real
self.widthOverRise = alongAway.overhangPlaneAngle.real / alongAway.overhangPlaneAngle.imag
def __repr__(self):
"Get the string representation of OverhangClockwise."
return '%s' % ( self.intersectionPlaneAngle )
def alterLoop( self, unsupportedPointIndexes ):
"Alter alongAway loop."
unsupportedBeginIndex = unsupportedPointIndexes[0]
unsupportedEndIndex = unsupportedPointIndexes[-1]
beginIndex = unsupportedBeginIndex - 1
endIndex = unsupportedEndIndex + 1
begin = self.alongAway.loop[ beginIndex ]
end = self.alongAway.loop[ endIndex ]
truncatedOverhangSpan = self.alongAway.overhangSpan
width = end.x - begin.x
heightDifference = abs( end.y - begin.y )
remainingWidth = width - self.widthOverRise * heightDifference
if remainingWidth <= 0.0:
del self.alongAway.loop[ unsupportedBeginIndex : endIndex ]
return
highest = begin
supportX = begin.x + remainingWidth
if end.y > begin.y:
highest = end
supportX = end.x - remainingWidth
tipY = highest.y + self.halfRiseOverWidth * remainingWidth
highestBetween = - 987654321.0
for unsupportedPointIndex in unsupportedPointIndexes:
highestBetween = max( highestBetween, self.alongAway.loop[ unsupportedPointIndex ].y )
if highestBetween > highest.y:
truncatedOverhangSpan = 0.0
if highestBetween < tipY:
below = tipY - highestBetween
truncatedOverhangSpan = min( self.alongAway.overhangSpan, below / self.halfRiseOverWidth )
truncatedOverhangSpanRadius = 0.5 * truncatedOverhangSpan
if remainingWidth <= truncatedOverhangSpan:
supportPoint = Vector3( supportX, highest.y, highest.z )
self.alongAway.loop[ unsupportedBeginIndex : endIndex ] = [ supportPoint ]
return
midSupportX = 0.5 * ( supportX + highest.x )
if truncatedOverhangSpan <= 0.0:
supportPoint = Vector3( midSupportX, tipY, highest.z )
self.alongAway.loop[ unsupportedBeginIndex : endIndex ] = [ supportPoint ]
return
supportXLeft = midSupportX - truncatedOverhangSpanRadius
supportXRight = midSupportX + truncatedOverhangSpanRadius
supportY = tipY - self.halfRiseOverWidth * truncatedOverhangSpan
supportPoints = [ Vector3( supportXLeft, supportY, highest.z ), Vector3( supportXRight, supportY, highest.z ) ]
self.alongAway.loop[ unsupportedBeginIndex : endIndex ] = supportPoints
class OverhangDerivation:
"Class to hold overhang variables."
def __init__(self, elementNode, prefix):
'Set defaults.'
self.overhangRadians = setting.getOverhangRadians(elementNode)
self.overhangInclinationRadians = math.radians(evaluate.getEvaluatedFloat(0.0, elementNode, prefix + 'inclination'))
class OverhangWiddershinsLeft:
"Class to get the intersection from the point down to the left."
def __init__( self, alongAway ):
"Initialize."
self.alongAway = alongAway
self.intersectionPlaneAngle = - alongAway.overhangPlaneAngle
self.setRatios()
def __repr__(self):
"Get the string representation of OverhangWiddershins."
return '%s' % ( self.intersectionPlaneAngle )
def alterLoop(self):
"Alter alongAway loop."
insertedPoint = self.alongAway.point.copy()
if self.closestXIntersectionIndex != None:
self.alongAway.loop = self.getIntersectLoop()
intersectionRelativeComplex = self.closestXDistance * self.intersectionPlaneAngle
intersectionPoint = insertedPoint + Vector3( intersectionRelativeComplex.real, intersectionRelativeComplex.imag )
self.alongAway.loop.append( intersectionPoint )
return
if self.closestBottomPoint == None:
return
if self.closestBottomPoint not in self.alongAway.loop:
return
insertedPoint.x = self.bottomX
closestBottomIndex = self.alongAway.loop.index( self.closestBottomPoint )
self.alongAway.addToBottomPoints( insertedPoint )
self.alongAway.loop = self.getBottomLoop( closestBottomIndex, insertedPoint )
self.alongAway.loop.append( insertedPoint )
def getBottomLoop( self, closestBottomIndex, insertedPoint ):
"Get loop around bottom."
endIndex = closestBottomIndex + len( self.alongAway.loop ) + 1
return euclidean.getAroundLoop( self.alongAway.pointIndex, endIndex, self.alongAway.loop )
def getDistance(self):
"Get distance between point and closest intersection or bottom point along line."
self.pointMinusBottomY = self.alongAway.point.y - self.alongAway.minimumY
self.diagonalDistance = self.pointMinusBottomY * self.diagonalRatio
if self.alongAway.pointIndex == None:
return self.getDistanceToBottom()
rotatedLoop = euclidean.getRotatedComplexes( self.intersectionYMirror, euclidean.getComplexPath( self.alongAway.loop ) )
rotatedPointComplex = rotatedLoop[ self.alongAway.pointIndex ]
beginX = rotatedPointComplex.real
endX = beginX + self.diagonalDistance + self.diagonalDistance
xIntersectionIndexList = []
for pointIndex in self.alongAway.awayIndexes:
beginComplex = rotatedLoop[pointIndex]
endComplex = rotatedLoop[ (pointIndex + 1) % len( rotatedLoop ) ]
xIntersection = euclidean.getXIntersectionIfExists( beginComplex, endComplex, rotatedPointComplex.imag )
if xIntersection != None:
if xIntersection >= beginX and xIntersection < endX:
xIntersectionIndexList.append( euclidean.XIntersectionIndex( pointIndex, xIntersection ) )
self.closestXDistance = 987654321.0
self.closestXIntersectionIndex = None
for xIntersectionIndex in xIntersectionIndexList:
xDistance = abs( xIntersectionIndex.x - beginX )
if xDistance < self.closestXDistance:
self.closestXIntersectionIndex = xIntersectionIndex
self.closestXDistance = xDistance
if self.closestXIntersectionIndex != None:
return self.closestXDistance
return self.getDistanceToBottom()
def getDistanceToBottom(self):
"Get distance between point and closest bottom point along line."
self.bottomX = self.alongAway.point.x + self.pointMinusBottomY * self.xRatio
self.closestBottomPoint = None
closestDistanceX = 987654321.0
for point in self.alongAway.bottomPoints:
distanceX = abs( point.x - self.bottomX )
if self.getIsOnside(point.x):
if distanceX < closestDistanceX:
closestDistanceX = distanceX
self.closestBottomPoint = point
return closestDistanceX + self.diagonalDistance
def getIntersectLoop(self):
"Get intersection loop."
endIndex = self.closestXIntersectionIndex.index + len( self.alongAway.loop ) + 1
return euclidean.getAroundLoop( self.alongAway.pointIndex, endIndex, self.alongAway.loop )
def getIsOnside( self, x ):
"Determine if x is on the side along the direction of the intersection line."
return x <= self.alongAway.point.x
def setRatios(self):
"Set ratios."
self.diagonalRatio = 1.0 / abs( self.intersectionPlaneAngle.imag )
self.intersectionYMirror = complex( self.intersectionPlaneAngle.real, - self.intersectionPlaneAngle.imag )
self.xRatio = self.intersectionPlaneAngle.real / abs( self.intersectionPlaneAngle.imag )
class OverhangWiddershinsRight( OverhangWiddershinsLeft ):
"Class to get the intersection from the point down to the right."
def __init__( self, alongAway ):
"Initialize."
self.alongAway = alongAway
self.intersectionPlaneAngle = complex( alongAway.overhangPlaneAngle.real, - alongAway.overhangPlaneAngle.imag )
self.setRatios()
def getBottomLoop( self, closestBottomIndex, insertedPoint ):
"Get loop around bottom."
endIndex = self.alongAway.pointIndex + len( self.alongAway.loop ) + 1
return euclidean.getAroundLoop( closestBottomIndex, endIndex, self.alongAway.loop )
def getIntersectLoop(self):
"Get intersection loop."
beginIndex = self.closestXIntersectionIndex.index + len( self.alongAway.loop ) + 1
endIndex = self.alongAway.pointIndex + len( self.alongAway.loop ) + 1
return euclidean.getAroundLoop( beginIndex, endIndex, self.alongAway.loop )
def getIsOnside( self, x ):
"Determine if x is on the side along the direction of the intersection line."
return x >= self.alongAway.point.x