feat(tdcgal/*): 添加Point2D Segment2D Line2D等
BREAKING CHANGE:
This commit is contained in:
parent
51c70f3312
commit
375dbe979c
|
|
@ -0,0 +1,15 @@
|
|||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/operators.h>
|
||||
|
||||
#include <CGAL/Simple_cartesian.h>
|
||||
|
||||
# include "cgal.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
typedef CGAL::Simple_cartesian<double> Kernel;
|
||||
|
||||
PYBIND11_MODULE(cgal_bindings, m) {
|
||||
py::module_ plane = m.def_submodule("plane");
|
||||
init_plane(plane);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#include <pybind11/pybind11.h>
|
||||
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
void init_plane(py::module_ &);
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
from math import isclose
|
||||
|
||||
from sympy import symbols, solve
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from ..cgal_bindings.plane import Point_2, Segment_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()
|
||||
|
||||
# 与线段共线的直线
|
||||
def supporting_line(self) -> Line:
|
||||
return self.supporting_line()
|
||||
|
||||
"""class Line(Shape2D):
|
||||
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
|
||||
|
||||
"""
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include <pybind11/operators.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <CGAL/Simple_cartesian.h>
|
||||
|
||||
#include "../cgal.hpp"
|
||||
|
||||
typedef CGAL::Simple_cartesian<double> Kernel;
|
||||
typedef Kernel::Point_2 Point_2;
|
||||
typedef Kernel::Segment_2 Segment_2;
|
||||
typedef Kernel::Line_2 Line_2;
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
double squared_distance(const Point_2 &a, const Point_2 &b) {
|
||||
return CGAL::squared_distance(a, b);
|
||||
}
|
||||
|
||||
void init_plane(py::module_ &m) {
|
||||
py::class_<Point_2>(m, "Point_2")
|
||||
.def(py::init<double, double>())
|
||||
.def("x", &Point_2::x)
|
||||
.def("y", &Point_2::y)
|
||||
.def("hx", &Point_2::hx)
|
||||
.def("hy", &Point_2::hy)
|
||||
.def("hw", &Point_2::hw)
|
||||
.def(py::self == py::self)
|
||||
.def("__repr__", [](const Point_2 &a) {
|
||||
return "<Point_2 x=" + std::to_string(a.x()) +
|
||||
" y=" + std::to_string(a.y()) + ">";
|
||||
});
|
||||
py::class_<Segment_2>(m, "Segment_2")
|
||||
.def(py::init<Point_2, Point_2>())
|
||||
.def("_source", &Segment_2::source)
|
||||
.def("_target", &Segment_2::target)
|
||||
.def("min", &Segment_2::min, "返回线段的端点中较小的那个")
|
||||
.def("max", &Segment_2::max, "返回线段的端点中较大的那个")
|
||||
.def("squared_length", &Segment_2::squared_length, "返回线段的平方长度")
|
||||
.def("opposite", &Segment_2::opposite, "返回反向的线段")
|
||||
.def("has_on", &Segment_2::collinear_has_on, "判断点是否在线段上")
|
||||
.def("supporting_line", &Segment_2::supporting_line, "返回与线段共线的直线")
|
||||
.def("is_degenerate", &Segment_2::is_degenerate, "判断线段端点是否重合")
|
||||
.def("is_horizontal", &Segment_2::is_horizontal, "判断线段是否水平")
|
||||
.def("is_vertical", &Segment_2::is_vertical, "判断线段是否垂直")
|
||||
.def(py::self == py::self)
|
||||
.def(py::self != py::self);
|
||||
py::class_<Line_2>(m, "Line_2")
|
||||
.def(py::init<Point_2, Point_2>())
|
||||
.def("a", &Line_2::a)
|
||||
.def("b", &Line_2::b)
|
||||
.def("c", &Line_2::c)
|
||||
.def(py::self == py::self);
|
||||
m.def("squared_distance", &squared_distance);
|
||||
};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
from pathlib import Path
|
||||
from pybind11.setup_helpers import Pybind11Extension, ParallelCompile, build_ext
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
__version__ = "0.0.1"
|
||||
|
||||
ext_modules = [
|
||||
Pybind11Extension(
|
||||
"cgal_bindings",
|
||||
sources=sorted([str(i.absolute()) for i in (Path(".").rglob("*.cpp"))]),
|
||||
define_macros=[("VERSION_INFO", __version__)],
|
||||
include_dirs=["../cgal/"],
|
||||
),
|
||||
]
|
||||
|
||||
ParallelCompile("NPY_NUM_BUILD_JOBS").install()
|
||||
|
||||
setup(
|
||||
name="cgal_bindings",
|
||||
version=__version__,
|
||||
author="songsenand",
|
||||
author_email="songsenand@163.com",
|
||||
url="https://gitea.winkinshly.site/songsenand/toydesigner-cgal",
|
||||
description="python bindings for some geometry algorithms in `cgal` (for [toydesigner](https://gitea.winkinshly.site/songsenand/ToyDesigner) only)",
|
||||
ext_modules=ext_modules,
|
||||
cmdclass={"build_ext": build_ext},
|
||||
)
|
||||
Loading…
Reference in New Issue