940 lines
36 KiB
Python
940 lines
36 KiB
Python
"""
|
|
Triangle Mesh holds the faces and edges of a triangular mesh.
|
|
|
|
"""
|
|
|
|
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.geometry_tools import face
|
|
from fabmetheus_utilities.geometry.geometry_tools import dictionary
|
|
from fabmetheus_utilities.geometry.geometry_tools import vertex
|
|
from fabmetheus_utilities.geometry.geometry_utilities import evaluate
|
|
from fabmetheus_utilities.geometry.geometry_utilities import matrix
|
|
from fabmetheus_utilities.geometry.solids import group
|
|
from fabmetheus_utilities import xml_simple_writer
|
|
from fabmetheus_utilities.vector3 import Vector3
|
|
from fabmetheus_utilities.vector3index import Vector3Index
|
|
from fabmetheus_utilities import euclidean
|
|
from fabmetheus_utilities import intercircle
|
|
from fabmetheus_utilities import settings
|
|
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'
|
|
|
|
|
|
def addEdgePair( edgePairTable, edges, faceEdgeIndex, remainingEdgeIndex, remainingEdgeTable ):
|
|
'Add edge pair to the edge pair table.'
|
|
if faceEdgeIndex == remainingEdgeIndex:
|
|
return
|
|
if not faceEdgeIndex in remainingEdgeTable:
|
|
return
|
|
edgePair = EdgePair().getFromIndexesEdges( [ remainingEdgeIndex, faceEdgeIndex ], edges )
|
|
edgePairTable[ str( edgePair ) ] = edgePair
|
|
|
|
def addFacesByConcaveLoop(faces, indexedLoop):
|
|
'Add faces from a polygon which is concave.'
|
|
if len(indexedLoop) < 3:
|
|
return
|
|
remainingLoop = indexedLoop[:]
|
|
while len(remainingLoop) > 2:
|
|
remainingLoop = getRemainingLoopAddFace(faces, remainingLoop)
|
|
|
|
def addFacesByConvex(faces, indexedLoop):
|
|
'Add faces from a convex polygon.'
|
|
if len(indexedLoop) < 3:
|
|
return
|
|
indexBegin = indexedLoop[0].index
|
|
for indexedPointIndex in xrange(1, len(indexedLoop) - 1):
|
|
indexCenter = indexedLoop[indexedPointIndex].index
|
|
indexEnd = indexedLoop[(indexedPointIndex + 1) % len(indexedLoop) ].index
|
|
if indexBegin != indexCenter and indexCenter != indexEnd and indexEnd != indexBegin:
|
|
faceFromConvex = face.Face()
|
|
faceFromConvex.index = len(faces)
|
|
faceFromConvex.vertexIndexes.append(indexBegin)
|
|
faceFromConvex.vertexIndexes.append(indexCenter)
|
|
faceFromConvex.vertexIndexes.append(indexEnd)
|
|
faces.append(faceFromConvex)
|
|
|
|
def addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop):
|
|
'Add faces from loops.'
|
|
if len(indexedLoopBottom) == 0 or len(indexedLoopTop) == 0:
|
|
return
|
|
for indexedPointIndex in xrange(max(len(indexedLoopBottom), len(indexedLoopTop))):
|
|
indexedConvex = []
|
|
if len(indexedLoopBottom) > 1:
|
|
indexedConvex.append(indexedLoopBottom[indexedPointIndex])
|
|
indexedConvex.append(indexedLoopBottom[(indexedPointIndex + 1) % len(indexedLoopBottom)])
|
|
else:
|
|
indexedConvex.append(indexedLoopBottom[0])
|
|
if len(indexedLoopTop) > 1:
|
|
indexedConvex.append(indexedLoopTop[(indexedPointIndex + 1) % len(indexedLoopTop)])
|
|
indexedConvex.append(indexedLoopTop[indexedPointIndex])
|
|
else:
|
|
indexedConvex.append(indexedLoopTop[0])
|
|
addFacesByConvex(faces, indexedConvex)
|
|
|
|
def addFacesByConvexLoops(faces, indexedLoops):
|
|
'Add faces from loops.'
|
|
if len(indexedLoops) < 2:
|
|
return
|
|
for indexedLoopsIndex in xrange(len(indexedLoops) - 2):
|
|
addFacesByConvexBottomTopLoop(faces, indexedLoops[indexedLoopsIndex], indexedLoops[indexedLoopsIndex + 1])
|
|
indexedLoopBottom = indexedLoops[-2]
|
|
indexedLoopTop = indexedLoops[-1]
|
|
if len(indexedLoopTop) < 1:
|
|
indexedLoopTop = indexedLoops[0]
|
|
addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop)
|
|
|
|
def addFacesByConvexReversed(faces, indexedLoop):
|
|
'Add faces from a reversed convex polygon.'
|
|
addFacesByConvex(faces, indexedLoop[: : -1])
|
|
|
|
def addFacesByGrid(faces, grid):
|
|
'Add faces from grid.'
|
|
cellTopLoops = getIndexedCellLoopsFromIndexedGrid(grid)
|
|
for cellTopLoop in cellTopLoops:
|
|
addFacesByConvex(faces, cellTopLoop)
|
|
|
|
def addFacesByLoop(faces, indexedLoop):
|
|
'Add faces from a polygon which may be concave.'
|
|
if len(indexedLoop) < 3:
|
|
return
|
|
lastNormal = None
|
|
for pointIndex, point in enumerate(indexedLoop):
|
|
center = indexedLoop[(pointIndex + 1) % len(indexedLoop)]
|
|
end = indexedLoop[(pointIndex + 2) % len(indexedLoop)]
|
|
normal = euclidean.getNormalWeighted(point, center, end)
|
|
if abs(normal) > 0.0:
|
|
if lastNormal != None:
|
|
if lastNormal.dot(normal) < 0.0:
|
|
addFacesByConcaveLoop(faces, indexedLoop)
|
|
return
|
|
lastNormal = normal
|
|
# totalNormal = Vector3()
|
|
# for pointIndex, point in enumerate(indexedLoop):
|
|
# center = indexedLoop[(pointIndex + 1) % len(indexedLoop)]
|
|
# end = indexedLoop[(pointIndex + 2) % len(indexedLoop)]
|
|
# totalNormal += euclidean.getNormalWeighted(point, center, end)
|
|
# totalNormal.normalize()
|
|
addFacesByConvex(faces, indexedLoop)
|
|
|
|
def addFacesByLoopReversed(faces, indexedLoop):
|
|
'Add faces from a reversed convex polygon.'
|
|
addFacesByLoop(faces, indexedLoop[: : -1])
|
|
|
|
def addFacesByMeldedConvexLoops(faces, indexedLoops):
|
|
'Add faces from melded loops.'
|
|
if len(indexedLoops) < 2:
|
|
return
|
|
for indexedLoopsIndex in xrange(len(indexedLoops) - 2):
|
|
FaceGenerator(faces, indexedLoops[indexedLoopsIndex], indexedLoops[indexedLoopsIndex + 1])
|
|
indexedLoopBottom = indexedLoops[-2]
|
|
indexedLoopTop = indexedLoops[-1]
|
|
if len(indexedLoopTop) < 1:
|
|
indexedLoopTop = indexedLoops[0]
|
|
FaceGenerator(faces, indexedLoopBottom, indexedLoopTop)
|
|
|
|
def addLoopToPointTable(loop, pointTable):
|
|
'Add the points in the loop to the point table.'
|
|
for point in loop:
|
|
pointTable[point] = None
|
|
|
|
def addMeldedPillarByLoops(faces, indexedLoops):
|
|
'Add melded pillar by loops which may be concave.'
|
|
if len(indexedLoops) < 1:
|
|
return
|
|
if len(indexedLoops[-1]) < 1:
|
|
addFacesByMeldedConvexLoops(faces, indexedLoops)
|
|
return
|
|
addFacesByLoopReversed(faces, indexedLoops[0])
|
|
addFacesByMeldedConvexLoops(faces, indexedLoops)
|
|
addFacesByLoop(faces, indexedLoops[-1])
|
|
|
|
def addPillarByLoops(faces, indexedLoops):
|
|
'Add pillar by loops which may be concave.'
|
|
if len(indexedLoops) < 1:
|
|
return
|
|
if len(indexedLoops[-1]) < 1:
|
|
addFacesByConvexLoops(faces, indexedLoops)
|
|
return
|
|
addFacesByLoopReversed(faces, indexedLoops[0])
|
|
addFacesByConvexLoops(faces, indexedLoops)
|
|
addFacesByLoop(faces, indexedLoops[-1])
|
|
|
|
def addPillarFromConvexLoopsGrids(faces, indexedGrids, indexedLoops):
|
|
'Add pillar from convex loops and grids.'
|
|
cellBottomLoops = getIndexedCellLoopsFromIndexedGrid(indexedGrids[0])
|
|
for cellBottomLoop in cellBottomLoops:
|
|
addFacesByConvexReversed(faces, cellBottomLoop)
|
|
addFacesByConvexLoops(faces, indexedLoops)
|
|
addFacesByGrid(faces, indexedGrids[-1])
|
|
|
|
def addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops):
|
|
'Add pillar from convex loops and grid top.'
|
|
addFacesByLoopReversed(faces, indexedLoops[0])
|
|
addFacesByConvexLoops(faces, indexedLoops)
|
|
addFacesByGrid(faces, indexedGridTop)
|
|
|
|
def addPointsAtZ(edgePair, points, radius, vertexes, z):
|
|
'Add point complexes on the segment between the edge intersections with z.'
|
|
carveIntersectionFirst = getCarveIntersectionFromEdge(edgePair.edges[0], vertexes, z)
|
|
carveIntersectionSecond = getCarveIntersectionFromEdge(edgePair.edges[1], vertexes, z)
|
|
# threshold radius above 0.8 can create extra holes on Screw Holder, 0.7 should be safe for everything
|
|
intercircle.addPointsFromSegment(carveIntersectionFirst, carveIntersectionSecond, points, radius, 0.7)
|
|
|
|
def addSymmetricXPath(outputs, path, x):
|
|
'Add x path output to outputs.'
|
|
vertexes = []
|
|
loops = [getSymmetricXLoop(path, vertexes, -x), getSymmetricXLoop(path, vertexes, x)]
|
|
outputs.append(getPillarOutput(loops))
|
|
|
|
def addSymmetricXPaths(outputs, paths, x):
|
|
'Add x paths outputs to outputs.'
|
|
for path in paths:
|
|
addSymmetricXPath(outputs, path, x)
|
|
|
|
def addSymmetricYPath(outputs, path, y):
|
|
'Add y path output to outputs.'
|
|
vertexes = []
|
|
loops = [getSymmetricYLoop(path, vertexes, -y), getSymmetricYLoop(path, vertexes, y)]
|
|
outputs.append(getPillarOutput(loops))
|
|
|
|
def addSymmetricYPaths(outputs, paths, y):
|
|
'Add y paths outputs to outputs.'
|
|
for path in paths:
|
|
addSymmetricYPath(outputs, path, y)
|
|
|
|
def addVector3Loop(loop, loops, vertexes, z):
|
|
'Add vector3Loop to loops if there is something in it, for inset and outset.'
|
|
vector3Loop = []
|
|
for point in loop:
|
|
vector3Index = Vector3Index(len(vertexes), point.real, point.imag, z)
|
|
vector3Loop.append(vector3Index)
|
|
vertexes.append(vector3Index)
|
|
if len(vector3Loop) > 0:
|
|
loops.append(vector3Loop)
|
|
|
|
def addWithLeastLength(importRadius, loops, point):
|
|
'Insert a point into a loop, at the index at which the loop would be shortest.'
|
|
close = 1.65 * importRadius # a bit over the experimental minimum additional loop length to restore a right angle
|
|
shortestAdditionalLength = close
|
|
shortestLoop = None
|
|
shortestPointIndex = None
|
|
for loop in loops:
|
|
if len(loop) > 3:
|
|
for pointIndex in xrange(len(loop)):
|
|
additionalLoopLength = getAdditionalLoopLength(loop, point, pointIndex)
|
|
if additionalLoopLength < shortestAdditionalLength:
|
|
if getIsPointCloseInline(close, loop, point, pointIndex):
|
|
shortestAdditionalLength = additionalLoopLength
|
|
shortestLoop = loop
|
|
shortestPointIndex = pointIndex
|
|
if shortestPointIndex != None:
|
|
shortestLoop.insert( shortestPointIndex, point )
|
|
|
|
def convertElementNode(elementNode, geometryOutput):
|
|
'Convert the xml element to a TriangleMesh xml element.'
|
|
elementNode.linkObject(TriangleMesh())
|
|
matrix.getBranchMatrixSetElementNode(elementNode)
|
|
vertex.addGeometryList(elementNode, geometryOutput['vertex'])
|
|
face.addGeometryList(elementNode, geometryOutput['face'])
|
|
elementNode.getXMLProcessor().processChildNodes(elementNode)
|
|
|
|
def getAddIndexedGrid( grid, vertexes, z ):
|
|
'Get and add an indexed grid.'
|
|
indexedGrid = []
|
|
for row in grid:
|
|
indexedRow = []
|
|
indexedGrid.append( indexedRow )
|
|
for pointComplex in row:
|
|
vector3index = Vector3Index( len(vertexes), pointComplex.real, pointComplex.imag, z )
|
|
indexedRow.append(vector3index)
|
|
vertexes.append(vector3index)
|
|
return indexedGrid
|
|
|
|
def getAddIndexedLoop(loop, vertexes, z):
|
|
'Get and add an indexed loop.'
|
|
indexedLoop = []
|
|
for index in xrange(len(loop)):
|
|
pointComplex = loop[index]
|
|
vector3index = Vector3Index(len(vertexes), pointComplex.real, pointComplex.imag, z)
|
|
indexedLoop.append(vector3index)
|
|
vertexes.append(vector3index)
|
|
return indexedLoop
|
|
|
|
def getAddIndexedLoops( loop, vertexes, zList ):
|
|
'Get and add indexed loops.'
|
|
indexedLoops = []
|
|
for z in zList:
|
|
indexedLoop = getAddIndexedLoop( loop, vertexes, z )
|
|
indexedLoops.append(indexedLoop)
|
|
return indexedLoops
|
|
|
|
def getAdditionalLoopLength(loop, point, pointIndex):
|
|
'Get the additional length added by inserting a point into a loop.'
|
|
afterPoint = loop[pointIndex]
|
|
beforePoint = loop[(pointIndex + len(loop) - 1) % len(loop)]
|
|
return abs(point - beforePoint) + abs(point - afterPoint) - abs(afterPoint - beforePoint)
|
|
|
|
def getCarveIntersectionFromEdge(edge, vertexes, z):
|
|
'Get the complex where the carve intersects the edge.'
|
|
firstVertex = vertexes[ edge.vertexIndexes[0] ]
|
|
firstVertexComplex = firstVertex.dropAxis()
|
|
secondVertex = vertexes[ edge.vertexIndexes[1] ]
|
|
secondVertexComplex = secondVertex.dropAxis()
|
|
zMinusFirst = z - firstVertex.z
|
|
up = secondVertex.z - firstVertex.z
|
|
return zMinusFirst * ( secondVertexComplex - firstVertexComplex ) / up + firstVertexComplex
|
|
|
|
def getClosestDistanceIndexToPoint(point, loop):
|
|
'Get the distance squared to the closest point of the loop and index of that point.'
|
|
smallestDistance = 987654321987654321.0
|
|
closestDistanceIndex = None
|
|
pointComplex = point.dropAxis()
|
|
for otherPointIndex, otherPoint in enumerate(loop):
|
|
distance = abs(pointComplex - otherPoint.dropAxis())
|
|
if distance < smallestDistance:
|
|
smallestDistance = distance
|
|
closestDistanceIndex = euclidean.DistanceIndex(distance, otherPointIndex)
|
|
return closestDistanceIndex
|
|
|
|
def getDescendingAreaLoops(allPoints, corners, importRadius):
|
|
'Get descending area loops which include most of the points.'
|
|
loops = intercircle.getCentersFromPoints(allPoints, importRadius)
|
|
descendingAreaLoops = []
|
|
sortLoopsInOrderOfArea(True, loops)
|
|
pointDictionary = {}
|
|
for loop in loops:
|
|
if len(loop) > 2 and getOverlapRatio(loop, pointDictionary) < 0.3:
|
|
intercircle.directLoop(not euclidean.getIsInFilledRegion(descendingAreaLoops, loop[0]), loop)
|
|
descendingAreaLoops.append(loop)
|
|
addLoopToPointTable(loop, pointDictionary)
|
|
descendingAreaLoops = euclidean.getSimplifiedLoops(descendingAreaLoops, importRadius)
|
|
return getLoopsWithCorners(corners, importRadius, descendingAreaLoops, pointDictionary)
|
|
|
|
def getDescendingAreaOrientedLoops(allPoints, corners, importRadius):
|
|
'Get descending area oriented loops which include most of the points.'
|
|
return getOrientedLoops(getDescendingAreaLoops(allPoints, corners, importRadius))
|
|
|
|
def getGeometryOutputByFacesVertexes(faces, vertexes):
|
|
'Get geometry output dictionary by faces and vertexes.'
|
|
return {'trianglemesh' : {'vertex' : vertexes, 'face' : faces}}
|
|
|
|
def getGeometryOutputCopy(object):
|
|
'Get the geometry output copy.'
|
|
objectClass = object.__class__
|
|
if objectClass == dict:
|
|
objectCopy = {}
|
|
for key in object:
|
|
objectCopy[key] = getGeometryOutputCopy(object[key])
|
|
return objectCopy
|
|
if objectClass == list:
|
|
objectCopy = []
|
|
for value in object:
|
|
objectCopy.append(getGeometryOutputCopy(value))
|
|
return objectCopy
|
|
if objectClass == face.Face or objectClass == Vector3 or objectClass == Vector3Index:
|
|
return object.copy()
|
|
return object
|
|
|
|
def getIndexedCellLoopsFromIndexedGrid( grid ):
|
|
'Get indexed cell loops from an indexed grid.'
|
|
indexedCellLoops = []
|
|
for rowIndex in xrange( len( grid ) - 1 ):
|
|
rowBottom = grid[ rowIndex ]
|
|
rowTop = grid[ rowIndex + 1 ]
|
|
for columnIndex in xrange( len( rowBottom ) - 1 ):
|
|
columnIndexEnd = columnIndex + 1
|
|
indexedConvex = []
|
|
indexedConvex.append( rowBottom[ columnIndex ] )
|
|
indexedConvex.append( rowBottom[ columnIndex + 1 ] )
|
|
indexedConvex.append( rowTop[ columnIndex + 1 ] )
|
|
indexedConvex.append( rowTop[ columnIndex ] )
|
|
indexedCellLoops.append( indexedConvex )
|
|
return indexedCellLoops
|
|
|
|
def getIndexedLoopFromIndexedGrid( indexedGrid ):
|
|
'Get indexed loop from around the indexed grid.'
|
|
indexedLoop = indexedGrid[0][:]
|
|
for row in indexedGrid[1 : -1]:
|
|
indexedLoop.append( row[-1] )
|
|
indexedLoop += indexedGrid[-1][: : -1]
|
|
for row in indexedGrid[ len( indexedGrid ) - 2 : 0 : - 1 ]:
|
|
indexedLoop.append( row[0] )
|
|
return indexedLoop
|
|
|
|
def getInfillDictionary(arounds, aroundWidth, infillInset, infillWidth, pixelTable, rotatedLoops, testLoops=None):
|
|
'Get combined fill loops which include most of the points.'
|
|
slightlyGreaterThanInfillInset = intercircle.globalIntercircleMultiplier * infillInset
|
|
allPoints = intercircle.getPointsFromLoops(rotatedLoops, infillInset, 0.7)
|
|
centers = intercircle.getCentersFromPoints(allPoints, slightlyGreaterThanInfillInset)
|
|
infillDictionary = {}
|
|
for center in centers:
|
|
insetCenter = intercircle.getSimplifiedInsetFromClockwiseLoop(center, infillInset)
|
|
insetPoint = insetCenter[0]
|
|
if len(insetCenter) > 2 and intercircle.getIsLarge(insetCenter, infillInset) and euclidean.getIsInFilledRegion(rotatedLoops, insetPoint):
|
|
around = euclidean.getSimplifiedLoop(center, infillInset)
|
|
euclidean.addLoopToPixelTable(around, pixelTable, aroundWidth)
|
|
arounds.append(around)
|
|
insetLoop = intercircle.getSimplifiedInsetFromClockwiseLoop(center, infillInset)
|
|
euclidean.addXIntersectionsFromLoopForTable(insetLoop, infillDictionary, infillWidth)
|
|
if testLoops != None:
|
|
testLoops.append(insetLoop)
|
|
return infillDictionary
|
|
|
|
def getInsetPoint( loop, tinyRadius ):
|
|
'Get the inset vertex.'
|
|
pointIndex = getWideAnglePointIndex(loop)
|
|
point = loop[ pointIndex % len(loop) ]
|
|
afterPoint = loop[(pointIndex + 1) % len(loop)]
|
|
beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ]
|
|
afterSegmentNormalized = euclidean.getNormalized( afterPoint - point )
|
|
beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point )
|
|
afterClockwise = complex( afterSegmentNormalized.imag, - afterSegmentNormalized.real )
|
|
beforeWiddershins = complex( - beforeSegmentNormalized.imag, beforeSegmentNormalized.real )
|
|
midpoint = afterClockwise + beforeWiddershins
|
|
midpointNormalized = midpoint / abs( midpoint )
|
|
return point + midpointNormalized * tinyRadius
|
|
|
|
def getIsPathEntirelyOutsideTriangle(begin, center, end, vector3Path):
|
|
'Determine if a path is entirely outside another loop.'
|
|
loop = [begin.dropAxis(), center.dropAxis(), end.dropAxis()]
|
|
for vector3 in vector3Path:
|
|
point = vector3.dropAxis()
|
|
if euclidean.isPointInsideLoop(loop, point):
|
|
return False
|
|
return True
|
|
|
|
def getIsPointCloseInline(close, loop, point, pointIndex):
|
|
'Insert a point into a loop, at the index at which the loop would be shortest.'
|
|
afterCenterComplex = loop[pointIndex]
|
|
if abs(afterCenterComplex - point) > close:
|
|
return False
|
|
afterEndComplex = loop[(pointIndex + 1) % len(loop)]
|
|
if not isInline( point, afterCenterComplex, afterEndComplex ):
|
|
return False
|
|
beforeCenterComplex = loop[(pointIndex + len(loop) - 1) % len(loop)]
|
|
if abs(beforeCenterComplex - point) > close:
|
|
return False
|
|
beforeEndComplex = loop[(pointIndex + len(loop) - 2) % len(loop)]
|
|
return isInline(point, beforeCenterComplex, beforeEndComplex)
|
|
|
|
def getLoopsFromCorrectMesh( edges, faces, vertexes, z ):
|
|
'Get loops from a carve of a correct mesh.'
|
|
remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z)
|
|
remainingValues = remainingEdgeTable.values()
|
|
for edge in remainingValues:
|
|
if len( edge.faceIndexes ) < 2:
|
|
print('This should never happen, there is a hole in the triangle mesh, each edge should have two faces.')
|
|
print(edge)
|
|
print('Something will still be printed, but there is no guarantee that it will be the correct shape.' )
|
|
print('Once the gcode is saved, you should check over the layer with a z of:')
|
|
print(z)
|
|
return []
|
|
loops = []
|
|
while isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ):
|
|
pass
|
|
if euclidean.isLoopListIntersecting(loops):
|
|
print('Warning, the triangle mesh slice intersects itself in getLoopsFromCorrectMesh in triangle_mesh.')
|
|
print('Something will still be printed, but there is no guarantee that it will be the correct shape.')
|
|
print('Once the gcode is saved, you should check over the layer with a z of:')
|
|
print(z)
|
|
return []
|
|
return loops
|
|
# untouchables = []
|
|
# for boundingLoop in boundingLoops:
|
|
# if not boundingLoop.isIntersectingList( untouchables ):
|
|
# untouchables.append( boundingLoop )
|
|
# if len( untouchables ) < len( boundingLoops ):
|
|
# print('This should never happen, the carve layer intersects itself. Something will still be printed, but there is no guarantee that it will be the correct shape.')
|
|
# print('Once the gcode is saved, you should check over the layer with a z of:')
|
|
# print(z)
|
|
# remainingLoops = []
|
|
# for untouchable in untouchables:
|
|
# remainingLoops.append( untouchable.loop )
|
|
# return remainingLoops
|
|
|
|
def getLoopsFromUnprovenMesh(edges, faces, importRadius, vertexes, z):
|
|
'Get loops from a carve of an unproven mesh.'
|
|
edgePairTable = {}
|
|
corners = []
|
|
remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z)
|
|
remainingEdgeTableKeys = remainingEdgeTable.keys()
|
|
for remainingEdgeIndexKey in remainingEdgeTable:
|
|
edge = remainingEdgeTable[remainingEdgeIndexKey]
|
|
carveIntersection = getCarveIntersectionFromEdge(edge, vertexes, z)
|
|
corners.append(carveIntersection)
|
|
for edgeFaceIndex in edge.faceIndexes:
|
|
face = faces[edgeFaceIndex]
|
|
for edgeIndex in face.edgeIndexes:
|
|
addEdgePair(edgePairTable, edges, edgeIndex, remainingEdgeIndexKey, remainingEdgeTable)
|
|
allPoints = corners[:]
|
|
for edgePairValue in edgePairTable.values():
|
|
addPointsAtZ(edgePairValue, allPoints, importRadius, vertexes, z)
|
|
pointTable = {}
|
|
return getDescendingAreaLoops(allPoints, corners, importRadius)
|
|
|
|
def getLoopLayerAppend(loopLayers, layerCount, z):
|
|
'Get next z and add extruder loops.'
|
|
settings.printProgressByNumber(len(loopLayers), layerCount, 'slice')
|
|
loopLayer = euclidean.LoopLayer(z)
|
|
loopLayers.append(loopLayer)
|
|
return loopLayer
|
|
|
|
def getLoopsWithCorners(corners, importRadius, loops, pointTable):
|
|
'Add corners to the loops.'
|
|
for corner in corners:
|
|
if corner not in pointTable:
|
|
addWithLeastLength(importRadius, loops, corner)
|
|
pointTable[corner] = None
|
|
return euclidean.getSimplifiedLoops(loops, importRadius)
|
|
|
|
def getMeldedPillarOutput(loops):
|
|
'Get melded pillar output.'
|
|
faces = []
|
|
vertexes = getUniqueVertexes(loops)
|
|
addMeldedPillarByLoops(faces, loops)
|
|
return getGeometryOutputByFacesVertexes(faces, vertexes)
|
|
|
|
def getNewDerivation(elementNode):
|
|
'Get new derivation.'
|
|
return evaluate.EmptyObject(elementNode)
|
|
|
|
def getNextEdgeIndexAroundZ(edge, faces, remainingEdgeTable):
|
|
'Get the next edge index in the mesh carve.'
|
|
for faceIndex in edge.faceIndexes:
|
|
face = faces[faceIndex]
|
|
for edgeIndex in face.edgeIndexes:
|
|
if edgeIndex in remainingEdgeTable:
|
|
return edgeIndex
|
|
return -1
|
|
|
|
def getOrientedLoops(loops):
|
|
'Orient the loops which must be in descending order.'
|
|
for loopIndex, loop in enumerate(loops):
|
|
leftPoint = euclidean.getLeftPoint(loop)
|
|
isInFilledRegion = euclidean.getIsInFilledRegion(loops[: loopIndex] + loops[loopIndex + 1 :], leftPoint)
|
|
if isInFilledRegion == euclidean.isWiddershins(loop):
|
|
loop.reverse()
|
|
return loops
|
|
|
|
def getOverlapRatio( loop, pointTable ):
|
|
'Get the overlap ratio between the loop and the point table.'
|
|
numberOfOverlaps = 0
|
|
for point in loop:
|
|
if point in pointTable:
|
|
numberOfOverlaps += 1
|
|
return float( numberOfOverlaps ) / float(len(loop))
|
|
|
|
def getPath( edges, pathIndexes, loop, z ):
|
|
'Get the path from the edge intersections.'
|
|
path = []
|
|
for pathIndexIndex in xrange( len( pathIndexes ) ):
|
|
pathIndex = pathIndexes[ pathIndexIndex ]
|
|
edge = edges[ pathIndex ]
|
|
carveIntersection = getCarveIntersectionFromEdge( edge, loop, z )
|
|
path.append( carveIntersection )
|
|
return path
|
|
|
|
def getPillarOutput(loops):
|
|
'Get pillar output.'
|
|
faces = []
|
|
vertexes = getUniqueVertexes(loops)
|
|
addPillarByLoops(faces, loops)
|
|
return getGeometryOutputByFacesVertexes(faces, vertexes)
|
|
|
|
def getPillarsOutput(loopLists):
|
|
'Get pillars output.'
|
|
pillarsOutput = []
|
|
for loopList in loopLists:
|
|
pillarsOutput.append(getPillarOutput(loopList))
|
|
return getUnifiedOutput(pillarsOutput)
|
|
|
|
def getRemainingEdgeTable(edges, vertexes, z):
|
|
'Get the remaining edge hashtable.'
|
|
remainingEdgeTable = {}
|
|
if len(edges) > 0:
|
|
if edges[0].zMinimum == None:
|
|
for edge in edges:
|
|
setEdgeMaximumMinimum(edge, vertexes)
|
|
for edgeIndex in xrange(len(edges)):
|
|
edge = edges[edgeIndex]
|
|
if (edge.zMinimum < z) and (edge.zMaximum > z):
|
|
remainingEdgeTable[edgeIndex] = edge
|
|
return remainingEdgeTable
|
|
|
|
def getRemainingLoopAddFace(faces, remainingLoop):
|
|
'Get the remaining loop and add face.'
|
|
for indexedVertexIndex, indexedVertex in enumerate(remainingLoop):
|
|
nextIndex = (indexedVertexIndex + 1) % len(remainingLoop)
|
|
previousIndex = (indexedVertexIndex + len(remainingLoop) - 1) % len(remainingLoop)
|
|
nextVertex = remainingLoop[nextIndex]
|
|
previousVertex = remainingLoop[previousIndex]
|
|
remainingPath = euclidean.getAroundLoop((indexedVertexIndex + 2) % len(remainingLoop), previousIndex, remainingLoop)
|
|
if len(remainingLoop) < 4 or getIsPathEntirelyOutsideTriangle(previousVertex, indexedVertex, nextVertex, remainingPath):
|
|
faceConvex = face.Face()
|
|
faceConvex.index = len(faces)
|
|
faceConvex.vertexIndexes.append(indexedVertex.index)
|
|
faceConvex.vertexIndexes.append(nextVertex.index)
|
|
faceConvex.vertexIndexes.append(previousVertex.index)
|
|
faces.append(faceConvex)
|
|
return euclidean.getAroundLoop(nextIndex, indexedVertexIndex, remainingLoop)
|
|
print('Warning, could not decompose polygon in getRemainingLoopAddFace in trianglemesh for:')
|
|
print(remainingLoop)
|
|
return []
|
|
|
|
def getSharedFace( firstEdge, faces, secondEdge ):
|
|
'Get the face which is shared by two edges.'
|
|
for firstEdgeFaceIndex in firstEdge.faceIndexes:
|
|
for secondEdgeFaceIndex in secondEdge.faceIndexes:
|
|
if firstEdgeFaceIndex == secondEdgeFaceIndex:
|
|
return faces[ firstEdgeFaceIndex ]
|
|
return None
|
|
|
|
def getSymmetricXLoop(path, vertexes, x):
|
|
'Get symmetrix x loop.'
|
|
loop = []
|
|
for point in path:
|
|
vector3Index = Vector3Index(len(vertexes), x, point.real, point.imag)
|
|
loop.append(vector3Index)
|
|
vertexes.append(vector3Index)
|
|
return loop
|
|
|
|
def getSymmetricYLoop(path, vertexes, y):
|
|
'Get symmetrix y loop.'
|
|
loop = []
|
|
for point in path:
|
|
vector3Index = Vector3Index(len(vertexes), point.real, y, point.imag)
|
|
loop.append(vector3Index)
|
|
vertexes.append(vector3Index)
|
|
return loop
|
|
|
|
def getUnifiedOutput(outputs):
|
|
'Get unified output.'
|
|
if len(outputs) < 1:
|
|
return {}
|
|
if len(outputs) == 1:
|
|
return outputs[0]
|
|
return {'union' : {'shapes' : outputs}}
|
|
|
|
def getUniqueVertexes(loops):
|
|
'Get unique vertexes.'
|
|
vertexDictionary = {}
|
|
uniqueVertexes = []
|
|
for loop in loops:
|
|
for vertexIndex, vertex in enumerate(loop):
|
|
vertexTuple = (vertex.x, vertex.y, vertex.z)
|
|
if vertexTuple in vertexDictionary:
|
|
loop[vertexIndex] = vertexDictionary[vertexTuple]
|
|
else:
|
|
if vertex.__class__ == Vector3Index:
|
|
loop[vertexIndex].index = len(vertexDictionary)
|
|
else:
|
|
loop[vertexIndex] = Vector3Index(len(vertexDictionary), vertex.x, vertex.y, vertex.z)
|
|
vertexDictionary[vertexTuple] = loop[vertexIndex]
|
|
uniqueVertexes.append(loop[vertexIndex])
|
|
return uniqueVertexes
|
|
|
|
def getWideAnglePointIndex(loop):
|
|
'Get a point index which has a wide enough angle, most point indexes have a wide enough angle, this is just to make sure.'
|
|
dotProductMinimum = 9999999.9
|
|
widestPointIndex = 0
|
|
for pointIndex in xrange(len(loop)):
|
|
point = loop[ pointIndex % len(loop) ]
|
|
afterPoint = loop[(pointIndex + 1) % len(loop)]
|
|
beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ]
|
|
afterSegmentNormalized = euclidean.getNormalized( afterPoint - point )
|
|
beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point )
|
|
dotProduct = euclidean.getDotProduct( afterSegmentNormalized, beforeSegmentNormalized )
|
|
if dotProduct < .99:
|
|
return pointIndex
|
|
if dotProduct < dotProductMinimum:
|
|
dotProductMinimum = dotProduct
|
|
widestPointIndex = pointIndex
|
|
return widestPointIndex
|
|
|
|
def isInline( beginComplex, centerComplex, endComplex ):
|
|
'Determine if the three complex points form a line.'
|
|
centerBeginComplex = beginComplex - centerComplex
|
|
centerEndComplex = endComplex - centerComplex
|
|
centerBeginLength = abs( centerBeginComplex )
|
|
centerEndLength = abs( centerEndComplex )
|
|
if centerBeginLength <= 0.0 or centerEndLength <= 0.0:
|
|
return False
|
|
centerBeginComplex /= centerBeginLength
|
|
centerEndComplex /= centerEndLength
|
|
return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) < -0.999
|
|
|
|
def isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ):
|
|
'Get the path indexes around a triangle mesh carve and add the path to the flat loops.'
|
|
if len( remainingEdgeTable ) < 1:
|
|
return False
|
|
pathIndexes = []
|
|
remainingEdgeIndexKey = remainingEdgeTable.keys()[0]
|
|
pathIndexes.append( remainingEdgeIndexKey )
|
|
del remainingEdgeTable[remainingEdgeIndexKey]
|
|
nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[remainingEdgeIndexKey], faces, remainingEdgeTable )
|
|
while nextEdgeIndexAroundZ != - 1:
|
|
pathIndexes.append( nextEdgeIndexAroundZ )
|
|
del remainingEdgeTable[ nextEdgeIndexAroundZ ]
|
|
nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[ nextEdgeIndexAroundZ ], faces, remainingEdgeTable )
|
|
if len( pathIndexes ) < 3:
|
|
print('Dangling edges, will use intersecting circles to get import layer at height %s' % z)
|
|
del loops[:]
|
|
return False
|
|
loops.append( getPath( edges, pathIndexes, vertexes, z ) )
|
|
return True
|
|
|
|
def processElementNode(elementNode):
|
|
'Process the xml element.'
|
|
evaluate.processArchivable(TriangleMesh, elementNode)
|
|
|
|
def setEdgeMaximumMinimum(edge, vertexes):
|
|
'Set the edge maximum and minimum.'
|
|
beginIndex = edge.vertexIndexes[0]
|
|
endIndex = edge.vertexIndexes[1]
|
|
if beginIndex >= len(vertexes) or endIndex >= len(vertexes):
|
|
print('Warning, there are duplicate vertexes in setEdgeMaximumMinimum in triangle_mesh.')
|
|
print('Something might still be printed, but there is no guarantee that it will be the correct shape.' )
|
|
edge.zMaximum = -987654321.0
|
|
edge.zMinimum = -987654321.0
|
|
return
|
|
beginZ = vertexes[beginIndex].z
|
|
endZ = vertexes[endIndex].z
|
|
edge.zMinimum = min(beginZ, endZ)
|
|
edge.zMaximum = max(beginZ, endZ)
|
|
|
|
def sortLoopsInOrderOfArea(isDescending, loops):
|
|
'Sort the loops in the order of area according isDescending.'
|
|
loops.sort(key=euclidean.getAreaLoopAbsolute, reverse=isDescending)
|
|
|
|
|
|
class EdgePair:
|
|
def __init__(self):
|
|
'Pair of edges on a face.'
|
|
self.edgeIndexes = []
|
|
self.edges = []
|
|
|
|
def __repr__(self):
|
|
'Get the string representation of this EdgePair.'
|
|
return str( self.edgeIndexes )
|
|
|
|
def getFromIndexesEdges( self, edgeIndexes, edges ):
|
|
'Initialize from edge indices.'
|
|
self.edgeIndexes = edgeIndexes[:]
|
|
self.edgeIndexes.sort()
|
|
for edgeIndex in self.edgeIndexes:
|
|
self.edges.append( edges[ edgeIndex ] )
|
|
return self
|
|
|
|
|
|
class FaceGenerator:
|
|
'A face generator.'
|
|
def __init__(self, faces, indexedLoopBottom, indexedLoopTop):
|
|
'Initialize.'
|
|
self.startTop = 0
|
|
if len(indexedLoopBottom) == 0 or len(indexedLoopTop) == 0:
|
|
return
|
|
smallestDistance = 987654321987654321.0
|
|
for pointIndex, point in enumerate(indexedLoopBottom):
|
|
distanceIndex = getClosestDistanceIndexToPoint(point, indexedLoopTop)
|
|
if distanceIndex.distance < smallestDistance:
|
|
smallestDistance = distanceIndex.distance
|
|
offsetBottom = pointIndex
|
|
offsetTop = distanceIndex.index
|
|
self.indexedLoopBottom = indexedLoopBottom[offsetBottom :] + indexedLoopBottom[: offsetBottom]
|
|
self.indexedLoopTop = indexedLoopTop[offsetTop :] + indexedLoopTop[: offsetTop]
|
|
for bottomIndex in xrange(len(self.indexedLoopBottom)):
|
|
self.addFacesByBottomIndex(bottomIndex, faces)
|
|
subsetTop = self.indexedLoopTop[self.startTop :]
|
|
subsetTop.append(self.indexedLoopTop[0])
|
|
addFacesByConvexBottomTopLoop(faces, [self.indexedLoopBottom[0]], subsetTop[: : -1])
|
|
|
|
def addFacesByBottomIndex(self, bottomIndex, faces):
|
|
'Add faces from the bottom index to the next index.'
|
|
bottomPoint = self.indexedLoopBottom[bottomIndex % len(self.indexedLoopBottom)]
|
|
bottomPointNext = self.indexedLoopBottom[(bottomIndex + 1) % len(self.indexedLoopBottom)]
|
|
topIndex = self.startTop + getClosestDistanceIndexToPoint(bottomPointNext, self.indexedLoopTop[self.startTop :]).index
|
|
topIndexPlusOne = topIndex + 1
|
|
betweenIndex = self.getBetweenIndex(bottomPoint, bottomPointNext, topIndexPlusOne)
|
|
betweenIndexPlusOne = betweenIndex + 1
|
|
subsetStart = self.indexedLoopTop[self.startTop : betweenIndexPlusOne]
|
|
subsetEnd = self.indexedLoopTop[betweenIndex : topIndexPlusOne]
|
|
addFacesByConvexBottomTopLoop(faces, [bottomPoint], subsetStart[: : -1])
|
|
addFacesByConvexBottomTopLoop(faces, [bottomPoint, bottomPointNext], [self.indexedLoopTop[betweenIndex]])
|
|
addFacesByConvexBottomTopLoop(faces, [bottomPointNext], subsetEnd[: : -1])
|
|
self.startTop = topIndex
|
|
|
|
def getBetweenIndex(self, bottomPoint, bottomPointNext, topIndexPlusOne):
|
|
'Get the index of the last point along the loop which is closer to the bottomPoint.'
|
|
betweenIndex = self.startTop
|
|
bottomPointComplex = bottomPoint.dropAxis()
|
|
bottomPointNextComplex = bottomPointNext.dropAxis()
|
|
for topPointIndex in xrange(self.startTop, topIndexPlusOne):
|
|
topPointComplex = self.indexedLoopTop[topPointIndex].dropAxis()
|
|
if abs(topPointComplex - bottomPointComplex) > abs(topPointComplex - bottomPointNextComplex):
|
|
return betweenIndex
|
|
betweenIndex = topPointIndex
|
|
return betweenIndex
|
|
|
|
|
|
class TriangleMesh( group.Group ):
|
|
'A triangle mesh.'
|
|
def __init__(self):
|
|
'Add empty lists.'
|
|
group.Group.__init__(self)
|
|
self.belowLoops = []
|
|
self.edges = []
|
|
self.faces = []
|
|
self.importCoarseness = 1.0
|
|
self.isCorrectMesh = True
|
|
self.loopLayers = []
|
|
self.oldChainTetragrid = None
|
|
self.transformedVertexes = None
|
|
self.vertexes = []
|
|
|
|
def addXMLSection(self, depth, output):
|
|
'Add the xml section for this object.'
|
|
xml_simple_writer.addXMLFromVertexes( depth, output, self.vertexes )
|
|
xml_simple_writer.addXMLFromObjects( depth, self.faces, output )
|
|
|
|
def getCarveBoundaryLayers(self):
|
|
'Get the boundary layers.'
|
|
if self.getMinimumZ() == None:
|
|
return []
|
|
halfHeight = 0.5 * self.layerHeight
|
|
self.zoneArrangement = ZoneArrangement(self.layerHeight, self.getTransformedVertexes())
|
|
layerTop = self.cornerMaximum.z - halfHeight * 0.5
|
|
z = self.cornerMinimum.z + halfHeight
|
|
layerCount = int((layerTop - z) / self.layerHeight)
|
|
while z < layerTop:
|
|
getLoopLayerAppend(self.loopLayers, layerCount, z).loops = self.getLoopsFromMesh(self.zoneArrangement.getEmptyZ(z))
|
|
z += self.layerHeight
|
|
return self.loopLayers
|
|
|
|
def getCarveCornerMaximum(self):
|
|
'Get the corner maximum of the vertexes.'
|
|
return self.cornerMaximum
|
|
|
|
def getCarveCornerMinimum(self):
|
|
'Get the corner minimum of the vertexes.'
|
|
return self.cornerMinimum
|
|
|
|
def getCarveLayerHeight(self):
|
|
'Get the layer height.'
|
|
return self.layerHeight
|
|
|
|
def getFabmetheusXML(self):
|
|
'Return the fabmetheus XML.'
|
|
return None
|
|
|
|
def getGeometryOutput(self):
|
|
'Get geometry output dictionary.'
|
|
return getGeometryOutputByFacesVertexes(self.faces, self.vertexes)
|
|
|
|
def getInterpretationSuffix(self):
|
|
'Return the suffix for a triangle mesh.'
|
|
return 'xml'
|
|
|
|
def getLoops(self, importRadius, z):
|
|
'Get loops sliced through shape.'
|
|
self.importRadius = importRadius
|
|
return self.getLoopsFromMesh(z)
|
|
|
|
def getLoopsFromMesh( self, z ):
|
|
'Get loops from a carve of a mesh.'
|
|
originalLoops = []
|
|
self.setEdgesForAllFaces()
|
|
if self.isCorrectMesh:
|
|
originalLoops = getLoopsFromCorrectMesh( self.edges, self.faces, self.getTransformedVertexes(), z )
|
|
if len( originalLoops ) < 1:
|
|
originalLoops = getLoopsFromUnprovenMesh( self.edges, self.faces, self.importRadius, self.getTransformedVertexes(), z )
|
|
loops = euclidean.getSimplifiedLoops(originalLoops, self.importRadius)
|
|
sortLoopsInOrderOfArea(True, loops)
|
|
return getOrientedLoops(loops)
|
|
|
|
def getMinimumZ(self):
|
|
'Get the minimum z.'
|
|
self.cornerMaximum = Vector3(-987654321.0, -987654321.0, -987654321.0)
|
|
self.cornerMinimum = Vector3(987654321.0, 987654321.0, 987654321.0)
|
|
transformedVertexes = self.getTransformedVertexes()
|
|
if len(transformedVertexes) < 1:
|
|
return None
|
|
for point in transformedVertexes:
|
|
self.cornerMaximum.maximize(point)
|
|
self.cornerMinimum.minimize(point)
|
|
return self.cornerMinimum.z
|
|
|
|
def getTransformedVertexes(self):
|
|
'Get all transformed vertexes.'
|
|
if self.elementNode == None:
|
|
return self.vertexes
|
|
chainTetragrid = self.getMatrixChainTetragrid()
|
|
if self.oldChainTetragrid != chainTetragrid:
|
|
self.oldChainTetragrid = matrix.getTetragridCopy(chainTetragrid)
|
|
self.transformedVertexes = None
|
|
if self.transformedVertexes == None:
|
|
if len(self.edges) > 0:
|
|
self.edges[0].zMinimum = None
|
|
self.transformedVertexes = matrix.getTransformedVector3s(chainTetragrid, self.vertexes)
|
|
return self.transformedVertexes
|
|
|
|
def getTriangleMeshes(self):
|
|
'Get all triangleMeshes.'
|
|
return [self]
|
|
|
|
def getVertexes(self):
|
|
'Get all vertexes.'
|
|
self.transformedVertexes = None
|
|
return self.vertexes
|
|
|
|
def setCarveImportRadius( self, importRadius ):
|
|
'Set the import radius.'
|
|
self.importRadius = importRadius
|
|
|
|
def setCarveIsCorrectMesh( self, isCorrectMesh ):
|
|
'Set the is correct mesh flag.'
|
|
self.isCorrectMesh = isCorrectMesh
|
|
|
|
def setCarveLayerHeight( self, layerHeight ):
|
|
'Set the layer height.'
|
|
self.layerHeight = layerHeight
|
|
|
|
def setEdgesForAllFaces(self):
|
|
'Set the face edges of all the faces.'
|
|
edgeTable = {}
|
|
for face in self.faces:
|
|
face.setEdgeIndexesToVertexIndexes( self.edges, edgeTable )
|
|
|
|
|
|
class ZoneArrangement:
|
|
'A zone arrangement.'
|
|
def __init__(self, layerHeight, vertexes):
|
|
'Initialize the zone interval and the zZone table.'
|
|
self.zoneInterval = layerHeight / math.sqrt(len(vertexes)) / 1000.0
|
|
self.zZoneSet = set()
|
|
for point in vertexes:
|
|
zoneIndexFloat = point.z / self.zoneInterval
|
|
self.zZoneSet.add(math.floor(zoneIndexFloat))
|
|
self.zZoneSet.add(math.ceil(zoneIndexFloat ))
|
|
|
|
def getEmptyZ(self, z):
|
|
'Get the first z which is not in the zone table.'
|
|
zoneIndex = round(z / self.zoneInterval)
|
|
if zoneIndex not in self.zZoneSet:
|
|
return z
|
|
zoneAround = 1
|
|
while 1:
|
|
zoneDown = zoneIndex - zoneAround
|
|
if zoneDown not in self.zZoneSet:
|
|
return zoneDown * self.zoneInterval
|
|
zoneUp = zoneIndex + zoneAround
|
|
if zoneUp not in self.zZoneSet:
|
|
return zoneUp * self.zoneInterval
|
|
zoneAround += 1
|