toydesigner-cgal/tdcgal/plane/objects.py

315 lines
9.4 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 math import isclose
from sympy import symbols, solve
from loguru import logger
from ..cgal_bindings.plane import Point_2, Segment_2, Line_2, squared_distance
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")
class Point2D(Shape2D, Point_2):
def __init__(self, x: float, y: float, **kwargs):
Point_2.__init__(self, x, y)
Shape2D.__init__(self, **kwargs)
# 计算点到另一点的距离
def distance_to_point(self, point):
point = point_maker(point)
return (squared_distance(self, point)) ** 0.5
# 重载==运算符判断两个Point2D实例是否为同一点
def __eq__(self, other):
self.point == point_maker(other)
def __repr__(self):
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):
return self.squared_length() ** 0.5
# 重载==运算符判断两个Segment2D实例是否为同一条线段
def __eq__(self, other) -> bool:
return self == other
# 重载`in`运算符,判断点是否在线段上
def __contains__(self, point) -> bool:
return self.has_on(point)
# 重载`repr`方法返回Segment2D的字符串表示
def __repr__(self) -> str:
return f"Segment2D({self.point1}, {self.point2})"
# 线段的起点`source`
@property
def source(self):
return self._source()
# 线段的终点`target`
@property
def target(self):
return self._target()
# 直线类
class Line(Shape2D, Line_2):
def __init__(self, point1, point2, **kwargs):
super(Line, self).__init__(**kwargs)
self.point1 = point_maker(point1)
self.point2 = point_maker(point2)
if self.point1 == self.point2:
raise ValueError("Invalid input for Line: point1 == point2")
self._a = self.point2.y - self.point1.y
self._b = self.point1.x - self.point2.x
self._c = self.point2.x * self.point1.y - self.point1.x * self.point2.y
if isclose(self.point1.x, self.point2.x):
self._slope = float("inf")
self._y_intercept = float("inf")
else:
self._slope = -self._a / self._b
self._y_intercept = -self._c / self._b
self._x, self._y = symbols("x y")
self._expression = self._a * self._x + self._b * self._y + self._c
self._length = (
(self.point1.x - self.point2.x) ** 2 + (self.point1.y - self.point2.y) ** 2
) ** 0.5
def contains(self, point, allow_on_extended=False):
""""""判断点`point`是否在线段上
Args:
point (Point2D or tuple): 需要判断的点
allow_on_extended (bool, optional): 是否允许点在线段的延长线上. Defaults to False.
Returns:
_type_: boolean
""""""
point = point_maker(point)
f = self.function
if allow_on_extended:
if isclose(self._b, 0.0):
return isclose(point.x, self.point1.x)
return isclose(f(point.x), point.y)
if isclose(self._b, 0.0):
return isclose(point.x, self.point1.x) and (
point.y >= min(self.point1.y, self.point2.y)
and point.y <= max(self.point1.y, self.point2.y)
)
return isclose(f(point.x), point.y) and (
(
point.x >= min(self.point1.x, self.point2.x)
and point.x <= max(self.point1.x, self.point2.x)
)
)
# 通过关键字`in`判断点是否在线段上
def __contains__(self, point):
return self.contains(point)
# 计算点`point`到线段的垂足
def foot_point(self, point):
point = point_maker(point)
if self.contains(point, allow_on_extended=True):
return point
else:
denominator = self._a**2 + self._b**2
x = (
(self._b**2) * point.x
- self._a * self._b * point.y
- self._a * self._c
) / denominator
y = (
(self._a**2) * point.y
- self._a * self._b * point.x
- self._b * self._c
) / denominator
return Point2D(x, y)
# 计算点`point`到线段的距离
def point_distance(self, point, is_to_line=False):
point = point_maker(point)
if self.contains(point):
return 0
else:
dis_to_line = (
abs(self._expression.evalf(subs={self._x: point.x, self._y: point.y}))
/ (self._a**2 + self._b**2) ** 0.5
)
if is_to_line:
return dis_to_line
if self.foot_point(point) not in self:
return min(
self.point1.distance_to_point(point),
self.point2.distance_to_point(point),
)
return dis_to_line
# 该线段所在直线的纵截距
@property
def y_intercept(self):
return self._y_intercept
# 该线段的斜率
@property
def slope(self):
return self._slope
# 该线段所在直线的函数方程
@property
def function(self):
def f(x):
expr = self._expression.subs(self._x, x)
res = solve(expr, self._y)
if len(res) == 1:
return res[0].evalf()
else:
return None
return f
# 该线段的函数式
@property
def expression(self):
return f"{str(self._expression)} = 0"
@property
# 该线段的长度
def length(self):
return self._length
""""""
# 该线段与另一线段的交点
def intersection(self, other):
if isinstance(other, Line):
if self.slope() == other.slope():
return None
else:
x = (
self.slope() * other.point1.x
- self.point1.y
+ other.slope() * self.point1.x
- other.point1.y
) / (self.slope() - other.slope())
y = self.slope() * (x - self.point1.x) + self.point1.y
return Point2D(x, y)
else:
raise ValueError("Invalid input for Line")
""""""
"""
class Rectangle(Shape2D):
def __init__(self, point1, point2, **kwargs):
super(Rectangle, self).__init__(**kwargs)
p1 = point_maker(point1)
p2 = point_maker(point2)
self.point1 = point_maker(p1)
self.point2 = point_maker((point1.x, point2.y))
self.point3 = point_maker(point2)
self.point4 = point_maker((point2.x, point1.y))
# 该矩形的四条边
def sides(self):
return [
Line(self.point1, self.point2),
Line(self.point2, self.point3),
Line(self.point3, self.point4),
Line(self.point4, self.point1),
]
# 该矩形的边长
def side_lengths(self):
return [line.length() for line in self.sides()]
# 该矩形是否包含点`point`
def contains(self, point):
point = point_maker(point)
lines = self.sides()
return isclose(
sum([line.point_distance(point) for line in lines]),
sum(self.side_lengths()),
)
# 通过关键字`in`判断点是否在矩形内
def __contains__(self, point):
return self.contains(point)
# 该矩形的面积
def area(self):
return self.side_lengths()[0] * self.side_lengths()[1]
# 该矩形的周长
def perimeter(self):
return sum(self.side_lengths())
# 该矩形的中心点
@property
def center(self):
return Point2D(
(self.point1.x + self.point3.x) / 2, (self.point1.y + self.point2.y) / 2
)
# 该矩形的四个顶点
@property
def vertices(self):
return [self.point1, self.point2, self.point3, self.point4]
# 该矩形的外接圆
@property
def circumcircle(self):
pass
class Polygon(Shape2D):
def __init__(self, points, **kwargs):
super(Polygon, self).__init__(**kwargs)
self._points = [point_maker(point) for point in points]
self._lines = [Line(self.points[i], self.points[(i + 1) % len(self.points)]) for i in range(len(self.points))]
self._sides = [line.length() for line in self.lines]
self._perimeter = sum(self.sides)
self._center = Point2D(sum([point.x for point in self.points]) / len(self.points), sum([point.y for point in self.points]) / len(self.points))
@property
def points(self):
return self._points
@property
def lines(self):
return self._lines
@property
def sides(self):
return self._sides
@property
def perimeter(self):
return self._perimeter
@property
def center(self):
return self._center
"""