Files
ViewDesignEngine/python/src/bind_core.cpp
T
茂之钳 bd37f2249d
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 32s
feat: Python 绑定 — pybind11 核心/曲线/网格模块
- 使用 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 启用
2026-07-24 03:58:19 +00:00

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");
}