Compare commits

..

No commits in common. "9a170f93d301faede7967f4a4a11517bf0fdd81b" and "f8e50ca04862e756d72aee365730a1f1c7f1d4d6" have entirely different histories.

15 changed files with 3 additions and 543 deletions

2
.gitignore vendored
View File

@ -134,7 +134,7 @@ ipython_config.py
# This is especially recommended for binary packages to ensure reproducibility, and is more # This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries. # commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
poetry.lock #poetry.lock
# pdm # pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.

4
.gitmodules vendored
View File

@ -1,4 +0,0 @@
[submodule "cgal"]
path = cgal
url = https://gitee.com/atari/cgal.git
branch = 5.6.x-branch

View File

@ -1,5 +1,4 @@
# toydesigner-cgal # toydesigner-cgal
python bindings for some geometry algorithms in `cgal` (for [toydesigner](https://gitea.winkinshly.site/songsenand/ToyDesigner) only) python bindings for some geometry algorithms in 'cgal' (for 'toydesigner' only)
`cgal`中部分几何算法的python绑定仅用于`toydesigner`
>`cgal`中部分几何算法的python绑定仅用于[toydesigner](https://gitea.winkinshly.site/songsenand/ToyDesigner)

View File

@ -1,9 +0,0 @@
import warnings
import subprocess
res = subprocess.run(["python3", "setup.py", "build_ext", "--inplace"])
if res.returncode != 0:
warnings.warn(res.stderr.decode())
warnings.warn("Error building toydesigner-cgal")
elif res.returncode == 0:
print("Successfully built toydesigner-cgal")

1
cgal

@ -1 +0,0 @@
Subproject commit 11fc1943fbc75135d3ec5776cebab1f4aaa43fb6

View File

@ -1,40 +0,0 @@
[tool.poetry]
name = "toydesigner-cgal"
version = "0.0.1"
description = "python bindings for some geometry algorithms in `cgal` (for [toydesigner](https://gitea.winkinshly.site/songsenand/ToyDesigner) only)"
authors = ["songsenand <songsenand@163.com>"]
license = "GPL"
readme = "README.md"
packages = [{include = "tdcgal"}]
[tool.poetry.dependencies]
python = "^3.11"
pybind11 = "^2.11.1"
loguru = "^0.7.2"
sympy = "^1.12"
[[tool.poetry.source]]
name = "ali"
url = "https://mirrors.aliyun.com/pypi/simple/"
priority = "primary"
[[tool.poetry.source]]
name = "PyPI"
priority = "primary"
[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
[build-system]
requires = [
"poetry-core>=1.1.0",
"setuptools>=65.4.1",
"pybind11>=2.11.1"
]
build-backend = "poetry.core.masonry.api"
[tool.poetry.build]
script = "build.py"
generate-setup-file = false

View File

View File

@ -1,15 +0,0 @@
#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);
}

View File

@ -1,6 +0,0 @@
#include <pybind11/pybind11.h>
namespace py = pybind11;
void init_plane(py::module_ &);

View File

@ -1,315 +0,0 @@
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
"""

View File

@ -1,54 +0,0 @@
#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);
};

View File

@ -1,28 +0,0 @@
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},
)

View File

View File

@ -1,67 +0,0 @@
from math import isclose
from pytest import fixture
from tdcgal.plane.objects import *
class TestPoint2D:
def setup_method(self):
self.point1 = Point2D(1.0, 2.0)
self.point2 = Point2D(3.0, 4.0)
def test_distance(self):
assert isclose(self.point1.distance_to_point(self.point2), 8 ** 0.5)
def test_x(self):
assert self.point1.x() == 1
assert self.point2.x() == 3
def test_y(self):
assert self.point1.y() == 2
assert self.point2.y() == 4
"""
class TestLine:
def setup_method(self):
self.line1 = Line((1, 3), (1, 4))
self.line2 = Line((1, 1), (2, 2))
self.line3 = Line((37, 5), (7, 29))
def test_slope(self):
assert self.line1.slope == float("inf")
assert self.line2.slope == 1
def test_y_intercept(self):
assert self.line1.y_intercept == float("inf")
assert self.line2.y_intercept == 0
assert self.line3.y_intercept == 34.6
def test_equation(self):
assert self.line1.expression == "1.0*x - 1.0 = 0"
assert self.line2.expression == "1.0*x - 1.0*y = 0"
assert self.line3.expression == "24.0*x + 30.0*y - 1038.0 = 0"
def test_contains(self):
assert (1, 5) not in self.line1
assert (1, 3.2) in self.line1
assert (1.45, 1.45) in self.line2
assert (3, 3) not in self.line2
assert (37, 5) in self.line3
assert (7, 29) in self.line3
assert (12, 25) in self.line3
assert (12, 12) not in self.line3
assert (3, 32.2) not in self.line3
assert self.line3.contains((6, 29.8), allow_on_extended=True)
def test_length(self):
assert self.line1.length == 1
assert self.line2.length == 2 ** 0.5
assert isclose(self.line3.length, 38.41874542459709)
def test_distance(self):
assert self.line1.point_distance((1, 5)) == 1
assert self.line2.point_distance((1.5, 1.5)) == 0
assert isclose(self.line3.point_distance((12, 24)),0.780868809443030)
"""