"""Polyhedron Module"""
from .body import GeoBody
from .polygon import ConvexPolygon,Parallelogram,Circle,get_circle_point_list
from .point import Point
from .line import Line
from .segment import Segment
from .plane import Plane
from .pyramid import Pyramid
from ..utils.vector import Vector,x_unit_vector,y_unit_vector,z_unit_vector
from ..utils.constant import *
from ..utils.logger import get_main_logger
import copy
[docs]class ConvexPolyhedron(GeoBody):
class_level = 5 # the class level of ConvexPolyhedron
"""
**Input:**
- convex_polygons: tuple of ConvexPolygons
**Output:**
- ConvexPolyhedron
- The correctness of convex_polygons are checked According to Euler's formula.
- The normal of the convex polygons are checked and corrected which should be toward the outer direction
"""
[docs] @classmethod
def Parallelepiped(cls,base_point,v1,v2,v3):
"""
A special function for creating Parallelepiped
**Input:**
- base_point: a Point
- v1, v2, v3: three Vectors
**Output:**
- A parallelepiped which is a ConvexPolyhedron instance.
"""
if isinstance(base_point,Point) and isinstance(v1,Vector) and isinstance(v2,Vector) and isinstance(v3,Vector):
if v1.length() == 0 or v2.length == 0 or v3.length == 0:
raise ValueError("The length for the three vector shouldn't be zero")
elif v1.parallel(v2) or v1.parallel(v3) or v2.parallel(v3):
raise ValueError("The three vectors shouldn't be parallel to each other")
else:
p_diag = copy.deepcopy(base_point).move(v1).move(v2).move(v3)
rectangle0=Parallelogram(base_point,v1,v2)
rectangle1=Parallelogram(base_point,v2,v3)
rectangle2=Parallelogram(base_point,v1,v3)
rectangle3=Parallelogram(p_diag,-v1,-v2)
rectangle4=Parallelogram(p_diag,-v2,-v3)
rectangle5=Parallelogram(p_diag,-v1,-v3)
return cls((rectangle0,rectangle1,rectangle2,rectangle3,rectangle4,rectangle5))
else:
raise TypeError("Parallelepiped should be initialized with Point, Vector, Vector and Vector, but the given types are %s, %s, %s and %s" %(type(base_point),type(v1),type(v2),type(v3)))
[docs] @classmethod
def Sphere(cls,center,radius,n1=10,n2=3):
"""
A special function for creating the inscribed polyhedron of a sphere
**Input:**
- center: The center of the sphere
- radius: The radius of the sphere
- n1=10: The number of Points on a longitude circle
- n2=3: The number sections of a quater latitude circle
**Output:**
- An inscribed polyhedron of the given sphere.
"""
import copy
import math
cpg_list = []
mc = get_circle_point_list(center = center,normal = z_unit_vector(),radius = radius,n=n1) # medium circle
top_point = copy.deepcopy(center).move(radius * z_unit_vector())
bottom_point = copy.deepcopy(center).move(-radius * z_unit_vector())
# heights=[]
# radii = []
tc = [] # top circles
bc = [] # bottom circles
for i in range(n2-1):
angle_i = math.pi / 2 / n2 *(i+1)
height_i=radius * math.sin(angle_i)
r_i=radius * math.cos(angle_i)
tc.append(get_circle_point_list(
center = copy.deepcopy(center).move(height_i * z_unit_vector()),
normal = z_unit_vector(),
radius = r_i,
n=n1
))
bc.append(get_circle_point_list(
center = copy.deepcopy(center).move(-height_i * z_unit_vector()),
normal = z_unit_vector(),
radius = r_i,
n=n1
))
for i in range(n1):
start = i
end = (i + 1) % n1
cpg_list.append(ConvexPolygon((mc[start],mc[end],tc[0][end],tc[0][start])))
cpg_list.append(ConvexPolygon((mc[start],mc[end],bc[0][end],bc[0][start])))
for j in range(1,n2-1):
cpg_list.append(ConvexPolygon((tc[j-1][start],tc[j-1][end],tc[j][end],tc[j][start])))
cpg_list.append(ConvexPolygon((bc[j-1][start],bc[j-1][end],bc[j][end],bc[j][start])))
cpg_list.append(ConvexPolygon((top_point,tc[n2-2][end],tc[n2-2][start])))
cpg_list.append(ConvexPolygon((bottom_point,bc[n2-2][end],bc[n2-2][start])))
return cls(tuple(cpg_list))
# return cpg_list
[docs] @classmethod
def Cylinder(cls,circle_center,radius,height_vector,n=10):
"""
A special function for creating the inscribed polyhedron of a sphere
**Input:**
- circle_center: The center of the bottom circle
- radius: The radius of the bottom circle
- height_vector: The Vector from the bottom circle center to the top circle center
- n=10: The number of Points on the bottom circle
**Output:**
- An inscribed polyhedron of the given cylinder.
"""
import copy
top_point = copy.deepcopy(circle_center).move(height_vector)
# print(top_point)
bottom_circle = Circle(center=circle_center,normal=height_vector,radius=radius,n=n)
bottom_circle_point_list = get_circle_point_list(center=circle_center,normal=height_vector,radius=radius,n=n)
top_circle = Circle(center=top_point,normal=height_vector,radius=radius,n=n)
top_circle_point_list = get_circle_point_list(center=top_point,normal=height_vector,radius=radius,n=n)
cpg_list = [top_circle,bottom_circle]
for i in range(len(top_circle_point_list)):
start = i
end = (i + 1) % len(top_circle_point_list)
cpg_list.append(ConvexPolygon((top_circle_point_list[start],top_circle_point_list[end],bottom_circle_point_list[end],bottom_circle_point_list[start])))
return cls(tuple(cpg_list))
[docs] @classmethod
def Cone(cls,circle_center,radius,height_vector,n=10):
"""
A special function for creating the inscribed polyhedron of a sphere
**Input:**
- circle_center: The center of the bottom circle
- radius: The radius of the bottom circle
- height_vector: The Vector from the bottom circle center to the top circle center
- n=10: The number of Points on the bottom circle
**Output:**
- An inscribed polyhedron of the given cone.
"""
import copy
top_point = copy.deepcopy(circle_center).move(height_vector)
# print(top_point)
circle = Circle(center=circle_center,normal=height_vector,radius=radius,n=n)
circle_point_list = get_circle_point_list(center=circle_center,normal=height_vector,radius=radius,n=n)
cpg_list = [circle]
for i in range(len(circle_point_list)):
start = i
end = (i + 1) % len(circle_point_list)
cpg_list.append(ConvexPolygon((top_point,circle_point_list[start],circle_point_list[end])))
return cls(tuple(cpg_list))
def __init__(self,convex_polygons):
self.convex_polygons = list(copy.deepcopy(convex_polygons))
# self.convex_polygons = list(convex_polygons)
self.point_set = set()
self.segment_set = set()
self.pyramid_set = set()
for convex_polygon in self.convex_polygons:
for point in convex_polygon.points:
self.point_set.add(point)
for segment in convex_polygon.segments():
self.segment_set.add(segment)
self.center_point = self._get_center_point()
for i in range(len(self.convex_polygons)):
convex_polygon = self.convex_polygons[i]
if Vector(self.center_point,convex_polygon.plane.p) * convex_polygon.plane.n < -get_eps():
self.convex_polygons[i] = - convex_polygon
self.pyramid_set.add(Pyramid(convex_polygon,self.center_point,direct_call=False))
if not self._check_normal():
raise ValueError('Check Normal Fails For The Convex Polyhedron')
if not self._euler_check():
get_main_logger().critical('V:{} E:{} F:{}'.format(len(self.point_set),len(self.segment_set),len(self.convex_polygons)))
raise ValueError('Check for the number of vertices, faces and edges fails, the polyhedron may not be closed')
def _euler_check(self):
number_points = len(self.point_set)
number_segments = len(self.segment_set)
number_polygons = len(self.convex_polygons)
return number_points - number_segments + number_polygons == 2
def _check_normal(self):
"""return True if all the polygons' normals point to the outside"""
for convex_polygon in self.convex_polygons:
if Vector(self.center_point,convex_polygon.plane.p) * convex_polygon.plane.n < -get_eps():
return False
return True
def _get_center_point(self):
"""
**Input:**
- self
**Output:**
- The center point of this point set
"""
x,y,z = 0,0,0
num_points = len(self.point_set)
for point in self.point_set:
x += point.x
y += point.y
z += point.z
return Point(x / num_points,y / num_points, z / num_points)
def __repr__(self):
return "ConvexPolyhedron({})".format(self.point_set)
def __contains__(self,other):
"""
**Input:**
- point: a Object
**Output:**
- Whether the polyhedron contains the point
"""
if isinstance(other,Point):
for polygon in self.convex_polygons:
direction_vector = Vector(polygon.center_point,other)
if direction_vector * polygon.plane.n > get_eps():
return False
return True
elif isinstance(other,Segment):
return ((other.start_point in self) and (other.end_point in self))
elif isinstance(other,ConvexPolygon):
for point in other.points:
if not point in self:
return False
return True
else:
raise NotImplementedError("")
def __eq__(self,other):
if isinstance(other,ConvexPolyhedron):
return (hash(self) == hash(other))
else:
return False
[docs] def move(self,v):
"""Return the ConvexPolyhedron that you get when you move self by vector v, self is also moved"""
if isinstance(v,Vector):
convexpolygon_list = []
for convexpolygon in self.convex_polygons:
convexpolygon_list.append(convexpolygon.move(v))
self.convex_polygons = tuple(convexpolygon_list)
self.point_set = set()
self.segment_set = set()
self.pyramid_set = set()
for convex_polygon in self.convex_polygons:
for point in convex_polygon.points:
self.point_set.add(point)
for segment in convex_polygon.segments():
self.segment_set.add(segment)
self.center_point = self._get_center_point()
for i in range(len(self.convex_polygons)):
convex_polygon = self.convex_polygons[i]
if Vector(self.center_point,convex_polygon.plane.p) * convex_polygon.plane.n < -get_eps():
self.convex_polygons[i] = - convex_polygon
self.pyramid_set.add(Pyramid(convex_polygon,self.center_point,direct_call=False))
if not self._check_normal():
raise ValueError('Check Normal Fails For The Convex Polyhedron')
if not self._euler_check():
get_main_logger().critical('V:{} F:{} E:{}'.format(len(self.point_set),len(self.segment_set),len(self.convex_polygons)))
raise ValueError('Check for the number of vertices, faces and edges fails, the polyhedron may not be closed')
return ConvexPolyhedron(self.convex_polygons)
else:
raise NotImplementedError("The second parameter for move function must be Vector")
def _get_polygon_hash_sum(self):
"""return the sum of hash value of all the ConvexPolygons"""
hash_sum = 0
for polygon in self.convex_polygons:
hash_sum += hash(polygon)
return hash_sum
def _get_point_hash_sum(self):
"""return the sum of hash value of all the points"""
hash_sum = 0
for point in self.point_set:
hash_sum += hash(point)
return hash_sum
# the hash function is not accurate
# in some extreme case, this function may fail
# which means it's vulnerable to attacks.
def __hash__(self):
"""return the hash value of the ConvexPolyhedron"""
return hash((
"ConvexPolyhedron",
round(self._get_polygon_hash_sum(),SIG_FIGURES),
round(self._get_point_hash_sum(),SIG_FIGURES)
))
#no in_ function
[docs] def length(self):
"""return the total length of the polyhedron"""
l = 0
for segment in self.segment_set:
l += segment.length()
return l
[docs] def area(self):
"""return the total area of the polyhedron"""
a = 0
for polygon in self.convex_polygons:
a += polygon.area()
return a
[docs] def volume(self):
"""return the total volume of the polyhedron"""
v = 0
for pyramid in self.pyramid_set:
v += pyramid.volume()
return v
Parallelepiped = ConvexPolyhedron.Parallelepiped
Cone = ConvexPolyhedron.Cone
Sphere = ConvexPolyhedron.Sphere
Cylinder = ConvexPolyhedron.Cylinder
__all__=("ConvexPolyhedron","Parallelepiped","Cone","Sphere","Cylinder")