toydesigner-cgal/tdcgal/plane/objects.py

413 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"