Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ loop_cgal/__pycache__/__init__.cpython-311.pyc
build/*
*.vtk
*.ply
build_win64/*
build_win64/*

# Local import shim: symlinks __init__.py / utils.py / build/_loop_cgal.so into
# one directory so `PYTHONPATH=.local-shim` imports the locally built extension.
# Needed because the source `loop_cgal/` package has no .so and shadows the build.
.local-shim/
40 changes: 35 additions & 5 deletions loop_cgal/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ PYBIND11_MODULE(_loop_cgal, m)
.value("KEEP_POSITIVE_SIDE", ImplicitCutMode::KEEP_POSITIVE_SIDE)
.value("KEEP_NEGATIVE_SIDE", ImplicitCutMode::KEEP_NEGATIVE_SIDE)
.export_values();
py::class_<TriMesh>(m, "TriMesh")
py::class_<TriMesh>(m, "TriMesh",
"A CGAL surface mesh of triangles.\n\n"
"NOT THREAD-SAFE. Most methods release the GIL for the duration of the "
"CGAL call, so two Python threads touching the same TriMesh concurrently "
"are a data race in C++ -- corrupt memory, not merely a wrong answer. That "
"includes passing one mesh as the `other`/`surface` argument of a call "
"running on another thread. Give each thread its own meshes, or serialise "
"access yourself. Separate processes (the usual pattern here) are unaffected.")
.def(py::init<const pybind11::array_t<double> &, const pybind11::array_t<int> &>(),
py::arg("vertices"), py::arg("triangles"))
.def(py::init([](const TriMesh& other) { return other.clone(); }),
Expand All @@ -28,13 +35,16 @@ PYBIND11_MODULE(_loop_cgal, m)
.def("clip_with_plane", &TriMesh::clipWithPlane,
py::arg("a"), py::arg("b"), py::arg("c"), py::arg("d"),
py::arg("use_exact_kernel") = true,
py::call_guard<py::gil_scoped_release>(),
"Clip the mesh with the halfspace ax+by+cz+d <= 0. "
"Uses PMP::clip(mesh, Plane_3) directly — no corefinement, no skirt construction. "
"Returns the number of faces removed (0 = no-op).")
.def("corefine", &TriMesh::corefine, py::arg("other"),
py::arg("use_exact_kernel") = true,
py::call_guard<py::gil_scoped_release>(),
"Corefine this mesh with another, inserting the shared intersection "
"polyline into BOTH meshes in place. After the call the two meshes "
"polyline into BOTH meshes in place. NOTE this MUTATES `other` as well "
"as self, so `other` must not be shared with a concurrent call. After the call the two meshes "
"carry coincident vertices/edges along the intersection curve, so "
"patches taken from each side stitch together watertight. "
"Returns the number of vertices added to this mesh. "
Expand All @@ -44,23 +54,31 @@ PYBIND11_MODULE(_loop_cgal, m)
py::arg("preserve_intersection") = false,
py::arg("preserve_intersection_clipper") = false,
py::arg("use_exact_kernel") = true,
py::arg("extend") = true,
py::call_guard<py::gil_scoped_release>(),
"Cut mesh with a clipper surface. Returns the number of faces removed "
"(0 indicates the clipper did not intersect or did not extend beyond the mesh).")
"(0 indicates the clipper did not intersect or did not extend beyond the mesh). "
"extend=True grows a skirt so a small clipper cuts all the way through; pass "
"extend=False when the clipper already spans the domain. "
"Mutates self and reads `surface`; neither may be touched by a concurrent call.")
.def("remesh", &TriMesh::remesh, py::arg("split_long_edges") = true,
py::arg("target_edge_length") = 10.0,
py::arg("number_of_iterations") = 3,
py::arg("protect_constraints") = true,
py::arg("relax_constraints") = false)
py::arg("relax_constraints") = false,
py::call_guard<py::gil_scoped_release>())
.def("save", &TriMesh::save, py::arg("area_threshold") = 1e-6,
py::arg("duplicate_vertex_threshold") = 1e-6)
.def("reverse_face_orientation", &TriMesh::reverseFaceOrientation,
"Reverse the face orientation of the mesh.")
.def("add_fixed_edges", &TriMesh::add_fixed_edges,
py::arg("pairs"),
py::call_guard<py::gil_scoped_release>(),
"Vertex index pairs defining edges to be fixed in mesh when remeshing.")
.def("cut_with_implicit_function", &TriMesh::cut_with_implicit_function,
py::arg("property"), py::arg("value"),py::arg("cutmode") = ImplicitCutMode::KEEP_POSITIVE_SIDE,
py::arg("snap_tol") = 1e-4,
py::call_guard<py::gil_scoped_release>(),
"Cut the mesh with an implicit function defined by vertex properties. "
"snap_tol reuses an existing vertex when a crossing lands within that "
"fraction of an edge from it, preventing sliver triangles (0 = disable).")
Expand All @@ -69,7 +87,19 @@ PYBIND11_MODULE(_loop_cgal, m)
.def("_cgal_n_vertices", &TriMesh::n_vertices, "Number of vertices in the CGAL mesh.")
.def("_cgal_points", &TriMesh::get_points, "Vertex coordinates as (n, 3) numpy array.")
.def("overlaps", &TriMesh::overlaps, py::arg("other"), py::arg("bbox_tol") = 1e-6,
"Return True if this mesh intersects another TriMesh (AABB fast-reject + triangle-level check).")
py::call_guard<py::gil_scoped_release>(),
"Return True if this mesh intersects another TriMesh (AABB fast-reject + triangle-level check). "
"Reads both meshes; neither may be mutated by a concurrent call.")
.def("does_self_intersect", &TriMesh::does_self_intersect,
py::call_guard<py::gil_scoped_release>(),
"Return True if the mesh intersects itself.\n\n"
"Wraps CGAL PMP::does_self_intersect. A self-intersecting surface is "
"outside the domain of the boolean operations - corefine, cut_with_surface "
"and the clip paths may fail or return a corrupt result on one - so this is "
"the check to run before handing a mesh to them, and after a remesh or a fit "
"that could have folded it.\n\n"
"Cost is O(n log n) via an AABB tree over the faces: cheap enough to assert "
"on, not free in a tight loop.")
.def("clone", &TriMesh::clone,
"Return a deep copy of this TriMesh at the C++ level (no pyvista round-trip). "
"Preserves the full fixed-edge set including any user-added edges.")
Expand Down
Loading
Loading