bd37f2249d
- 使用 pybind11 v2.11.1 (FetchContent) 构建 _vde 原生模块 - 模块化结构: bind_core.cpp / bind_curves.cpp / bind_mesh.cpp - 暴露核心类型: Point2D, Point3D, Vector3D, AABB3D, Polygon2D - 暴露曲线: BezierCurve, BSplineCurve, NurbsCurve - 暴露网格: delaunay_2d/3d, DelaunayResult, HalfedgeMesh - 变换函数: translate, rotate, scale 等 - Python 包 vde/ 含 __init__.py 及子模块封装 - setup.py 支持 pip install -e . 可编辑安装 - VDE_BUILD_PYTHON CMake 选项 (默认 OFF),通过 -DVDE_BUILD_PYTHON=ON 启用
226 lines
11 KiB
C++
226 lines
11 KiB
C++
#include <pybind11/pybind11.h>
|
|
#include <pybind11/eigen.h>
|
|
#include <pybind11/stl.h>
|
|
|
|
#include "vde/core/point.h"
|
|
#include "vde/core/aabb.h"
|
|
#include "vde/core/distance.h"
|
|
#include "vde/core/convex_hull.h"
|
|
#include "vde/core/polygon.h"
|
|
#include "vde/core/transform.h"
|
|
|
|
namespace py = pybind11;
|
|
using namespace vde::core;
|
|
|
|
namespace {
|
|
|
|
// Pretty __repr__ for Eigen points
|
|
std::string point3d_repr(const Point3D& p) {
|
|
return "Point3D(" + std::to_string(p.x()) + ", "
|
|
+ std::to_string(p.y()) + ", "
|
|
+ std::to_string(p.z()) + ")";
|
|
}
|
|
|
|
std::string point2d_repr(const Point2D& p) {
|
|
return "Point2D(" + std::to_string(p.x()) + ", "
|
|
+ std::to_string(p.y()) + ")";
|
|
}
|
|
|
|
std::string vector3d_repr(const Vector3D& v) {
|
|
return "Vector3D(" + std::to_string(v.x()) + ", "
|
|
+ std::to_string(v.y()) + ", "
|
|
+ std::to_string(v.z()) + ")";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void bind_core(py::module& m) {
|
|
auto core = m.def_submodule("core", "Core geometry types and operations");
|
|
|
|
// ── Point3D ──────────────────────────────────────
|
|
py::class_<Point3D>(core, "Point3D",
|
|
R"pbdoc(
|
|
3D point with double precision (Eigen::Vector3d).
|
|
|
|
Construct with ``Point3D(x, y, z)``. Supports arithmetic +, -, *scalar, /scalar.
|
|
)pbdoc")
|
|
.def(py::init<double, double, double>(),
|
|
py::arg("x") = 0.0, py::arg("y") = 0.0, py::arg("z") = 0.0,
|
|
"Construct from (x, y, z)")
|
|
.def(py::init([](const py::list& lst) {
|
|
if (py::len(lst) != 3)
|
|
throw std::runtime_error("Point3D requires exactly 3 values");
|
|
return Point3D(lst[0].cast<double>(),
|
|
lst[1].cast<double>(),
|
|
lst[2].cast<double>());
|
|
}),
|
|
py::arg("coords"), "Construct from [x, y, z] list")
|
|
.def_property_readonly("x", [](const Point3D& p) -> double { return p.x(); })
|
|
.def_property_readonly("y", [](const Point3D& p) -> double { return p.y(); })
|
|
.def_property_readonly("z", [](const Point3D& p) -> double { return p.z(); })
|
|
.def("__repr__", &point3d_repr)
|
|
.def("__len__", [](const Point3D&) { return 3; })
|
|
.def("__getitem__", [](const Point3D& p, int i) {
|
|
if (i < 0 || i > 2) throw py::index_error();
|
|
return p(i);
|
|
})
|
|
.def("__add__", [](const Point3D& a, const Vector3D& v) { return a + v; })
|
|
.def("__sub__", [](const Point3D& a, const Point3D& b) { return a - b; })
|
|
.def("norm", [](const Point3D& p) { return p.norm(); })
|
|
.def("squared_norm", [](const Point3D& p) { return p.squaredNorm(); })
|
|
.def("dot", [](const Point3D& a, const Point3D& b) { return a.dot(b); })
|
|
.def("cross", [](const Point3D& a, const Point3D& b) { return a.cross(b); })
|
|
.def("normalized", [](const Point3D& p) { return p.normalized(); })
|
|
.def("distance_to", [](const Point3D& a, const Point3D& b) {
|
|
return distance(a, b);
|
|
}, py::arg("other"), "Euclidean distance to another point");
|
|
|
|
// ── Vector3D ─────────────────────────────────────
|
|
py::class_<Vector3D>(core, "Vector3D",
|
|
R"pbdoc(
|
|
3D vector with double precision (Eigen::Vector3d).
|
|
|
|
Construct with ``Vector3D(x, y, z)``. Supports arithmetic +, -, *scalar, /scalar.
|
|
)pbdoc")
|
|
.def(py::init<double, double, double>(),
|
|
py::arg("x") = 0.0, py::arg("y") = 0.0, py::arg("z") = 0.0)
|
|
.def(py::init([](const py::list& lst) {
|
|
if (py::len(lst) != 3)
|
|
throw std::runtime_error("Vector3D requires exactly 3 values");
|
|
return Vector3D(lst[0].cast<double>(),
|
|
lst[1].cast<double>(),
|
|
lst[2].cast<double>());
|
|
}),
|
|
py::arg("coords"))
|
|
.def_property_readonly("x", [](const Vector3D& v) -> double { return v.x(); })
|
|
.def_property_readonly("y", [](const Vector3D& v) -> double { return v.y(); })
|
|
.def_property_readonly("z", [](const Vector3D& v) -> double { return v.z(); })
|
|
.def("__repr__", &vector3d_repr)
|
|
.def("__len__", [](const Vector3D&) { return 3; })
|
|
.def("__getitem__", [](const Vector3D& v, int i) {
|
|
if (i < 0 || i > 2) throw py::index_error();
|
|
return v(i);
|
|
})
|
|
.def("__add__", [](const Vector3D& a, const Vector3D& b) { return a + b; })
|
|
.def("__sub__", [](const Vector3D& a, const Vector3D& b) { return a - b; })
|
|
.def("__mul__", [](const Vector3D& v, double s) { return v * s; })
|
|
.def("__truediv__", [](const Vector3D& v, double s) { return v / s; })
|
|
.def("__neg__", [](const Vector3D& v) { return -v; })
|
|
.def("norm", [](const Vector3D& v) { return v.norm(); })
|
|
.def("squared_norm", [](const Vector3D& v) { return v.squaredNorm(); })
|
|
.def("dot", [](const Vector3D& a, const Vector3D& b) { return a.dot(b); })
|
|
.def("cross", [](const Vector3D& a, const Vector3D& b) { return a.cross(b); })
|
|
.def("normalized", [](const Vector3D& v) { return v.normalized(); });
|
|
|
|
// ── Point2D ──────────────────────────────────────
|
|
py::class_<Point2D>(core, "Point2D")
|
|
.def(py::init<double, double>(),
|
|
py::arg("x") = 0.0, py::arg("y") = 0.0)
|
|
.def(py::init([](const py::list& lst) {
|
|
if (py::len(lst) != 2)
|
|
throw std::runtime_error("Point2D requires exactly 2 values");
|
|
return Point2D(lst[0].cast<double>(), lst[1].cast<double>());
|
|
}))
|
|
.def_property_readonly("x", [](const Point2D& p) -> double { return p.x(); })
|
|
.def_property_readonly("y", [](const Point2D& p) -> double { return p.y(); })
|
|
.def("__repr__", &point2d_repr)
|
|
.def("__len__", [](const Point2D&) { return 2; })
|
|
.def("__getitem__", [](const Point2D& p, int i) {
|
|
if (i < 0 || i > 1) throw py::index_error();
|
|
return p(i);
|
|
});
|
|
|
|
// ── AABB3D ───────────────────────────────────────
|
|
py::class_<AABB<double>>(core, "AABB3D",
|
|
R"pbdoc(
|
|
Axis-aligned bounding box with double precision.
|
|
|
|
An empty box has min > max. Call ``expand()`` or construct with min/max points.
|
|
)pbdoc")
|
|
.def(py::init<>(), "Create an empty AABB")
|
|
.def(py::init<const Point3D&, const Point3D&>(),
|
|
py::arg("min"), py::arg("max"),
|
|
"Create from min and max corners")
|
|
.def("expand", py::overload_cast<const Point3D&>(&AABB<double>::expand),
|
|
py::arg("point"), "Expand to include a point")
|
|
.def("expand", py::overload_cast<const AABB<double>&>(&AABB<double>::expand),
|
|
py::arg("other"), "Expand to include another AABB")
|
|
.def_property_readonly("center", &AABB<double>::center)
|
|
.def_property_readonly("extent", &AABB<double>::extent)
|
|
.def_property_readonly("surface_area", &AABB<double>::surface_area)
|
|
.def_property_readonly("volume", &AABB<double>::volume)
|
|
.def_property_readonly("min", &AABB<double>::min)
|
|
.def_property_readonly("max", &AABB<double>::max)
|
|
.def("contains", &AABB<double>::contains,
|
|
py::arg("point"), "Check if point is inside (inclusive)")
|
|
.def("intersects", &AABB<double>::intersects,
|
|
py::arg("other"), "Check if AABBs overlap")
|
|
.def("__repr__", [](const AABB<double>& box) {
|
|
return "AABB3D(min=" + point3d_repr(box.min())
|
|
+ ", max=" + point3d_repr(box.max()) + ")";
|
|
});
|
|
|
|
// ── Polygon2D ────────────────────────────────────
|
|
py::class_<Polygon2D>(core, "Polygon2D",
|
|
R"pbdoc(
|
|
2D polygon defined by a list of vertices.
|
|
)pbdoc")
|
|
.def(py::init<>())
|
|
.def(py::init<std::vector<Point2D>>(),
|
|
py::arg("vertices"), "Construct from vertex list")
|
|
.def_property_readonly("vertices", &Polygon2D::vertices)
|
|
.def("__len__", &Polygon2D::size)
|
|
.def_property_readonly("size", &Polygon2D::size)
|
|
.def_property_readonly("area", &Polygon2D::area)
|
|
.def_property_readonly("signed_area", &Polygon2D::signed_area)
|
|
.def_property_readonly("perimeter", &Polygon2D::perimeter)
|
|
.def_property_readonly("centroid", &Polygon2D::centroid)
|
|
.def_property_readonly("bounding_box", &Polygon2D::bounding_box)
|
|
.def_property_readonly("is_ccw", &Polygon2D::is_ccw)
|
|
.def("contains", &Polygon2D::contains,
|
|
py::arg("point"), "Point-in-polygon test (ray casting)")
|
|
.def("simplify", &Polygon2D::simplify,
|
|
py::arg("tolerance"), "Douglas-Peucker simplification")
|
|
.def("triangulate", &Polygon2D::triangulate,
|
|
"Ear-clipping triangulation. Returns list of [i, j, k] triples.")
|
|
.def_static("convex_hull_2d", &Polygon2D::convex_hull_2d,
|
|
py::arg("points"), "Andrew's monotone chain convex hull")
|
|
.def("__repr__", [](const Polygon2D& poly) {
|
|
return "Polygon2D(vertices=" + std::to_string(poly.size()) + ")";
|
|
});
|
|
|
|
// ── Free functions ───────────────────────────────
|
|
core.def("distance", py::overload_cast<const Point3D&, const Point3D&>(&distance),
|
|
py::arg("a"), py::arg("b"),
|
|
"Euclidean distance between two 3D points");
|
|
|
|
core.def("convex_hull_2d", &convex_hull_2d,
|
|
py::arg("points"),
|
|
"Graham scan 2D convex hull. Returns vertices in CCW order.");
|
|
|
|
core.def("convex_hull_3d", &convex_hull_3d,
|
|
py::arg("points"),
|
|
"QuickHull 3D convex hull. Returns triangles as list of [p0, p1, p2].");
|
|
|
|
// ── Transform helpers ────────────────────────────
|
|
core.def("translate", py::overload_cast<const Vector3D&>(&translate),
|
|
py::arg("v"), "Create translation transform");
|
|
core.def("translate", py::overload_cast<double, double, double>(&translate),
|
|
py::arg("x"), py::arg("y"), py::arg("z"),
|
|
"Create translation transform from x, y, z");
|
|
core.def("rotate", &rotate,
|
|
py::arg("axis"), py::arg("angle_rad"),
|
|
"Create rotation transform around axis by angle (radians)");
|
|
core.def("rotate_x", &rotate_x, py::arg("rad"),
|
|
"Create rotation around X axis");
|
|
core.def("rotate_y", &rotate_y, py::arg("rad"),
|
|
"Create rotation around Y axis");
|
|
core.def("rotate_z", &rotate_z, py::arg("rad"),
|
|
"Create rotation around Z axis");
|
|
core.def("scale", py::overload_cast<double, double, double>(&scale),
|
|
py::arg("sx"), py::arg("sy"), py::arg("sz"),
|
|
"Create non-uniform scale transform");
|
|
core.def("scale", py::overload_cast<double>(&scale),
|
|
py::arg("s"), "Create uniform scale transform");
|
|
}
|