438 lines
14 KiB
Python
438 lines
14 KiB
Python
from typing import Tuple, Union
|
||
|
||
from sympy import symbols, solve
|
||
|
||
from loguru import logger
|
||
|
||
from ..cgal_bindings.plane import (
|
||
Point_2,
|
||
Segment_2,
|
||
Vector_2,
|
||
Ray_2,
|
||
Direction_2,
|
||
Line_2,
|
||
squared_distance,
|
||
)
|
||
from ..cgal_bindings.enums import Orientation
|
||
|
||
|
||
def point_maker(pre_point):
|
||
if isinstance(pre_point, Point2D):
|
||
return pre_point
|
||
elif isinstance(pre_point, tuple):
|
||
return Point2D(pre_point[0], pre_point[1])
|
||
else:
|
||
print(type(pre_point))
|
||
raise ValueError("Invalid input for Point2D")
|
||
|
||
|
||
class Shape2D(object):
|
||
def __init__(self, **kwargs):
|
||
self.color = kwargs.get("color", "white")
|
||
|
||
# 计算self到另一个Shape2D的距离,返回float
|
||
def distance(
|
||
self, target: [Tuple[float, float], Point2D, Line2D, Segment2D, Ray2D]
|
||
) -> float:
|
||
if isinstance(target, tuple):
|
||
target = point_maker(target)
|
||
return (squared_distance(self, target)) ** 0.5
|
||
|
||
|
||
class Point2D(Shape2D, Point_2):
|
||
def __init__(self, x: float, y: float, **kwargs):
|
||
Point_2.__init__(self, x, y)
|
||
Shape2D.__init__(self, **kwargs)
|
||
|
||
# 重载==运算符,判断两个Point2D实例是否为同一点
|
||
def __eq__(self, other: [Tuple[float, float], Point2D]) -> bool:
|
||
self.point == point_maker(other)
|
||
|
||
def __repr__(self) -> str:
|
||
return f"Point2D(x: {self.x}, y: {self.y})"
|
||
|
||
|
||
class Segment2D(Shape2D, Segment_2):
|
||
def __init__(self, point1, point2, **kwargs):
|
||
point1 = point_maker(point1)
|
||
point2 = point_maker(point2)
|
||
super(Shape2D, self).__init__(**kwargs)
|
||
super(Segment_2, self).__init__(point1, point2)
|
||
|
||
# 计算线段的长度
|
||
def length(self) -> float:
|
||
return self._squared_length() ** 0.5
|
||
|
||
# 重载==运算符,判断两个Segment2D实例是否为同一条线段
|
||
def __eq__(self, other: Segment2D) -> bool:
|
||
return self == other
|
||
|
||
# 重载`in`运算符,判断点是否在线段上
|
||
def __contains__(self, point: [Tuple[float, float], Point2D]) -> bool:
|
||
point = point_maker(point)
|
||
return self.has_on(point)
|
||
|
||
# 重载`repr`方法,返回Segment2D的字符串表示
|
||
def __repr__(self) -> str:
|
||
return f"Segment2D({self.point1}, {self.point2})"
|
||
|
||
# 线段的起点`source`
|
||
@property
|
||
def source(self) -> Point2D:
|
||
return self._source()
|
||
|
||
# 线段的终点`target`
|
||
@property
|
||
def target(self) -> Point2D:
|
||
return self._target()
|
||
|
||
|
||
class Ray2D(Shape2D, Ray_2):
|
||
def __init__(
|
||
self,
|
||
source: [Tuple[float, float], Point2D],
|
||
point: [Tuple[float, float], Point2D] = None,
|
||
vec: Vector2D = None,
|
||
direction: Direction2D = None,
|
||
line: Line2D = None,
|
||
**kwargs,
|
||
):
|
||
source = point_maker(source)
|
||
if point is not None:
|
||
point = point_maker(point)
|
||
Ray_2.__init__(self, source, point)
|
||
elif vec is not None:
|
||
Ray_2.__init__(self, source, vec)
|
||
elif direction is not None:
|
||
Ray_2.__init__(self, source, direction)
|
||
elif line is not None:
|
||
Ray_2.__init__(self, source, line)
|
||
else:
|
||
raise ValueError("Invalid input for Ray2D")
|
||
Shape2D.__init__(self, **kwargs)
|
||
|
||
# 返回射线起点
|
||
@property
|
||
def source(self) -> Point2D:
|
||
return self._source()
|
||
|
||
# 返回射线方向
|
||
@property
|
||
def direction(self) -> Direction2D:
|
||
return self._direction()
|
||
|
||
# 重载`repr`方法,返回Ray2D的字符串表示
|
||
def __repr__(self) -> str:
|
||
return f"Ray2D({self.source}, {self.direction})"
|
||
|
||
# 重载`in`运算符,判断点是否在射线上
|
||
def __contains__(self, point: [Tuple[float, float], Point2D]) -> bool:
|
||
point = point_maker(point)
|
||
return self.has_on(point)
|
||
|
||
|
||
class Direction2D(Shape2D, Direction_2):
|
||
def __init__(
|
||
self,
|
||
vec: Vector2D = None,
|
||
line: Line2D = None,
|
||
ray: Ray2D = None,
|
||
seg: Segment2D = None,
|
||
coor: Tuple[float, float] = None,
|
||
**kwargs,
|
||
):
|
||
"""`Direction2D`构造函数
|
||
|
||
Args:
|
||
vec (Vector2D, optional): _description_. Defaults to None.
|
||
line (Line2D, optional): _description_. Defaults to None.
|
||
ray (Ray2D, optional): _description_. Defaults to None.
|
||
seg (Segment2D, optional): _description_. Defaults to None.
|
||
coor (Tuple[float, float], optional): _description_. Defaults to None.
|
||
|
||
> 需要提供`vec`, `line`, `ray`, `seg`, `coor`中的任意一个作为初始参数。
|
||
"""
|
||
Shape2D.__init__(self, **kwargs)
|
||
if vec is not None:
|
||
Direction_2.__init__(self, vec)
|
||
elif line is not None:
|
||
Direction_2.__init__(self, line)
|
||
elif ray is not None:
|
||
Direction_2.__init__(self, ray)
|
||
elif seg is not None:
|
||
Direction_2.__init__(self, seg)
|
||
elif coor is not None:
|
||
Direction_2.__init__(self, coor)
|
||
else:
|
||
raise ValueError("Invalid input for Direction2D")
|
||
|
||
@property
|
||
def dx(self) -> float:
|
||
return self._dx()
|
||
|
||
@property
|
||
def dy(self) -> float:
|
||
return self._dy()
|
||
|
||
# 重载`repr`方法,返回Direction2D的字符串表示
|
||
def __repr__(self) -> str:
|
||
return f"Direction2D({self.dx}, {self.dy})"
|
||
|
||
|
||
class Vector2D(Shape2D, Vector_2):
|
||
def __init__(
|
||
self,
|
||
points: Tuple[Tuple[float, float], Tuple[float, float]] = None,
|
||
seg: Segment2D = None,
|
||
ray: Ray2D = None,
|
||
line: Line2D = None,
|
||
values: Tuple[float, float] = None,
|
||
homo_values: Tuple[float, float, float] = None,
|
||
**kwargs,
|
||
):
|
||
"""向量Vector2D的初始化方法
|
||
|
||
Args:
|
||
points (Tuple[Tuple[float, float], Tuple[float, float]], optional): 两个端点的坐标. Defaults to None.
|
||
seg (Segment2D, optional): 线段. Defaults to None.
|
||
ray (Ray2D, optional): 射线. Defaults to None.
|
||
line (Line2D, optional): 直线. Defaults to None.
|
||
values (Tuple[float, float], optional): 坐标值. Defaults to None.
|
||
homo_values (Tuple[float, float, float], optional): 齐次坐标值. Defaults to None.
|
||
"""
|
||
Shape2D.__init__(self, **kwargs)
|
||
if points is not None:
|
||
point1 = point_maker(points[0])
|
||
point2 = point_maker(points[1])
|
||
Vector_2.__init__(self, point1, point2)
|
||
elif seg is not None:
|
||
Vector_2.__init__(self, seg)
|
||
elif ray is not None:
|
||
Vector_2.__init__(self, ray)
|
||
elif line is not None:
|
||
Vector_2.__init__(self, line)
|
||
elif values is not None:
|
||
Vector_2.__init__(self, *values)
|
||
elif homo_values is not None:
|
||
Vector_2.__init__(self, *homo_values)
|
||
else:
|
||
Vector_2.__init__(self)
|
||
|
||
def __repr__(self) -> str:
|
||
return f"Vector2D({self.x}, {self.y})"
|
||
|
||
@property
|
||
def x(self) -> float:
|
||
return self._x()
|
||
|
||
@property
|
||
def y(self) -> float:
|
||
return self._y()
|
||
|
||
@property
|
||
def hx(self) -> float:
|
||
return self._hx()
|
||
|
||
@property
|
||
def hy(self) -> float:
|
||
return self._hy()
|
||
|
||
@property
|
||
def hw(self) -> float:
|
||
return self._hw()
|
||
|
||
|
||
# 直线类
|
||
class Line2D(Shape2D, Line_2):
|
||
def __init__(
|
||
self,
|
||
coefficient: Tuple[float, float, float] = None,
|
||
points: Tuple[Tuple[float, float], Tuple[float, float]] = None,
|
||
point_and_direction: Tuple[Point2D, Direction2D] = None,
|
||
point_and_vector: Tuple[Point2D, Vector2D] = None,
|
||
segment: Segment2D = None,
|
||
ray: Ray2D = None,
|
||
**kwargs,
|
||
):
|
||
"""直线Line2D的初始化方法
|
||
|
||
Args:
|
||
直线Line2D的初始化参数,可以是以下几种形式:
|
||
- "coefficient": 直线一般式方程的参数A, B, C
|
||
- "points": 两个端点
|
||
- "point_and_direction": 点和方向
|
||
- "point_and_vector": 点和向量
|
||
- "segment": 线段
|
||
- "ray": 射线
|
||
|
||
"""
|
||
Shape2D.__init__(self, **kwargs)
|
||
if "points" in init_from:
|
||
point1 = point_maker(point1)
|
||
point2 = point_maker(point2)
|
||
Line_2.__init__(self, point1, point2)
|
||
if "coefficient" in init_from:
|
||
Line_2.__init__(self, a, b, c)
|
||
if "point_and_direction" in init_from:
|
||
Line_2.__init__(self, point1, direction)
|
||
if "point_and_vector" in init_from:
|
||
point1 = point_maker(point1)
|
||
Line_2.__init__(self, point1, vec)
|
||
if "segment" in init_from:
|
||
Line_2.__init__(self, seg)
|
||
if "ray" in init_from:
|
||
Line_2.__init__(self, ray)
|
||
|
||
self._x, self._y = symbols("x y")
|
||
if self.is_degenerate():
|
||
self._expression = None
|
||
else:
|
||
self._expression = self.a * self._x + self.b * self._y + self.c
|
||
|
||
@property
|
||
def a(self) -> float:
|
||
return self._a()
|
||
|
||
@property
|
||
def b(self) -> float:
|
||
return self._b()
|
||
|
||
@property
|
||
def c(self) -> float:
|
||
return self._c()
|
||
|
||
# 返回该直线一般式方程文本,形如`ax+by+c=0`
|
||
@property
|
||
def expression(self):
|
||
return f"{str(self._expression)} = 0"
|
||
|
||
# 通过关键字`in`判断点是否在线段上
|
||
def __contains__(self, point):
|
||
return self.has_on(point)
|
||
|
||
# 重载`repr`方法,返回Line2D的字符串表示
|
||
def __repr__(self) -> str:
|
||
return f"Line2D(expression is {self.expression})"
|
||
|
||
# 计算点point到此直线的距离
|
||
def distance(self, point: [Tuple[float, float], Point2D]) -> float:
|
||
point = point_maker(point)
|
||
return squared_distance(self, point) ** 0.5
|
||
|
||
|
||
class Circle2D(Shape2D, Circle_2):
|
||
def __init__(
|
||
self,
|
||
center_and_radius: Tuple[Union[Point2D, Tuple[float, float]], float] = None,
|
||
points: Tuple[
|
||
Union[Point2D, Tuple[float, float]], Union[Point2D, Tuple[float, float]]
|
||
] = None,
|
||
diameter_and_orientation: Tuple[
|
||
Union[Point2D, Tuple[float, float]],
|
||
Union[Point2D, Tuple[float, float]],
|
||
Orientation,
|
||
] = None,
|
||
center_and_orientation: Tuple[
|
||
Union[Point2D, Tuple[float, float]], Orientation
|
||
] = None,
|
||
**kwargs,
|
||
):
|
||
"""
|
||
圆Circle2D的初始化方法
|
||
|
||
Args:
|
||
center_and_radius: 圆心和半径, 方向默认为Orientation.COUNTERCLOCKWISE
|
||
points: 过三个不共线点的圆
|
||
diameter_and_orientation: 直径为给定两个端点的线段,方向默认为Orientation.COUNTERCLOCKWISE, 方向不可为Orienatation.COLLINEAR
|
||
center_and_orientation: 圆心和方向, 通过该方法初始化的圆半径为0, `is_degenerate`方法返回True\
|
||
|
||
Example:
|
||
```python
|
||
# 圆心和方向
|
||
circle1 = Circle2D(center_and_orientation=((0, 0), Orientation.COUNTERCLOCKWISE))
|
||
assert circle1.is_degenerate() == True
|
||
|
||
# 过三个不共线点的圆
|
||
circle2 = Circle2D(points=((0, 0), (1, 1), (2, 0)))
|
||
|
||
# 直径为给定两个端点的线段,方向默认为Orientation.COUNTERCLOCKWISE
|
||
circle3 = Circle2D(diameter_and_orientation=((0, 0), (1, 1)), Orientation.COUNTERCLOCKWISE)
|
||
|
||
# 圆心和半径
|
||
circle4 = Circle2D(center_and_radius=((0, 0), 1))((0, 0), 1, Orientation.COUNTERCLOCKWISE))
|
||
"""
|
||
Shape2D.__init__(self, **kwargs)
|
||
if center_and_radius is not None:
|
||
center = point_maker(center_and_radius[0])
|
||
radius = center_and_radius[1]
|
||
ori = center_and_radius[2] if len(center_and_radius) == 3 else Orientation.COUNTERCLOCKWISE
|
||
Circle_2.__init__(self, center, radius, ori)
|
||
elif points is not None:
|
||
points = [point_maker(p) for p in points]
|
||
Circle_2.__init__(self, *points)
|
||
elif diameter_and_orientation is not None:
|
||
point1 = point_maker(diameter_and_orientation[0][0])
|
||
point2 = point_maker(diameter_and_orientation[0][1])
|
||
ori = diameter_and_orientation[1] if len(diameter_and_orientation) == 2 else Orientation.COUNTERCLOCKWISE
|
||
Circle_2.__init__(self, point1, point2, ori)
|
||
elif center_and_orientation is not None:
|
||
center = point_maker(center_and_orientation[0])
|
||
ori = center_and_orientation[1] if len(center_and_orientation) == 2 else Orientation.COUNTERCLOCKWISE
|
||
Circle_2.__init__(self, center, ori)
|
||
else:
|
||
raise ValueError("Invalid input for Circle2D")
|
||
self._x, self._y = symbols("x y")
|
||
self._expression = (self._x - self.center.x) ** 2 + (self._y - self.center.y) ** 2 - self._squared_radius
|
||
|
||
@property
|
||
def center(self) -> Point2D:
|
||
return self._center()
|
||
|
||
@property
|
||
def radius(self) -> float:
|
||
return self._squared_radius() ** 0.5
|
||
|
||
@property
|
||
def orientation(self) -> Orientation:
|
||
return self._orientation()
|
||
|
||
# 重载`repr`方法,返回Circle2D的字符串表示
|
||
def __repr__(self) -> str:
|
||
return f"Circle2D(center: {self.center}, radius: {self.radius}, orientation: {self.orientation})"
|
||
|
||
# 圆的标准方程
|
||
@property
|
||
def standard_equation(self) -> str:
|
||
return f"{str(self._expression)} = 0"
|
||
|
||
# 圆的一般方程
|
||
@property
|
||
def general_equation(self) -> str:
|
||
return f"{str(self._expression.expand())} = 0"
|
||
|
||
# 三角形类
|
||
class Triangle2D(Shape2D, Triangle_2):
|
||
def __init__(
|
||
self,
|
||
points: Tuple[
|
||
Union[Point2D, Tuple[float, float]],
|
||
Union[Point2D, Tuple[float, float]],
|
||
Union[Point2D, Tuple[float, float]],
|
||
] = None,
|
||
**kwargs,
|
||
):
|
||
"""
|
||
三角形Triangle2D的初始化方法
|
||
|
||
Args:
|
||
points: 三个端点
|
||
"""
|
||
Shape2D.__init__(self, **kwargs)
|
||
points = [point_maker(p) for p in points]
|
||
Triangle_2.__init__(self, *points)
|
||
|
||
# 重载`repr`方法,返回Triangle2D的字符串表示
|
||
def __repr__(self) -> str:
|
||
return f"Triangle2D(points: {[self.vertex(i) for i in range(3)]})"
|
||
|
||
|