|
|
|
@@ -0,0 +1,151 @@
|
|
|
|
|
#include <pybind11/pybind11.h>
|
|
|
|
|
#include <pybind11/eigen.h>
|
|
|
|
|
#include <pybind11/stl.h>
|
|
|
|
|
#include <pybind11/functional.h>
|
|
|
|
|
|
|
|
|
|
#include "vde/sdf/sdf_tree.h"
|
|
|
|
|
#include "vde/sdf/sdf_to_mesh.h"
|
|
|
|
|
|
|
|
|
|
namespace py = pybind11;
|
|
|
|
|
using namespace vde::sdf;
|
|
|
|
|
|
|
|
|
|
void bind_sdf(py::module& m) {
|
|
|
|
|
auto sdf_m = m.def_submodule("sdf",
|
|
|
|
|
"Signed Distance Field implicit modeling");
|
|
|
|
|
|
|
|
|
|
// ── SdfNode ────────────────────────────────────────
|
|
|
|
|
py::class_<SdfNode, std::shared_ptr<SdfNode>>(sdf_m, "SdfNode",
|
|
|
|
|
R"pbdoc(
|
|
|
|
|
A node in a signed-distance-field CSG expression tree.
|
|
|
|
|
|
|
|
|
|
SdfNode represents either a primitive shape, a boolean CSG operation,
|
|
|
|
|
a distance modifier, or a domain transform. Build trees by combining
|
|
|
|
|
nodes with the factory functions.
|
|
|
|
|
)pbdoc")
|
|
|
|
|
// Primitive factories
|
|
|
|
|
.def_static("sphere", &SdfNode::sphere,
|
|
|
|
|
py::arg("radius"), "Sphere centered at origin")
|
|
|
|
|
.def_static("box", &SdfNode::box,
|
|
|
|
|
py::arg("half_extents"), "Axis-aligned box")
|
|
|
|
|
.def_static("round_box", &SdfNode::round_box,
|
|
|
|
|
py::arg("half_extents"), py::arg("radius"), "Rounded box")
|
|
|
|
|
.def_static("cylinder", &SdfNode::cylinder,
|
|
|
|
|
py::arg("radius"), py::arg("height"), "Capped cylinder along Y")
|
|
|
|
|
.def_static("torus", &SdfNode::torus,
|
|
|
|
|
py::arg("major_radius"), py::arg("minor_radius"), "Torus in XZ plane")
|
|
|
|
|
.def_static("capsule", &SdfNode::capsule,
|
|
|
|
|
py::arg("a"), py::arg("b"), py::arg("radius"), "Capsule from a to b")
|
|
|
|
|
.def_static("cone", &SdfNode::cone,
|
|
|
|
|
py::arg("angle_rad"), py::arg("height"), "Cone along Y axis")
|
|
|
|
|
.def_static("plane", &SdfNode::plane,
|
|
|
|
|
py::arg("normal"), py::arg("offset"), "Infinite plane")
|
|
|
|
|
.def_static("ellipsoid", &SdfNode::ellipsoid,
|
|
|
|
|
py::arg("radii"), "Ellipsoid centered at origin")
|
|
|
|
|
.def_static("triangular_prism", &SdfNode::triangular_prism,
|
|
|
|
|
py::arg("height"), "Triangular prism along Y")
|
|
|
|
|
.def_static("hex_prism", &SdfNode::hex_prism,
|
|
|
|
|
py::arg("height"), "Hexagonal prism along Y")
|
|
|
|
|
.def_static("link", &SdfNode::link,
|
|
|
|
|
py::arg("radius"), py::arg("length"), py::arg("thickness"), "Link segment")
|
|
|
|
|
.def_static("wedge", &SdfNode::wedge,
|
|
|
|
|
py::arg("extents"), "Wedge shape")
|
|
|
|
|
|
|
|
|
|
// Boolean CSG operations
|
|
|
|
|
.def_static("union_", &SdfNode::op_union,
|
|
|
|
|
py::arg("a"), py::arg("b"), "CSG union (min)")
|
|
|
|
|
.def_static("intersection", &SdfNode::op_intersection,
|
|
|
|
|
py::arg("a"), py::arg("b"), "CSG intersection (max)")
|
|
|
|
|
.def_static("difference", &SdfNode::op_difference,
|
|
|
|
|
py::arg("a"), py::arg("b"), "CSG difference (max(a, -b))")
|
|
|
|
|
.def_static("smooth_union", &SdfNode::smooth_union,
|
|
|
|
|
py::arg("a"), py::arg("b"), py::arg("k"), "Smooth blend union")
|
|
|
|
|
.def_static("smooth_intersection", &SdfNode::smooth_intersection,
|
|
|
|
|
py::arg("a"), py::arg("b"), py::arg("k"), "Smooth blend intersection")
|
|
|
|
|
.def_static("smooth_difference", &SdfNode::smooth_difference,
|
|
|
|
|
py::arg("a"), py::arg("b"), py::arg("k"), "Smooth blend difference")
|
|
|
|
|
|
|
|
|
|
// Modifiers
|
|
|
|
|
.def_static("round", &SdfNode::round,
|
|
|
|
|
py::arg("child"), py::arg("radius"), "Offset surface outward")
|
|
|
|
|
.def_static("onion", &SdfNode::onion,
|
|
|
|
|
py::arg("child"), py::arg("thickness"), "Hollow shell")
|
|
|
|
|
|
|
|
|
|
// Domain transforms
|
|
|
|
|
.def_static("repeat", &SdfNode::repeat,
|
|
|
|
|
py::arg("child"), py::arg("cell"), "Infinite repetition")
|
|
|
|
|
.def_static("mirror_x", &SdfNode::mirror_x,
|
|
|
|
|
py::arg("child"), "Mirror across YZ plane")
|
|
|
|
|
.def_static("mirror_y", &SdfNode::mirror_y,
|
|
|
|
|
py::arg("child"), "Mirror across XZ plane")
|
|
|
|
|
.def_static("mirror_z", &SdfNode::mirror_z,
|
|
|
|
|
py::arg("child"), "Mirror across XY plane")
|
|
|
|
|
.def_static("translate", &SdfNode::translate,
|
|
|
|
|
py::arg("child"), py::arg("offset"), "Translate in space")
|
|
|
|
|
.def_static("rotate", &SdfNode::rotate,
|
|
|
|
|
py::arg("child"), py::arg("angle_rad"), "Rotate around Y axis")
|
|
|
|
|
.def_static("scale", &SdfNode::scale,
|
|
|
|
|
py::arg("child"), py::arg("factors"), "Non-uniform scale")
|
|
|
|
|
.def_static("twist", &SdfNode::twist,
|
|
|
|
|
py::arg("child"), py::arg("amount"), "Twist around Y axis")
|
|
|
|
|
.def_static("bend", &SdfNode::bend,
|
|
|
|
|
py::arg("child"), py::arg("amount"), "Bend around Z axis")
|
|
|
|
|
.def_static("elongate", &SdfNode::elongate,
|
|
|
|
|
py::arg("child"), py::arg("h"), "Elongate (stretch space)")
|
|
|
|
|
.def_static("cheap_bend", &SdfNode::cheap_bend,
|
|
|
|
|
py::arg("child"), py::arg("amount"), "Simplified bend")
|
|
|
|
|
.def_static("displace", &SdfNode::displace,
|
|
|
|
|
py::arg("child"), py::arg("amplitude"), py::arg("frequency"),
|
|
|
|
|
"Sinusoidal displacement")
|
|
|
|
|
|
|
|
|
|
// Accessors
|
|
|
|
|
.def_readwrite("name", &SdfNode::name, "Optional label")
|
|
|
|
|
.def("__repr__", [](const SdfNode& n) {
|
|
|
|
|
return "SdfNode(" + n.name + ")";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ── evaluate() ─────────────────────────────────────
|
|
|
|
|
sdf_m.def("evaluate", &evaluate,
|
|
|
|
|
py::arg("node"), py::arg("point"),
|
|
|
|
|
"Evaluate SDF tree at point. Returns signed distance.");
|
|
|
|
|
|
|
|
|
|
sdf_m.def("estimate_bounds", &estimate_bounds,
|
|
|
|
|
py::arg("root"), py::arg("margin") = 1.0,
|
|
|
|
|
"Estimate half-extent bounding radius of SDF tree");
|
|
|
|
|
|
|
|
|
|
// ── SdfBBox ──────────────────────────────────────
|
|
|
|
|
py::class_<SdfBBox>(sdf_m, "SdfBBox")
|
|
|
|
|
.def(py::init<>())
|
|
|
|
|
.def_readwrite("min", &SdfBBox::min)
|
|
|
|
|
.def_readwrite("max", &SdfBBox::max);
|
|
|
|
|
|
|
|
|
|
sdf_m.def("estimate_bbox", &estimate_bbox,
|
|
|
|
|
py::arg("root"), py::arg("margin") = 1.0,
|
|
|
|
|
"Estimate full AABB of SDF tree");
|
|
|
|
|
|
|
|
|
|
// ── sdf_to_mesh() ──────────────────────────────────
|
|
|
|
|
py::class_<vde::mesh::MCMesh>(sdf_m, "MCMesh",
|
|
|
|
|
R"pbdoc(
|
|
|
|
|
Marching cubes mesh result.
|
|
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
|
vertices: N x 3 list of Point3D
|
|
|
|
|
triangles: M x 3 list of index triples
|
|
|
|
|
)pbdoc")
|
|
|
|
|
.def(py::init<>())
|
|
|
|
|
.def_readwrite("vertices", &vde::mesh::MCMesh::vertices)
|
|
|
|
|
.def_readwrite("triangles", &vde::mesh::MCMesh::triangles)
|
|
|
|
|
.def("__repr__", [](const vde::mesh::MCMesh& m) {
|
|
|
|
|
return "MCMesh(v=" + std::to_string(m.vertices.size())
|
|
|
|
|
+ ", t=" + std::to_string(m.triangles.size()) + ")";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
sdf_m.def("sdf_to_mesh", &sdf_to_mesh,
|
|
|
|
|
py::arg("root"), py::arg("resolution") = 64,
|
|
|
|
|
py::arg("iso_level") = 0.0,
|
|
|
|
|
"Convert SDF tree to triangle mesh via marching cubes");
|
|
|
|
|
|
|
|
|
|
sdf_m.def("sdf_to_mesh_lambda", &sdf_to_mesh_lambda,
|
|
|
|
|
py::arg("f"), py::arg("bmin"), py::arg("bmax"),
|
|
|
|
|
py::arg("resolution") = 64, py::arg("iso_level") = 0.0,
|
|
|
|
|
"Convert lambda SDF f(x,y,z) to triangle mesh");
|
|
|
|
|
}
|