From 2bdeaebe287e5502dae86053246f5aa5f6df29c5 Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Fri, 20 Jun 2025 11:27:56 -0600 Subject: [PATCH 1/4] chore: Refactor bindings and header files for improved readability and organization --- loop_cgal/bindings.cpp | 85 ++++++++++++++++++----------------------- src/clip.h | 86 +++++++++++++++++------------------------- src/numpymesh.h | 14 +++---- 3 files changed, 78 insertions(+), 107 deletions(-) diff --git a/loop_cgal/bindings.cpp b/loop_cgal/bindings.cpp index ed2fc7f..f34dba9 100644 --- a/loop_cgal/bindings.cpp +++ b/loop_cgal/bindings.cpp @@ -5,53 +5,42 @@ namespace py = pybind11; -PYBIND11_MODULE(loop_cgal, m) -{ - - m.def("clip_surface", &clip_surface, - py::arg("tm"), - py::arg("clipper"), - py::arg("target_edge_length") = 10.0, - py::arg("remesh_before_clipping") = true, - py::arg("remesh_after_clipping") = true, - py::arg("remove_degenerate_faces") = true, - py::arg("duplicate_vertex_threshold") = 1e-6, - py::arg("area_threshold") = 1e-6, - py::arg("protect_constraints") = false, - py::arg("relax_constraints") = true, - py::arg("verbose") = false, - "Clip one surface with another."); - m.def("clip_plane", &clip_plane, - py::arg("tm"), - py::arg("clipper"), - py::arg("target_edge_length") = 10.0, - py::arg("remesh_before_clipping") = true, - py::arg("remesh_after_clipping") = true, - py::arg("remove_degenerate_faces") = true, - py::arg("duplicate_vertex_threshold") = 1e-6, - py::arg("area_threshold") = 1e-6, - py::arg("protect_constraints") = false, - py::arg("relax_constraints") = true, - py::arg("verbose") = false, +PYBIND11_MODULE(loop_cgal, m) { - "Clip a surface with a plane."); - m.def("corefine_mesh", &corefine_mesh, - py::arg("tm1"), - py::arg("tm2"), - py::arg("target_edge_length") = 10.0, - py::arg("duplicate_vertex_threshold") = 1e-6, - py::arg("area_threshold") = 1e-6, - py::arg("number_of_iterations") = 3, - py::arg("relax_constraints") = true, - py::arg("protect_constraints") = false, - py::arg("verbose") = false, - "Corefine two meshes."); - py::class_(m, "NumpyMesh") - .def(py::init<>()) - .def_readwrite("vertices", &NumpyMesh::vertices) - .def_readwrite("triangles", &NumpyMesh::triangles); - py::class_(m, "NumpyPlane") - .def(py::init<>()) - .def_readwrite("normal", &NumpyPlane::normal) - .def_readwrite("origin", &NumpyPlane::origin); + m.def("clip_surface", &clip_surface, py::arg("tm"), py::arg("clipper"), + py::arg("target_edge_length") = 10.0, + py::arg("remesh_before_clipping") = true, + py::arg("remesh_after_clipping") = true, + py::arg("remove_degenerate_faces") = true, + py::arg("duplicate_vertex_threshold") = 1e-6, + py::arg("area_threshold") = 1e-6, + py::arg("protect_constraints") = false, + py::arg("relax_constraints") = true, py::arg("verbose") = false, + "Clip one surface with another."); + m.def("clip_plane", &clip_plane, py::arg("tm"), py::arg("clipper"), + py::arg("target_edge_length") = 10.0, + py::arg("remesh_before_clipping") = true, + py::arg("remesh_after_clipping") = true, + py::arg("remove_degenerate_faces") = true, + py::arg("duplicate_vertex_threshold") = 1e-6, + py::arg("area_threshold") = 1e-6, + py::arg("protect_constraints") = false, + py::arg("relax_constraints") = true, py::arg("verbose") = false, + + "Clip a surface with a plane."); + m.def("corefine_mesh", &corefine_mesh, py::arg("tm1"), py::arg("tm2"), + py::arg("target_edge_length") = 10.0, + py::arg("duplicate_vertex_threshold") = 1e-6, + py::arg("area_threshold") = 1e-6, py::arg("number_of_iterations") = 3, + py::arg("relax_constraints") = true, + py::arg("protect_constraints") = false, py::arg("verbose") = false, + "Corefine two meshes."); + py::class_(m, "NumpyMesh") + .def(py::init<>()) + .def_readwrite("vertices", &NumpyMesh::vertices) + .def_readwrite("triangles", &NumpyMesh::triangles); + py::class_(m, "NumpyPlane") + .def(py::init<>()) + .def_readwrite("normal", &NumpyPlane::normal) + .def_readwrite("origin", &NumpyPlane::origin); } \ No newline at end of file diff --git a/src/clip.h b/src/clip.h index 88d24a5..d136c70 100644 --- a/src/clip.h +++ b/src/clip.h @@ -1,11 +1,11 @@ #ifndef CLIP_H #define CLIP_H -#include #include "numpymesh.h" +#include #include #include -#include #include +#include typedef CGAL::Simple_cartesian Kernel; typedef Kernel::Point_3 Point; @@ -13,55 +13,39 @@ typedef CGAL::Surface_mesh TriangleMesh; typedef CGAL::Plane_3 Plane; typedef CGAL::Vector_3 Vector; std::set collect_border_edges(const TriangleMesh &tm); -double calculate_triangle_area( - const std::array &v1, - const std::array &v2, - const std::array &v3); -NumpyMesh clip_surface( - NumpyMesh tm, - NumpyMesh clipper, - double target_edge_length = 10.0, bool remesh_before_clipping = true, - bool remesh_after_clipping = true, - bool remove_degenerate_faces = true, - double duplicate_vertex_threshold = 1e-6, - double area_threshold = 1e-6, - bool protect_constraints = true, - bool relax_constraints = false,bool verbose = false); -NumpyMesh clip_plane( - NumpyMesh tm, - NumpyPlane clipper, - double target_edge_length = 10.0, - bool remesh_before_clipping = true, - bool remesh_after_clipping = true, - bool remove_degenerate_faces = true, - double duplicate_vertex_threshold = 1e-6, - double area_threshold = 1e-6, - bool protect_constraints = true, - bool relax_constraints = false, - bool verbose = false); +double calculate_triangle_area(const std::array &v1, + const std::array &v2, + const std::array &v3); +NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, + double target_edge_length = 10.0, + bool remesh_before_clipping = true, + bool remesh_after_clipping = true, + bool remove_degenerate_faces = true, + double duplicate_vertex_threshold = 1e-6, + double area_threshold = 1e-6, + bool protect_constraints = true, + bool relax_constraints = false, bool verbose = false); +NumpyMesh clip_plane(NumpyMesh tm, NumpyPlane clipper, + double target_edge_length = 10.0, + bool remesh_before_clipping = true, + bool remesh_after_clipping = true, + bool remove_degenerate_faces = true, + double duplicate_vertex_threshold = 1e-6, + double area_threshold = 1e-6, + bool protect_constraints = true, + bool relax_constraints = false, bool verbose = false); TriangleMesh load_mesh(NumpyMesh mesh, bool verbose = false); Plane load_plane(NumpyPlane plane, bool verbose = false); -void refine_mesh(TriangleMesh &mesh, - bool split_long_edges = true, - bool verbose = false, - double target_edge_length = 10.0, - int number_of_iterations = 1, - bool protect_constraints = true, - bool relax_constraints = false -); -NumpyMesh export_mesh(const TriangleMesh &tm, - double area_threshold, - double duplicate_vertex_threshold, - bool verbose = false -); -std::vector corefine_mesh( - NumpyMesh tm1, - NumpyMesh tm2, - double target_edge_length = 10.0, - double duplicate_vertex_threshold = 1e-6, - double area_threshold = 1e-6, - int number_of_iterations = 3, - bool relax_constraints = true, - bool protect_constraints = false, - bool verbose = false); +void refine_mesh(TriangleMesh &mesh, bool split_long_edges = true, + bool verbose = false, double target_edge_length = 10.0, + int number_of_iterations = 1, bool protect_constraints = true, + bool relax_constraints = false); +NumpyMesh export_mesh(const TriangleMesh &tm, double area_threshold, + double duplicate_vertex_threshold, bool verbose = false); +std::vector +corefine_mesh(NumpyMesh tm1, NumpyMesh tm2, double target_edge_length = 10.0, + double duplicate_vertex_threshold = 1e-6, + double area_threshold = 1e-6, int number_of_iterations = 3, + bool relax_constraints = true, bool protect_constraints = false, + bool verbose = false); #endif \ No newline at end of file diff --git a/src/numpymesh.h b/src/numpymesh.h index f914545..e42ae87 100644 --- a/src/numpymesh.h +++ b/src/numpymesh.h @@ -1,14 +1,12 @@ #ifndef NUMPYMESH_H #define NUMPYMESH_H #include -struct NumpyMesh -{ - pybind11::array_t vertices; - pybind11::array_t triangles; +struct NumpyMesh { + pybind11::array_t vertices; + pybind11::array_t triangles; }; -struct NumpyPlane -{ - pybind11::array_t normal; // Normal vector of the plane - pybind11::array_t origin; // A point on the plane +struct NumpyPlane { + pybind11::array_t normal; // Normal vector of the plane + pybind11::array_t origin; // A point on the plane }; #endif // NUMPYMESH_H \ No newline at end of file From 43d49add2d0703e0180b08b608e57423cb40514a Mon Sep 17 00:00:00 2001 From: Robin Thibaut Date: Fri, 1 Aug 2025 14:14:26 -0600 Subject: [PATCH 2/4] refactor: silence logs. improve code formatting and organization in mesh-related files --- loop_cgal/__init__.py | 16 +- src/clip.cpp | 100 +++++------ src/mesh.cpp | 402 ++++++++++++++++++++---------------------- src/mesh.h | 59 +++---- src/meshutils.cpp | 237 ++++++++++++------------- 5 files changed, 388 insertions(+), 426 deletions(-) diff --git a/loop_cgal/__init__.py b/loop_cgal/__init__.py index 3c38cbc..5c078c2 100644 --- a/loop_cgal/__init__.py +++ b/loop_cgal/__init__.py @@ -1,9 +1,13 @@ -from .loop_cgal import clip_surface, NumpyMesh, NumpyPlane, clip_plane, corefine_mesh -from .loop_cgal import TriMesh as _TriMesh -import pyvista as pv -import numpy as np +from __future__ import annotations + from typing import Tuple +import numpy as np +import pyvista as pv + +from .loop_cgal import NumpyMesh, NumpyPlane, clip_plane, clip_surface, corefine_mesh +from .loop_cgal import TriMesh as _TriMesh + class TriMesh(_TriMesh): """ A class for handling triangular meshes using CGAL. @@ -148,9 +152,7 @@ def clip_pyvista_polydata( relax_constraints=relax_constraints, verbose=verbose, ) - out = pv.PolyData.from_regular_faces(mesh.vertices, mesh.triangles) - - return out + return pv.PolyData.from_regular_faces(mesh.vertices, mesh.triangles) def corefine_pyvista_polydata( diff --git a/src/clip.cpp b/src/clip.cpp index c9179a2..57b2625 100644 --- a/src/clip.cpp +++ b/src/clip.cpp @@ -1,6 +1,6 @@ #include "clip.h" -#include "numpymesh.h" #include "meshutils.h" +#include "numpymesh.h" #include #include #include @@ -16,8 +16,6 @@ namespace PMP = CGAL::Polygon_mesh_processing; using face_descriptor = TriangleMesh::Face_index; - - TriangleMesh load_mesh(NumpyMesh mesh, bool verbose) { TriangleMesh tm; auto vertices_buf = mesh.vertices.unchecked<2>(); @@ -96,7 +94,7 @@ void refine_mesh(TriangleMesh &mesh, bool split_long_edges, bool verbose, std::cout << " edge length range: [" << min_e << ", " << max_e << "] target = " << target_edge_length << '\n'; - if (!CGAL::is_valid_polygon_mesh(mesh,verbose) && verbose) + if (!CGAL::is_valid_polygon_mesh(mesh, verbose) && verbose) std::cout << " ! mesh is not a valid polygon mesh\n"; // ------------------------------------------------------------------ @@ -146,12 +144,10 @@ void refine_mesh(TriangleMesh &mesh, bool split_long_edges, bool verbose, if (verbose) std::cout << "Refined mesh → " << mesh.number_of_vertices() << " V, " << mesh.number_of_faces() << " F\n"; - if (!CGAL::is_valid_polygon_mesh(mesh,verbose) && verbose) + if (!CGAL::is_valid_polygon_mesh(mesh, verbose) && verbose) std::cout << " ! mesh is not a valid polygon mesh after remeshing\n"; } - - bool plane_cuts_mesh(const TriangleMesh &mesh, const Plane &P) { bool has_pos = false, has_neg = false; @@ -208,8 +204,7 @@ NumpyMesh clip_plane(NumpyMesh tm, NumpyPlane clipper, if (verbose) { std::cout << "Clipping tm with clipper." << std::endl; } - bool flag = PMP::clip( - _tm, _clipper, CGAL::parameters::clip_volume(false)); + bool flag = PMP::clip(_tm, _clipper, CGAL::parameters::clip_volume(false)); // PMP::triangulate_faces(_tm); if (verbose) { std::cout << "Clipping done." << std::endl; @@ -226,12 +221,10 @@ NumpyMesh clip_plane(NumpyMesh tm, NumpyPlane clipper, if (verbose) std::cout << " – stitching borders…" << std::endl; - PMP::stitch_borders( - _tm); + PMP::stitch_borders(_tm); if (verbose) std::cout << " – merging dup vertices…" << std::endl; - PMP:: - merge_duplicated_vertices_in_boundary_cycles(_tm); + PMP::merge_duplicated_vertices_in_boundary_cycles(_tm); if (verbose) std::cout << " – isotropic remeshing…" << std::endl; refine_mesh(_tm, true, verbose, target_edge_length, @@ -249,20 +242,18 @@ NumpyMesh clip_plane(NumpyMesh tm, NumpyPlane clipper, std::set protected_edges = collect_border_edges(_tm); #if CGAL_VERSION_NR >= 1060000000 - bool beautify_flag = - PMP::remove_almost_degenerate_faces( - faces(_tm), _tm, - CGAL::parameters::edge_is_constrained_map( - CGAL::make_boolean_property_map(protected_edges))); + bool beautify_flag = PMP::remove_almost_degenerate_faces( + faces(_tm), _tm, + CGAL::parameters::edge_is_constrained_map( + CGAL::make_boolean_property_map(protected_edges))); #else - bool beautify_flag = - PMP::remove_degenerate_faces( - faces(_tm), _tm, - CGAL::parameters::edge_is_constrained_map( - CGAL::make_boolean_property_map(protected_edges))); + bool beautify_flag = PMP::remove_degenerate_faces( + faces(_tm), _tm, + CGAL::parameters::edge_is_constrained_map( + CGAL::make_boolean_property_map(protected_edges))); #endif if (!beautify_flag) { - std::cout << "Removing degenerate faces failed." << std::endl; + std::cerr << "Removing degenerate faces failed." << std::endl; } if (verbose) { std::cout << "Removing degenerate faces done." << std::endl; @@ -270,7 +261,8 @@ NumpyMesh clip_plane(NumpyMesh tm, NumpyPlane clipper, } } } else { - std::cout << "Meshes do not intersect. Returning tm." << std::endl; + if (verbose) + std::cout << "Meshes do not intersect. Returning tm." << std::endl; } if (verbose) { std::cout << "Clipping done." << std::endl; @@ -304,11 +296,12 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, } PMP::remove_isolated_vertices(_tm); PMP::remove_isolated_vertices(_clipper); - if (!CGAL::is_valid_polygon_mesh(_tm,verbose)) { + if (!CGAL::is_valid_polygon_mesh(_tm, verbose)) { std::cerr << "tm is invalid!" << std::endl; - CGAL::is_valid_polygon_mesh(_tm,true); + if (verbose) + CGAL::is_valid_polygon_mesh(_tm, true); } - if (!CGAL::is_valid_polygon_mesh(_clipper,verbose)) { + if (!CGAL::is_valid_polygon_mesh(_clipper, verbose)) { std::cerr << "clipper is invalid!" << std::endl; } // Parameters for isotropic remeshing @@ -320,7 +313,8 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, refine_mesh(_tm, true, verbose, target_edge_length, number_of_iterations, protect_constraints, relax_constraints); // refine_mesh(_clipper, true, verbose, target_edge_length, - // number_of_iterations, protect_constraints, relax_constraints); + // number_of_iterations, protect_constraints, + // relax_constraints); if (verbose) { std::cout << "Remeshing before clipping done." << std::endl; @@ -328,8 +322,7 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, } // make sure the meshes actually intersect. If they don't, just return mesh 1 - bool intersection = - PMP::do_intersect(_tm, _clipper); + bool intersection = PMP::do_intersect(_tm, _clipper); if (intersection) { // Clip tm with clipper if (verbose) { @@ -351,12 +344,10 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, } if (verbose) std::cout << " – stitching borders…" << std::endl; - PMP::stitch_borders( - _tm); + PMP::stitch_borders(_tm); if (verbose) std::cout << " – merging dup vertices…" << std::endl; - PMP:: - merge_duplicated_vertices_in_boundary_cycles(_tm); + PMP::merge_duplicated_vertices_in_boundary_cycles(_tm); if (verbose) std::cout << " – isotropic remeshing…" << std::endl; refine_mesh(_tm, true, verbose, target_edge_length, @@ -375,20 +366,18 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, collect_border_edges(_tm); #if CGAL_VERSION_NR >= 1060000000 - bool beautify_flag = - PMP::remove_almost_degenerate_faces( - faces(_tm), _tm, - CGAL::parameters::edge_is_constrained_map( - CGAL::make_boolean_property_map(protected_edges))); + bool beautify_flag = PMP::remove_almost_degenerate_faces( + faces(_tm), _tm, + CGAL::parameters::edge_is_constrained_map( + CGAL::make_boolean_property_map(protected_edges))); #else - bool beautify_flag = - PMP::remove_degenerate_faces( - faces(_tm), _tm, - CGAL::parameters::edge_is_constrained_map( - CGAL::make_boolean_property_map(protected_edges))); + bool beautify_flag = PMP::remove_degenerate_faces( + faces(_tm), _tm, + CGAL::parameters::edge_is_constrained_map( + CGAL::make_boolean_property_map(protected_edges))); #endif if (!beautify_flag) { - std::cout << "Removing degenerate faces failed." << std::endl; + std::cerr << "Removing degenerate faces failed." << std::endl; } if (verbose) { std::cout << "Removing degenerate faces done." << std::endl; @@ -396,7 +385,8 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, } } } else { - std::cout << "Meshes do not intersect. Returning tm." << std::endl; + if (verbose) + std::cout << "Meshes do not intersect. Returning tm." << std::endl; } if (verbose) { std::cout << "Clipping done." << std::endl; @@ -422,10 +412,8 @@ corefine_mesh(NumpyMesh tm1, NumpyMesh tm2, double target_edge_length, // Load the meshes TriangleMesh _tm1 = load_mesh(tm1, false); TriangleMesh _tm2 = load_mesh(tm2, false); - PMP::split_long_edges(edges(_tm1), - target_edge_length, _tm1); - PMP::split_long_edges(edges(_tm2), - target_edge_length, _tm2); + PMP::split_long_edges(edges(_tm1), target_edge_length, _tm1); + PMP::split_long_edges(edges(_tm2), target_edge_length, _tm2); // Perform corefinement PMP::corefine(_tm1, _tm2); @@ -448,9 +436,10 @@ corefine_mesh(NumpyMesh tm1, NumpyMesh tm2, double target_edge_length, } } } - std::cout << "Found " << tm_1_shared_edges.size() - << " shared edges in tm1 and " << tm_2_shared_edges.size() - << " shared edges in tm2." << std::endl; + if (verbose) + std::cout << "Found " << tm_1_shared_edges.size() + << " shared edges in tm1 and " << tm_2_shared_edges.size() + << " shared edges in tm2." << std::endl; // std::set constrained_edges; @@ -480,7 +469,8 @@ corefine_mesh(NumpyMesh tm1, NumpyMesh tm2, double target_edge_length, .relax_constraints(relax_constraints) .protect_constraints(protect_constraints)); - std::cout << "Corefinement done." << std::endl; + if (verbose) + std::cout << "Corefinement done." << std::endl; return { export_mesh(_tm1, area_threshold, duplicate_vertex_threshold, verbose), diff --git a/src/mesh.cpp b/src/mesh.cpp index fed011c..267f5fc 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -16,243 +16,231 @@ namespace PMP = CGAL::Polygon_mesh_processing; TriMesh::TriMesh(const std::vector> &triangles, - const std::vector> &vertices - ) -{ - std::vector vertex_indices; - bool verbose = true; - if (verbose) - { - std::cout << "Loading mesh with " << vertices.size() - << " vertices and " << triangles.size() << " triangles." - << std::endl; - } - - // Assemble CGAL mesh objects from numpy/pybind11 arrays - for (ssize_t i = 0; i < vertices.size(); ++i) - { - vertex_indices.push_back(_mesh.add_vertex( - Point(vertices[i].first, vertices[i].second, 0.0))); - } - for (ssize_t i = 0; i < triangles.size(); ++i) - { - _mesh.add_face(vertex_indices[triangles[i][0]], - vertex_indices[triangles[i][1]], - vertex_indices[triangles[i][2]]); - } - - if (verbose) - { - std::cout << "Loaded mesh with " << _mesh.number_of_vertices() - << " vertices and " << _mesh.number_of_faces() << " faces." - << std::endl; - } - init(); + const std::vector> &vertices) { + std::vector vertex_indices; + bool verbose = false; // Default to false for non-verbose constructor + if (verbose) { + std::cout << "Loading mesh with " << vertices.size() << " vertices and " + << triangles.size() << " triangles." << std::endl; + } + + // Assemble CGAL mesh objects from numpy/pybind11 arrays + for (ssize_t i = 0; i < vertices.size(); ++i) { + vertex_indices.push_back( + _mesh.add_vertex(Point(vertices[i].first, vertices[i].second, 0.0))); + } + for (ssize_t i = 0; i < triangles.size(); ++i) { + _mesh.add_face(vertex_indices[triangles[i][0]], + vertex_indices[triangles[i][1]], + vertex_indices[triangles[i][2]]); + } + + if (verbose) { + std::cout << "Loaded mesh with " << _mesh.number_of_vertices() + << " vertices and " << _mesh.number_of_faces() << " faces." + << std::endl; + } + init(); } TriMesh::TriMesh(const pybind11::array_t &vertices, - const pybind11::array_t &triangles) -{ - auto verts = vertices.unchecked<2>(); - auto tris = triangles.unchecked<2>(); + const pybind11::array_t &triangles) { + auto verts = vertices.unchecked<2>(); + auto tris = triangles.unchecked<2>(); - std::vector vertex_indices; + std::vector vertex_indices; - for (ssize_t i = 0; i < verts.shape(0); ++i) - { - vertex_indices.push_back(_mesh.add_vertex( - Point(verts(i, 0), verts(i, 1), verts(i, 2)))); - } + for (ssize_t i = 0; i < verts.shape(0); ++i) { + vertex_indices.push_back( + _mesh.add_vertex(Point(verts(i, 0), verts(i, 1), verts(i, 2)))); + } - for (ssize_t i = 0; i < tris.shape(0); ++i) - { - _mesh.add_face(vertex_indices[tris(i, 0)], - vertex_indices[tris(i, 1)], - vertex_indices[tris(i, 2)]); - } + for (ssize_t i = 0; i < tris.shape(0); ++i) { + _mesh.add_face(vertex_indices[tris(i, 0)], vertex_indices[tris(i, 1)], + vertex_indices[tris(i, 2)]); + } - std::cout << "Loaded mesh with " << _mesh.number_of_vertices() - << " vertices and " << _mesh.number_of_faces() << " faces." << std::endl; - init(); + // No verbose logging in this constructor since verbose parameter is not + // available + init(); } -void TriMesh::init(){ - _fixedEdges = collect_border_edges(_mesh); - std::cout << "Found " << _fixedEdges.size() << " fixed edges." << std::endl; - _edge_is_constrained_map = CGAL::make_boolean_property_map(_fixedEdges); +void TriMesh::init() { + _fixedEdges = collect_border_edges(_mesh); + _edge_is_constrained_map = CGAL::make_boolean_property_map(_fixedEdges); } - void TriMesh::add_fixed_edges(const pybind11::array_t &pairs) { - if (!CGAL::is_valid_polygon_mesh(_mesh)) - { - std::cerr << "Mesh is not valid!" << std::endl; + if (!CGAL::is_valid_polygon_mesh(_mesh)) { + std::cerr << "Mesh is not valid!" << std::endl; + } + // Convert std::set> to std::set + auto pairs_buf = pairs.unchecked<2>(); + + for (ssize_t i = 0; i < pairs_buf.shape(0); ++i) { + TriangleMesh::Vertex_index v0 = TriangleMesh::Vertex_index(pairs_buf(i, 1)); + TriangleMesh::Vertex_index v1 = TriangleMesh::Vertex_index(pairs_buf(i, 0)); + if (!_mesh.is_valid(v0) || !_mesh.is_valid(v1)) { + std::cerr << "Invalid vertex indices: (" << v0 << ", " << v1 << ")" + << std::endl; + continue; // Skip invalid vertex pairs + } + TriangleMesh::Halfedge_index edge = + _mesh.halfedge(TriangleMesh::Vertex_index(pairs_buf(i, 0)), + TriangleMesh::Vertex_index(pairs_buf(i, 1))); + if (edge == TriangleMesh::null_halfedge()) { + std::cerr << "Half-edge is null for vertices (" << v1 << ", " << v0 << ")" + << std::endl; + continue; } - // Convert std::set> to std::set - auto pairs_buf = pairs.unchecked<2>(); - - for (ssize_t i = 0; i < pairs_buf.shape(0); ++i) { - TriangleMesh::Vertex_index v0 = TriangleMesh::Vertex_index(pairs_buf(i, 1)); - TriangleMesh::Vertex_index v1 = TriangleMesh::Vertex_index(pairs_buf(i, 0)); - if (!_mesh.is_valid(v0) || !_mesh.is_valid(v1)) { - std::cerr << "Invalid vertex indices: (" << v0 << ", " << v1 << ")" << std::endl; - continue; // Skip invalid vertex pairs - } - TriangleMesh::Halfedge_index edge = _mesh.halfedge(TriangleMesh::Vertex_index(pairs_buf(i, 0)), - TriangleMesh::Vertex_index(pairs_buf(i, 1))); - if (edge == TriangleMesh::null_halfedge()) - { - std::cerr << "Half-edge is null for vertices (" << v1 << ", " << v0 << ")" << std::endl; - continue; - } - if (!_mesh.is_valid(edge)) { - std::cerr << "Invalid half-edge for vertices (" << v0 << ", " << v1 << ")" << std::endl; - continue; // Skip invalid edges - } - TriangleMesh::Edge_index e = _mesh.edge(edge); - - _fixedEdges.insert(e); - // if (e.is_valid()) { - // _fixedEdges.insert(e); - // } else { - // std::cerr << "Warning: Edge (" << edge[0] << ", " << edge[1] << ") is not valid in the mesh." << std::endl; - // } + if (!_mesh.is_valid(edge)) { + std::cerr << "Invalid half-edge for vertices (" << v0 << ", " << v1 << ")" + << std::endl; + continue; // Skip invalid edges } - // // Update the property map with the new fixed edges - _edge_is_constrained_map = CGAL::make_boolean_property_map(_fixedEdges); + TriangleMesh::Edge_index e = _mesh.edge(edge); + + _fixedEdges.insert(e); + // if (e.is_valid()) { + // _fixedEdges.insert(e); + // } else { + // std::cerr << "Warning: Edge (" << edge[0] << ", " << edge[1] << + // ") is not valid in the mesh." << std::endl; + // } + } + // // Update the property map with the new fixed edges + _edge_is_constrained_map = CGAL::make_boolean_property_map(_fixedEdges); } void TriMesh::remesh(bool split_long_edges, bool verbose, double target_edge_length, int number_of_iterations, bool protect_constraints, bool relax_constraints) { - - // ------------------------------------------------------------------ - // 0. Guard‑rail: sensible target length w.r.t. bbox - // ------------------------------------------------------------------ - CGAL::Bbox_3 bb = PMP::bbox(_mesh); - const double bbox_diag = std::sqrt(CGAL::square(bb.xmax() - bb.xmin()) + - CGAL::square(bb.ymax() - bb.ymin()) + - CGAL::square(bb.zmax() - bb.zmin())); - PMP::remove_isolated_vertices(_mesh); - if (target_edge_length < 1e-4 * bbox_diag) - { - if (verbose) - std::cout << " ! target_edge_length (" << target_edge_length - << ") too small – skipping remesh\n"; - return; - } - // ------------------------------------------------------------------ - // 1. Quick diagnostics - // ------------------------------------------------------------------ - double min_e = std::numeric_limits::max(), max_e = 0.0; - for (auto e : _mesh.edges()) - { - const double l = PMP::edge_length(e, _mesh); - min_e = std::min(min_e, l); - max_e = std::max(max_e, l); - } + // ------------------------------------------------------------------ + // 0. Guard‑rail: sensible target length w.r.t. bbox + // ------------------------------------------------------------------ + CGAL::Bbox_3 bb = PMP::bbox(_mesh); + const double bbox_diag = std::sqrt(CGAL::square(bb.xmax() - bb.xmin()) + + CGAL::square(bb.ymax() - bb.ymin()) + + CGAL::square(bb.zmax() - bb.zmin())); + PMP::remove_isolated_vertices(_mesh); + if (target_edge_length < 1e-4 * bbox_diag) { if (verbose) - std::cout << " edge length range: [" << min_e << ", " << max_e - << "] target = " << target_edge_length << '\n'; - - if (!CGAL::is_valid_polygon_mesh(_mesh, verbose) && verbose) - std::cout << " ! mesh is not a valid polygon mesh\n"; - - // ------------------------------------------------------------------ - // 2. Abort when self‑intersections remain - // ------------------------------------------------------------------ - // std::vector> overlaps; - // PMP::self_intersections(mesh, std::back_inserter(overlaps)); - // if (!overlaps.empty()) { - // if (verbose) - // std::cout << " --> " << overlaps.size() - // << " self‑intersections – remesh skipped\n"; - // return; - // } - - // ------------------------------------------------------------------ - // 3. “Tiny patch” bailout: only split long edges - // ------------------------------------------------------------------ - // const std::size_t n_faces = _mesh.number_of_faces(); - // if (n_faces < 40) - // { - // if (split_long_edges) - // PMP::split_long_edges(edges(_mesh), target_edge_length, _mesh); - // if (verbose) - // std::cout << " → tiny patch (" << n_faces - // << " faces) – isotropic remesh skipped\n"; - // return; - // } - - // ------------------------------------------------------------------ - // 4. Normal isotropic remeshing loop - // ------------------------------------------------------------------ - // Convert _fixedEdges to a compatible property map - - // Update remeshing calls to use the property map - if (split_long_edges) - if (verbose) - std::cout << "Splitting long edges before remeshing.\n"; - PMP::split_long_edges(edges(_mesh), target_edge_length, _mesh, CGAL::parameters::edge_is_constrained_map(_edge_is_constrained_map)); - - for (int iter = 0; iter < number_of_iterations; ++iter) - { - if (split_long_edges) - if (verbose) - std::cout << "Splitting long edges in iteration " << iter + 1 << ".\n"; - PMP::split_long_edges(edges(_mesh), target_edge_length, _mesh, CGAL::parameters::edge_is_constrained_map(_edge_is_constrained_map)); - if (verbose) - std::cout << "Remeshing iteration " << iter + 1 << " of " - << number_of_iterations << ".\n"; - PMP::isotropic_remeshing( - faces(_mesh), target_edge_length, _mesh, - CGAL::parameters::number_of_iterations(1) // one sub‑iteration per loop - .edge_is_constrained_map(_edge_is_constrained_map) - .protect_constraints(protect_constraints) - .relax_constraints(relax_constraints)); + std::cout << " ! target_edge_length (" << target_edge_length + << ") too small – skipping remesh\n"; + return; + } + + // ------------------------------------------------------------------ + // 1. Quick diagnostics + // ------------------------------------------------------------------ + double min_e = std::numeric_limits::max(), max_e = 0.0; + for (auto e : _mesh.edges()) { + const double l = PMP::edge_length(e, _mesh); + min_e = std::min(min_e, l); + max_e = std::max(max_e, l); + } + if (verbose) + std::cout << " edge length range: [" << min_e << ", " << max_e + << "] target = " << target_edge_length << '\n'; + + if (!CGAL::is_valid_polygon_mesh(_mesh, verbose) && verbose) + std::cout << " ! mesh is not a valid polygon mesh\n"; + + // ------------------------------------------------------------------ + // 2. Abort when self‑intersections remain + // ------------------------------------------------------------------ + // std::vector> overlaps; + // PMP::self_intersections(mesh, std::back_inserter(overlaps)); + // if (!overlaps.empty()) { + // if (verbose) + // std::cout << " --> " << overlaps.size() + // << " self‑intersections – remesh skipped\n"; + // return; + // } + + // ------------------------------------------------------------------ + // 3. “Tiny patch” bailout: only split long edges + // ------------------------------------------------------------------ + // const std::size_t n_faces = _mesh.number_of_faces(); + // if (n_faces < 40) + // { + // if (split_long_edges) + // PMP::split_long_edges(edges(_mesh), target_edge_length, _mesh); + // if (verbose) + // std::cout << " → tiny patch (" << n_faces + // << " faces) – isotropic remesh skipped\n"; + // return; + // } + + // ------------------------------------------------------------------ + // 4. Normal isotropic remeshing loop + // ------------------------------------------------------------------ + // Convert _fixedEdges to a compatible property map + + // Update remeshing calls to use the property map + if (split_long_edges) { + if (verbose) + std::cout << "Splitting long edges before remeshing.\n"; + PMP::split_long_edges( + edges(_mesh), target_edge_length, _mesh, + CGAL::parameters::edge_is_constrained_map(_edge_is_constrained_map)); + } + + for (int iter = 0; iter < number_of_iterations; ++iter) { + if (split_long_edges) { + if (verbose) + std::cout << "Splitting long edges in iteration " << iter + 1 << ".\n"; + PMP::split_long_edges( + edges(_mesh), target_edge_length, _mesh, + CGAL::parameters::edge_is_constrained_map(_edge_is_constrained_map)); } - - - if (verbose) - std::cout << "Refined mesh → " << _mesh.number_of_vertices() << " V, " - << _mesh.number_of_faces() << " F\n"; - if (!CGAL::is_valid_polygon_mesh(_mesh, verbose) && verbose) - std::cout << " ! mesh is not a valid polygon mesh after remeshing\n"; + std::cout << "Remeshing iteration " << iter + 1 << " of " + << number_of_iterations << ".\n"; + PMP::isotropic_remeshing( + faces(_mesh), target_edge_length, _mesh, + CGAL::parameters::number_of_iterations(1) // one sub‑iteration per loop + .edge_is_constrained_map(_edge_is_constrained_map) + .protect_constraints(protect_constraints) + .relax_constraints(relax_constraints)); + } + + if (verbose) + std::cout << "Refined mesh → " << _mesh.number_of_vertices() << " V, " + << _mesh.number_of_faces() << " F\n"; + if (!CGAL::is_valid_polygon_mesh(_mesh, verbose) && verbose) + std::cout << " ! mesh is not a valid polygon mesh after remeshing\n"; } void TriMesh::reverseFaceOrientation() { - // Reverse the face orientation of the mesh - PMP::reverse_face_orientations(_mesh); - if (!CGAL::is_valid_polygon_mesh(_mesh, true)) - { - std::cerr << "Error: Mesh is not valid after reversing face orientations." << std::endl; - } + // Reverse the face orientation of the mesh + PMP::reverse_face_orientations(_mesh); + if (!CGAL::is_valid_polygon_mesh(_mesh, false)) { + std::cerr << "Error: Mesh is not valid after reversing face orientations." + << std::endl; + } } -void TriMesh::cutWithSurface(TriMesh &clipper, bool verbose, bool preserve_intersection, bool preserve_intersection_clipper) { - if (verbose) - { - std::cout << "Cutting mesh with surface." << std::endl; +void TriMesh::cutWithSurface(TriMesh &clipper, bool verbose, + bool preserve_intersection, + bool preserve_intersection_clipper) { + if (verbose) { + std::cout << "Cutting mesh with surface." << std::endl; + } + bool intersection = PMP::do_intersect(_mesh, clipper._mesh); + if (intersection) { + // Clip tm with clipper + if (verbose) { + std::cout << "Clipping tm with clipper." << std::endl; } - bool intersection = - PMP::do_intersect(_mesh, clipper._mesh); - if (intersection) - { - // Clip tm with clipper - if (verbose) - { - std::cout << "Clipping tm with clipper." << std::endl; - } - bool flag = PMP::clip(_mesh, clipper._mesh, CGAL::parameters::clip_volume(false)); - - -} + bool flag = + PMP::clip(_mesh, clipper._mesh, CGAL::parameters::clip_volume(false)); + } } NumpyMesh TriMesh::save(double area_threshold, - double duplicate_vertex_threshold, bool verbose) -{ - return export_mesh(_mesh, area_threshold, duplicate_vertex_threshold, verbose); + double duplicate_vertex_threshold, bool verbose) { + return export_mesh(_mesh, area_threshold, duplicate_vertex_threshold, + verbose); } \ No newline at end of file diff --git a/src/mesh.h b/src/mesh.h index c1f1a9a..784e00a 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -1,50 +1,49 @@ #ifndef MESH_H #define MESH_H -#include -#include // For std::pair -#include -#include #include #include #include #include #include +#include +#include +#include // For std::pair +#include typedef CGAL::Simple_cartesian Kernel; typedef Kernel::Point_3 Point; typedef CGAL::Surface_mesh TriangleMesh; typedef CGAL::Plane_3 Plane; typedef CGAL::Vector_3 Vector; -class TriMesh -{ +class TriMesh { public: - // Constructor - TriMesh(const std::vector> &triangles, - const std::vector> &vertices); - TriMesh(const pybind11::array_t &vertices, - const pybind11::array_t &triangles); - - // Method to cut the mesh with another surface object - void cutWithSurface(TriMesh &surface, bool verbose = false, - bool preserve_intersection = false, - bool preserve_intersection_clipper = false); + // Constructor + TriMesh(const std::vector> &triangles, + const std::vector> &vertices); + TriMesh(const pybind11::array_t &vertices, + const pybind11::array_t &triangles); - // Method to remesh the triangle mesh - void remesh(bool split_long_edges, bool verbose, - double target_edge_length, int number_of_iterations, - bool protect_constraints, bool relax_constraints); - void init(); - // Getters for mesh properties - void reverseFaceOrientation(); - NumpyMesh save(double area_threshold, - double duplicate_vertex_threshold, bool verbose = false); - void add_fixed_edges(const pybind11::array_t &pairs); + // Method to cut the mesh with another surface object + void cutWithSurface(TriMesh &surface, bool verbose = false, + bool preserve_intersection = false, + bool preserve_intersection_clipper = false); - private: - std::set _fixedEdges; - TriangleMesh _mesh; // The underlying CGAL surface mesh - CGAL::Boolean_property_map> _edge_is_constrained_map; + // Method to remesh the triangle mesh + void remesh(bool split_long_edges, bool verbose, double target_edge_length, + int number_of_iterations, bool protect_constraints, + bool relax_constraints); + void init(); + // Getters for mesh properties + void reverseFaceOrientation(); + NumpyMesh save(double area_threshold, double duplicate_vertex_threshold, + bool verbose = false); + void add_fixed_edges(const pybind11::array_t &pairs); +private: + std::set _fixedEdges; + TriangleMesh _mesh; // The underlying CGAL surface mesh + CGAL::Boolean_property_map> + _edge_is_constrained_map; }; #endif // MESH_HANDLER_H \ No newline at end of file diff --git a/src/meshutils.cpp b/src/meshutils.cpp index 2eee6e5..17911a0 100644 --- a/src/meshutils.cpp +++ b/src/meshutils.cpp @@ -2,143 +2,126 @@ #include "mesh.h" std::set -collect_border_edges(const TriangleMesh &tm) -{ - std::set border_edges; - for (const auto &halfedge : tm.halfedges()) - { - if (tm.is_border(halfedge)) - { - border_edges.insert(CGAL::edge(halfedge, tm)); // Convert halfedge to edge - } +collect_border_edges(const TriangleMesh &tm) { + std::set border_edges; + for (const auto &halfedge : tm.halfedges()) { + if (tm.is_border(halfedge)) { + border_edges.insert(CGAL::edge(halfedge, tm)); // Convert halfedge to edge } - return border_edges; + } + return border_edges; } double calculate_triangle_area(const std::array &v1, const std::array &v2, - const std::array &v3) -{ - // Compute vectors - std::array vec1 = {v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]}; - std::array vec2 = {v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2]}; - - // Compute cross product - std::array cross_product = {vec1[1] * vec2[2] - vec1[2] * vec2[1], - vec1[2] * vec2[0] - vec1[0] * vec2[2], - vec1[0] * vec2[1] - vec1[1] * vec2[0]}; - - // Compute magnitude of cross product - double magnitude = std::sqrt(cross_product[0] * cross_product[0] + - cross_product[1] * cross_product[1] + - cross_product[2] * cross_product[2]); - - // Area is half the magnitude of the cross product - return 0.5 * magnitude; + const std::array &v3) { + // Compute vectors + std::array vec1 = {v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]}; + std::array vec2 = {v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2]}; + + // Compute cross product + std::array cross_product = {vec1[1] * vec2[2] - vec1[2] * vec2[1], + vec1[2] * vec2[0] - vec1[0] * vec2[2], + vec1[0] * vec2[1] - vec1[1] * vec2[0]}; + + // Compute magnitude of cross product + double magnitude = std::sqrt(cross_product[0] * cross_product[0] + + cross_product[1] * cross_product[1] + + cross_product[2] * cross_product[2]); + + // Area is half the magnitude of the cross product + return 0.5 * magnitude; } // --------------------------------------------------------------------------- // Efficient export: linear‑time duplicate detection via quantised hash grid // --------------------------------------------------------------------------- NumpyMesh export_mesh(const TriangleMesh &tm, double area_threshold, - double duplicate_vertex_threshold, bool verbose) -{ - using VIndex = TriangleMesh::Vertex_index; - - std::vector> vertices; // unique coords - std::vector> triangles; // face indices - std::map vertex_index_map; // CGAL → compact - - // —‑‑‑‑‑ 1. Build unique‑vertex list ---------------------------------- - struct QKey - { - long long x, y, z; - bool operator==(const QKey &o) const - { - return x == o.x && y == o.y && z == o.z; - } - }; - struct QHash - { - std::size_t operator()(const QKey &k) const noexcept - { - std::size_t h1 = std::hash{}(k.x); - std::size_t h2 = std::hash{}(k.y); - std::size_t h3 = std::hash{}(k.z); - return h1 ^ (h2 << 1) ^ (h3 << 2); - } - }; - - const double inv = 1.0 / duplicate_vertex_threshold; // quantisation - std::unordered_map qmap; // grid → index - - int next_idx = 0; - for (VIndex v : tm.vertices()) - { - const auto &p = tm.point(v); - QKey key{llround(p.x() * inv), llround(p.y() * inv), llround(p.z() * inv)}; - - auto it = qmap.find(key); - if (it == qmap.end()) - { // first occurrence → store - qmap[key] = next_idx; - vertices.push_back({p.x(), p.y(), p.z()}); - vertex_index_map[v] = next_idx++; - } - else - { // duplicate → alias - vertex_index_map[v] = it->second; - } + double duplicate_vertex_threshold, bool verbose) { + using VIndex = TriangleMesh::Vertex_index; + + std::vector> vertices; // unique coords + std::vector> triangles; // face indices + std::map vertex_index_map; // CGAL → compact + + // —‑‑‑‑‑ 1. Build unique‑vertex list ---------------------------------- + struct QKey { + long long x, y, z; + bool operator==(const QKey &o) const { + return x == o.x && y == o.y && z == o.z; } - - if (verbose) - { - std::cout << "Vertices after remeshing: " << vertices.size() << '\n'; - std::cout << "Duplicate‑detection grid cells: " << qmap.size() << '\n'; - } - - // —‑‑‑‑‑ 2. Build triangle list, skipping tiny faces ------------------ - for (auto f : tm.faces()) - { - std::array tri; - int k = 0; - for (auto he : CGAL::halfedges_around_face(tm.halfedge(f), tm)) - tri[k++] = vertex_index_map[CGAL::target(he, tm)]; - - double area = calculate_triangle_area(vertices[tri[0]], vertices[tri[1]], - vertices[tri[2]]); - - if (area >= area_threshold) - triangles.push_back(tri); - else if (verbose) - std::cout << "Skipping degenerate face (A=" << area << ")\n"; + }; + struct QHash { + std::size_t operator()(const QKey &k) const noexcept { + std::size_t h1 = std::hash{}(k.x); + std::size_t h2 = std::hash{}(k.y); + std::size_t h3 = std::hash{}(k.z); + return h1 ^ (h2 << 1) ^ (h3 << 2); } - - if (verbose) - std::cout << "Kept " << triangles.size() << " triangles.\n"; - - // —‑‑‑‑‑ 3. Convert to NumPy arrays ----------------------------------- - pybind11::array_t vertices_array( - {static_cast(vertices.size()), 3}); - auto vbuf = vertices_array.mutable_unchecked<2>(); - for (size_t i = 0; i < vertices.size(); ++i) - { - vbuf(i, 0) = vertices[i][0]; - vbuf(i, 1) = vertices[i][1]; - vbuf(i, 2) = vertices[i][2]; + }; + + const double inv = 1.0 / duplicate_vertex_threshold; // quantisation + std::unordered_map qmap; // grid → index + + int next_idx = 0; + for (VIndex v : tm.vertices()) { + const auto &p = tm.point(v); + QKey key{llround(p.x() * inv), llround(p.y() * inv), llround(p.z() * inv)}; + + auto it = qmap.find(key); + if (it == qmap.end()) { // first occurrence → store + qmap[key] = next_idx; + vertices.push_back({p.x(), p.y(), p.z()}); + vertex_index_map[v] = next_idx++; + } else { // duplicate → alias + vertex_index_map[v] = it->second; } - - pybind11::array_t triangles_array( - {static_cast(triangles.size()), 3}); - auto tbuf = triangles_array.mutable_unchecked<2>(); - for (size_t i = 0; i < triangles.size(); ++i) - { - tbuf(i, 0) = triangles[i][0]; - tbuf(i, 1) = triangles[i][1]; - tbuf(i, 2) = triangles[i][2]; - } - - // —‑‑‑‑‑ 4. Package & return ------------------------------------------ - NumpyMesh result; - result.vertices = vertices_array; - result.triangles = triangles_array; - return result; + } + + if (verbose) { + std::cout << "Vertices after remeshing: " << vertices.size() << '\n'; + std::cout << "Duplicate‑detection grid cells: " << qmap.size() << '\n'; + } + + // —‑‑‑‑‑ 2. Build triangle list, skipping tiny faces ------------------ + for (auto f : tm.faces()) { + std::array tri; + int k = 0; + for (auto he : CGAL::halfedges_around_face(tm.halfedge(f), tm)) + tri[k++] = vertex_index_map[CGAL::target(he, tm)]; + + double area = calculate_triangle_area(vertices[tri[0]], vertices[tri[1]], + vertices[tri[2]]); + + if (area >= area_threshold) + triangles.push_back(tri); + else if (verbose) + std::cout << "Skipping degenerate face (A=" << area << ")\n"; + } + + if (verbose) + std::cout << "Kept " << triangles.size() << " triangles.\n"; + + // —‑‑‑‑‑ 3. Convert to NumPy arrays ----------------------------------- + pybind11::array_t vertices_array( + {static_cast(vertices.size()), 3}); + auto vbuf = vertices_array.mutable_unchecked<2>(); + for (size_t i = 0; i < vertices.size(); ++i) { + vbuf(i, 0) = vertices[i][0]; + vbuf(i, 1) = vertices[i][1]; + vbuf(i, 2) = vertices[i][2]; + } + + pybind11::array_t triangles_array( + {static_cast(triangles.size()), 3}); + auto tbuf = triangles_array.mutable_unchecked<2>(); + for (size_t i = 0; i < triangles.size(); ++i) { + tbuf(i, 0) = triangles[i][0]; + tbuf(i, 1) = triangles[i][1]; + tbuf(i, 2) = triangles[i][2]; + } + + // —‑‑‑‑‑ 4. Package & return ------------------------------------------ + NumpyMesh result; + result.vertices = vertices_array; + result.triangles = triangles_array; + return result; } \ No newline at end of file From c48394a4b9e96030bb651617c48727569a53651a Mon Sep 17 00:00:00 2001 From: Lachlan grose Date: Tue, 5 Aug 2025 11:10:42 +1000 Subject: [PATCH 3/4] fix: adding global verbose flag --- src/globals.h | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/globals.h diff --git a/src/globals.h b/src/globals.h new file mode 100644 index 0000000..357d091 --- /dev/null +++ b/src/globals.h @@ -0,0 +1,9 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +namespace LoopCGAL +{ + extern bool verbose; // Declaration of the module-wide verbose flag +} + +#endif // GLOBALS_H \ No newline at end of file From b291e03051cceba71efc62c396e30c1b3ab56010 Mon Sep 17 00:00:00 2001 From: Lachlan grose Date: Tue, 5 Aug 2025 15:00:30 +1000 Subject: [PATCH 4/4] fix: use global verbose flag instead of per function variable to set verbose mode on loop_cgal.set_verbose(True) --- CMakeLists.txt | 11 ++-- examples/clip_v2.py | 8 +-- loop_cgal/__init__.py | 20 ++---- loop_cgal/bindings.cpp | 12 ++-- src/clip.cpp | 18 ++++-- src/globals.cpp | 13 ++++ src/globals.h | 1 + src/mesh.cpp | 141 ++++++++++++++++++++++++++--------------- src/mesh.h | 48 +++++++------- src/meshutils.cpp | 10 +-- src/meshutils.h | 2 +- 11 files changed, 167 insertions(+), 117 deletions(-) create mode 100644 src/globals.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d4bad1..2edc071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,17 +26,18 @@ include_directories(${CMAKE_SOURCE_DIR}/src) find_package(pybind11 REQUIRED) # Add the Python module -add_library(loop_cgal MODULE +add_library(_loop_cgal MODULE loop_cgal/bindings.cpp src/clip.cpp src/mesh.cpp src/meshutils.cpp + src/globals.cpp ) -target_link_libraries(loop_cgal PRIVATE pybind11::module CGAL::CGAL) -target_include_directories(loop_cgal PRIVATE ${CMAKE_SOURCE_DIR}/src) -set_target_properties(loop_cgal PROPERTIES PREFIX "" SUFFIX ".so") +target_link_libraries(_loop_cgal PRIVATE pybind11::module CGAL::CGAL) +target_include_directories(_loop_cgal PRIVATE ${CMAKE_SOURCE_DIR}/src) +set_target_properties(_loop_cgal PROPERTIES PREFIX "" SUFFIX ".so") # Install the Python module to the correct location -install(TARGETS loop_cgal +install(TARGETS _loop_cgal LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/loop_cgal ) \ No newline at end of file diff --git a/examples/clip_v2.py b/examples/clip_v2.py index b5877c4..8d1acf0 100644 --- a/examples/clip_v2.py +++ b/examples/clip_v2.py @@ -2,10 +2,11 @@ import numpy as np import pyvista as pv from LoopStructural.datatypes import BoundingBox +loop_cgal.set_verbose(True) +print(loop_cgal.verbose) # Should print True def test(): bb = BoundingBox(np.zeros(3), np.ones(3)) grid = bb.structured_grid().vtk() - print(grid) grid["scalars"] = grid.points[:,0] surface = grid.contour([.5]) surface_1_tri = surface.faces.reshape(-1, 4)[:, 1:].copy() @@ -18,12 +19,9 @@ def test(): # surface_cgal.remesh(target_edge_length=0.02, verbose=True,protect_constraints=True, relax_constraints=False, number_of_iterations=1,split_long_edges=False) surface_cgal_2 = loop_cgal.TriMesh(surface_2) # surface_cgal_2.remesh(target_edge_length=0.02, verbose=True,protect_constraints=True, relax_constraints=False, number_of_iterations=1,split_long_edges=False) - print("clipping 1") - surface_cgal.cut_with_surface(surface_cgal_2,verbose=True, preserve_intersection=False, preserve_intersection_clipper=False) + surface_cgal.cut_with_surface(surface_cgal_2, preserve_intersection=False, preserve_intersection_clipper=False) - print("reverse face orientation") surface_cgal_2.reverse_face_orientation() - print("clipping 3") surface_cgal_3 = loop_cgal.TriMesh(surface) # surface_cgal_3.remesh(target_edge_length=0.02, verbose=True,protect_constraints=True, relax_constraints=False, number_of_iterations=1,split_long_edges=False) surface_cgal_3.cut_with_surface(surface_cgal_2) diff --git a/loop_cgal/__init__.py b/loop_cgal/__init__.py index 5c078c2..163a304 100644 --- a/loop_cgal/__init__.py +++ b/loop_cgal/__init__.py @@ -5,9 +5,10 @@ import numpy as np import pyvista as pv -from .loop_cgal import NumpyMesh, NumpyPlane, clip_plane, clip_surface, corefine_mesh -from .loop_cgal import TriMesh as _TriMesh - +from ._loop_cgal import NumpyMesh, NumpyPlane, clip_plane, clip_surface, corefine_mesh +from ._loop_cgal import TriMesh as _TriMesh +from ._loop_cgal import verbose +from ._loop_cgal import set_verbose as set_verbose class TriMesh(_TriMesh): """ A class for handling triangular meshes using CGAL. @@ -21,7 +22,7 @@ def __init__(self, surface: pv.PolyData): def to_pyvista(self, area_threshold: float = 1e-6, # this is the area threshold for the faces, if the area is smaller than this it will be removed duplicate_vertex_threshold: float = 1e-4, # this is the threshold for duplicate vertices - verbose: bool = False) -> pv.PolyData: + ) -> pv.PolyData: """ Convert the TriMesh to a pyvista PolyData object. @@ -30,7 +31,7 @@ def to_pyvista(self, area_threshold: float = 1e-6, # this is the area threshold pyvista.PolyData The converted PolyData object. """ - np_mesh = self.save(area_threshold, duplicate_vertex_threshold, verbose) + np_mesh = self.save(area_threshold, duplicate_vertex_threshold) vertices = np.array(np_mesh.vertices).copy() triangles = np.array(np_mesh.triangles).copy() return pv.PolyData.from_regular_faces(vertices, triangles) @@ -47,7 +48,6 @@ def clip_pyvista_polydata_with_plane( area_threshold: float = 0.0001, protect_constraints: bool = False, relax_constraints: bool = True, - verbose: bool = False, ) -> pv.PolyData: """ Clip a pyvista PolyData object with a plane using the CGAL library. @@ -72,8 +72,7 @@ def clip_pyvista_polydata_with_plane( The threshold for merging duplicate vertices, by default 0.001 area_threshold : float, optional The area threshold for removing small faces, by default 0.0001 - verbose : bool, optional - Whether to print verbose output, by default False + Returns ------- pyvista.PolyData @@ -98,7 +97,6 @@ def clip_pyvista_polydata_with_plane( area_threshold=area_threshold, protect_constraints=protect_constraints, relax_constraints=relax_constraints, - verbose=verbose, ) return pv.PolyData.from_regular_faces(mesh.vertices, mesh.triangles) @@ -114,7 +112,6 @@ def clip_pyvista_polydata( area_threshold: float = 0.0001, protect_constraints: bool = False, relax_constraints: bool = True, - verbose: bool = False, ) -> pv.PolyData: """ Clip two pyvista PolyData objects using the CGAL library. @@ -150,7 +147,6 @@ def clip_pyvista_polydata( area_threshold=area_threshold, protect_constraints=protect_constraints, relax_constraints=relax_constraints, - verbose=verbose, ) return pv.PolyData.from_regular_faces(mesh.vertices, mesh.triangles) @@ -164,7 +160,6 @@ def corefine_pyvista_polydata( number_of_iterations: int = 10, protect_constraints: bool = True, relax_constraints: bool = True, - verbose: bool = False, ) -> Tuple[pv.PolyData, pv.PolyData]: """ Corefine two pyvista PolyData objects using the CGAL library. @@ -199,7 +194,6 @@ def corefine_pyvista_polydata( number_of_iterations=number_of_iterations, relax_constraints=relax_constraints, protect_constraints=protect_constraints, - verbose=verbose, ) return ( pv.PolyData.from_regular_faces(tm1.vertices, tm1.triangles), diff --git a/loop_cgal/bindings.cpp b/loop_cgal/bindings.cpp index 97f0d02..62bb964 100644 --- a/loop_cgal/bindings.cpp +++ b/loop_cgal/bindings.cpp @@ -4,12 +4,13 @@ #include "clip.h" // Include the API implementation #include "mesh.h" #include "numpymesh.h" - +#include "globals.h" // Include the global verbose flag namespace py = pybind11; -PYBIND11_MODULE(loop_cgal, m) +PYBIND11_MODULE(_loop_cgal, m) { - + m.attr("verbose") = &LoopCGAL::verbose; // Expose the global verbose flag + m.def("set_verbose", &LoopCGAL::set_verbose, "Set the verbose flag"); m.def("clip_surface", &clip_surface, py::arg("tm"), py::arg("clipper"), py::arg("target_edge_length") = 10.0, py::arg("remesh_before_clipping") = true, @@ -49,18 +50,15 @@ PYBIND11_MODULE(loop_cgal, m) .def(py::init &, const pybind11::array_t &>(), py::arg("vertices"), py::arg("triangles")) .def("cut_with_surface", &TriMesh::cutWithSurface, py::arg("surface"), - py::arg("verbose") = false, py::arg("preserve_intersection") = false, py::arg("preserve_intersection_clipper") = false) .def("remesh", &TriMesh::remesh, py::arg("split_long_edges") = true, - py::arg("verbose") = false, py::arg("target_edge_length") = 10.0, py::arg("number_of_iterations") = 3, py::arg("protect_constraints") = true, py::arg("relax_constraints") = false) .def("save", &TriMesh::save, py::arg("area_threshold") = 1e-6, - py::arg("duplicate_vertex_threshold") = 1e-6, - py::arg("verbose") = false) + 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, diff --git a/src/clip.cpp b/src/clip.cpp index 57b2625..f11689d 100644 --- a/src/clip.cpp +++ b/src/clip.cpp @@ -271,7 +271,7 @@ NumpyMesh clip_plane(NumpyMesh tm, NumpyPlane clipper, // store the result in a numpymesh object for sending back to Python NumpyMesh result = - export_mesh(_tm, area_threshold, duplicate_vertex_threshold, verbose); + export_mesh(_tm, area_threshold, duplicate_vertex_threshold); if (verbose) { std::cout << "Exported clipped mesh with " << result.vertices.shape(0) << " vertices and " << result.triangles.shape(0) << " triangles." @@ -299,7 +299,9 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, if (!CGAL::is_valid_polygon_mesh(_tm, verbose)) { std::cerr << "tm is invalid!" << std::endl; if (verbose) + { CGAL::is_valid_polygon_mesh(_tm, true); + } } if (!CGAL::is_valid_polygon_mesh(_clipper, verbose)) { std::cerr << "clipper is invalid!" << std::endl; @@ -386,7 +388,9 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, } } else { if (verbose) + { std::cout << "Meshes do not intersect. Returning tm." << std::endl; + } } if (verbose) { std::cout << "Clipping done." << std::endl; @@ -395,7 +399,7 @@ NumpyMesh clip_surface(NumpyMesh tm, NumpyMesh clipper, // store the result in a numpymesh object for sending back to Python NumpyMesh result = - export_mesh(_tm, area_threshold, duplicate_vertex_threshold, verbose); + export_mesh(_tm, area_threshold, duplicate_vertex_threshold); if (verbose) { std::cout << "Exported clipped mesh with " << result.vertices.shape(0) << " vertices and " << result.triangles.shape(0) << " triangles." @@ -437,10 +441,11 @@ corefine_mesh(NumpyMesh tm1, NumpyMesh tm2, double target_edge_length, } } if (verbose) + { std::cout << "Found " << tm_1_shared_edges.size() << " shared edges in tm1 and " << tm_2_shared_edges.size() << " shared edges in tm2." << std::endl; - + } // std::set constrained_edges; std::set boundary_edges = @@ -470,9 +475,10 @@ corefine_mesh(NumpyMesh tm1, NumpyMesh tm2, double target_edge_length, .protect_constraints(protect_constraints)); if (verbose) + { std::cout << "Corefinement done." << std::endl; - + } return { - export_mesh(_tm1, area_threshold, duplicate_vertex_threshold, verbose), - export_mesh(_tm2, area_threshold, duplicate_vertex_threshold, verbose)}; + export_mesh(_tm1, area_threshold, duplicate_vertex_threshold), + export_mesh(_tm2, area_threshold, duplicate_vertex_threshold)}; } diff --git a/src/globals.cpp b/src/globals.cpp new file mode 100644 index 0000000..6f06b67 --- /dev/null +++ b/src/globals.cpp @@ -0,0 +1,13 @@ +#include "globals.h" +#include + +namespace LoopCGAL +{ + bool verbose = false; // Definition of the verbose flag + + void set_verbose(bool value) + { + verbose = value; + std::cout << "Verbose flag set to: " << verbose << std::endl; + } +} \ No newline at end of file diff --git a/src/globals.h b/src/globals.h index 357d091..e166401 100644 --- a/src/globals.h +++ b/src/globals.h @@ -4,6 +4,7 @@ namespace LoopCGAL { extern bool verbose; // Declaration of the module-wide verbose flag + void set_verbose(bool value); // Declaration of the set_verbose function } #endif // GLOBALS_H \ No newline at end of file diff --git a/src/mesh.cpp b/src/mesh.cpp index 267f5fc..237ba92 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1,5 +1,6 @@ #include "mesh.h" #include "meshutils.h" +#include "globals.h" #include #include #include @@ -16,26 +17,31 @@ namespace PMP = CGAL::Polygon_mesh_processing; TriMesh::TriMesh(const std::vector> &triangles, - const std::vector> &vertices) { + const std::vector> &vertices) +{ + std::vector vertex_indices; - bool verbose = false; // Default to false for non-verbose constructor - if (verbose) { + if (LoopCGAL::verbose) + { std::cout << "Loading mesh with " << vertices.size() << " vertices and " << triangles.size() << " triangles." << std::endl; } // Assemble CGAL mesh objects from numpy/pybind11 arrays - for (ssize_t i = 0; i < vertices.size(); ++i) { + for (ssize_t i = 0; i < vertices.size(); ++i) + { vertex_indices.push_back( _mesh.add_vertex(Point(vertices[i].first, vertices[i].second, 0.0))); } - for (ssize_t i = 0; i < triangles.size(); ++i) { + for (ssize_t i = 0; i < triangles.size(); ++i) + { _mesh.add_face(vertex_indices[triangles[i][0]], vertex_indices[triangles[i][1]], vertex_indices[triangles[i][2]]); } - if (verbose) { + if (LoopCGAL::verbose) + { std::cout << "Loaded mesh with " << _mesh.number_of_vertices() << " vertices and " << _mesh.number_of_faces() << " faces." << std::endl; @@ -44,42 +50,60 @@ TriMesh::TriMesh(const std::vector> &triangles, } TriMesh::TriMesh(const pybind11::array_t &vertices, - const pybind11::array_t &triangles) { + const pybind11::array_t &triangles) +{ auto verts = vertices.unchecked<2>(); auto tris = triangles.unchecked<2>(); - std::vector vertex_indices; - for (ssize_t i = 0; i < verts.shape(0); ++i) { + for (ssize_t i = 0; i < verts.shape(0); ++i) + { vertex_indices.push_back( _mesh.add_vertex(Point(verts(i, 0), verts(i, 1), verts(i, 2)))); } - for (ssize_t i = 0; i < tris.shape(0); ++i) { + for (ssize_t i = 0; i < tris.shape(0); ++i) + { _mesh.add_face(vertex_indices[tris(i, 0)], vertex_indices[tris(i, 1)], vertex_indices[tris(i, 2)]); } + if (LoopCGAL::verbose) + { + std::cout << "Loaded mesh with " << _mesh.number_of_vertices() + << " vertices and " << _mesh.number_of_faces() << " faces." + << std::endl; + } - // No verbose logging in this constructor since verbose parameter is not - // available init(); } -void TriMesh::init() { + +void TriMesh::init() +{ _fixedEdges = collect_border_edges(_mesh); + + + if (LoopCGAL::verbose) + { + std::cout << "Found " << _fixedEdges.size() << " fixed edges." << std::endl; + } _edge_is_constrained_map = CGAL::make_boolean_property_map(_fixedEdges); } -void TriMesh::add_fixed_edges(const pybind11::array_t &pairs) { - if (!CGAL::is_valid_polygon_mesh(_mesh)) { +void TriMesh::add_fixed_edges(const pybind11::array_t &pairs) +{ + if (!CGAL::is_valid_polygon_mesh(_mesh, LoopCGAL::verbose)) + { std::cerr << "Mesh is not valid!" << std::endl; } // Convert std::set> to std::set auto pairs_buf = pairs.unchecked<2>(); - for (ssize_t i = 0; i < pairs_buf.shape(0); ++i) { + for (ssize_t i = 0; i < pairs_buf.shape(0); ++i) + { TriangleMesh::Vertex_index v0 = TriangleMesh::Vertex_index(pairs_buf(i, 1)); TriangleMesh::Vertex_index v1 = TriangleMesh::Vertex_index(pairs_buf(i, 0)); - if (!_mesh.is_valid(v0) || !_mesh.is_valid(v1)) { + if (!_mesh.is_valid(v0) || !_mesh.is_valid(v1)) + { std::cerr << "Invalid vertex indices: (" << v0 << ", " << v1 << ")" << std::endl; continue; // Skip invalid vertex pairs @@ -87,12 +111,14 @@ void TriMesh::add_fixed_edges(const pybind11::array_t &pairs) { TriangleMesh::Halfedge_index edge = _mesh.halfedge(TriangleMesh::Vertex_index(pairs_buf(i, 0)), TriangleMesh::Vertex_index(pairs_buf(i, 1))); - if (edge == TriangleMesh::null_halfedge()) { + if (edge == TriangleMesh::null_halfedge()) + { std::cerr << "Half-edge is null for vertices (" << v1 << ", " << v0 << ")" << std::endl; continue; } - if (!_mesh.is_valid(edge)) { + if (!_mesh.is_valid(edge)) // Check if the halfedge is valid + { std::cerr << "Invalid half-edge for vertices (" << v0 << ", " << v1 << ")" << std::endl; continue; // Skip invalid edges @@ -110,7 +136,7 @@ void TriMesh::add_fixed_edges(const pybind11::array_t &pairs) { // // Update the property map with the new fixed edges _edge_is_constrained_map = CGAL::make_boolean_property_map(_fixedEdges); } -void TriMesh::remesh(bool split_long_edges, bool verbose, +void TriMesh::remesh(bool split_long_edges, double target_edge_length, int number_of_iterations, bool protect_constraints, bool relax_constraints) @@ -124,8 +150,9 @@ void TriMesh::remesh(bool split_long_edges, bool verbose, CGAL::square(bb.ymax() - bb.ymin()) + CGAL::square(bb.zmax() - bb.zmin())); PMP::remove_isolated_vertices(_mesh); - if (target_edge_length < 1e-4 * bbox_diag) { - if (verbose) + if (target_edge_length < 1e-4 * bbox_diag) + { + if (LoopCGAL::verbose) std::cout << " ! target_edge_length (" << target_edge_length << ") too small – skipping remesh\n"; return; @@ -135,18 +162,21 @@ void TriMesh::remesh(bool split_long_edges, bool verbose, // 1. Quick diagnostics // ------------------------------------------------------------------ double min_e = std::numeric_limits::max(), max_e = 0.0; - for (auto e : _mesh.edges()) { + for (auto e : _mesh.edges()) + { const double l = PMP::edge_length(e, _mesh); min_e = std::min(min_e, l); max_e = std::max(max_e, l); } - if (verbose) + if (LoopCGAL::verbose) + { std::cout << " edge length range: [" << min_e << ", " << max_e << "] target = " << target_edge_length << '\n'; - - if (!CGAL::is_valid_polygon_mesh(_mesh, verbose) && verbose) + } + if (!CGAL::is_valid_polygon_mesh(_mesh, LoopCGAL::verbose) && LoopCGAL::verbose) + { std::cout << " ! mesh is not a valid polygon mesh\n"; - + } // ------------------------------------------------------------------ // 2. Abort when self‑intersections remain // ------------------------------------------------------------------ @@ -179,23 +209,23 @@ void TriMesh::remesh(bool split_long_edges, bool verbose, // Convert _fixedEdges to a compatible property map // Update remeshing calls to use the property map - if (split_long_edges) { - if (verbose) + if (split_long_edges) + { + if (LoopCGAL::verbose) std::cout << "Splitting long edges before remeshing.\n"; PMP::split_long_edges( edges(_mesh), target_edge_length, _mesh, CGAL::parameters::edge_is_constrained_map(_edge_is_constrained_map)); } - - for (int iter = 0; iter < number_of_iterations; ++iter) { - if (split_long_edges) { - if (verbose) + for (int iter = 0; iter < number_of_iterations; ++iter) + { + if (split_long_edges) + if (LoopCGAL::verbose) std::cout << "Splitting long edges in iteration " << iter + 1 << ".\n"; - PMP::split_long_edges( - edges(_mesh), target_edge_length, _mesh, - CGAL::parameters::edge_is_constrained_map(_edge_is_constrained_map)); - } - if (verbose) + PMP::split_long_edges( + edges(_mesh), target_edge_length, _mesh, + CGAL::parameters::edge_is_constrained_map(_edge_is_constrained_map)); + if (LoopCGAL::verbose) std::cout << "Remeshing iteration " << iter + 1 << " of " << number_of_iterations << ".\n"; PMP::isotropic_remeshing( @@ -206,32 +236,41 @@ void TriMesh::remesh(bool split_long_edges, bool verbose, .relax_constraints(relax_constraints)); } - if (verbose) + if (LoopCGAL::verbose) + { std::cout << "Refined mesh → " << _mesh.number_of_vertices() << " V, " << _mesh.number_of_faces() << " F\n"; - if (!CGAL::is_valid_polygon_mesh(_mesh, verbose) && verbose) + } + if (!CGAL::is_valid_polygon_mesh(_mesh, LoopCGAL::verbose) && LoopCGAL::verbose) std::cout << " ! mesh is not a valid polygon mesh after remeshing\n"; } -void TriMesh::reverseFaceOrientation() { +void TriMesh::reverseFaceOrientation() +{ // Reverse the face orientation of the mesh PMP::reverse_face_orientations(_mesh); - if (!CGAL::is_valid_polygon_mesh(_mesh, false)) { - std::cerr << "Error: Mesh is not valid after reversing face orientations." + if (!CGAL::is_valid_polygon_mesh(_mesh, LoopCGAL::verbose)) + { + std::cerr << "Mesh is not valid after reversing face orientations." << std::endl; } + } -void TriMesh::cutWithSurface(TriMesh &clipper, bool verbose, +void TriMesh::cutWithSurface(TriMesh &clipper, bool preserve_intersection, - bool preserve_intersection_clipper) { - if (verbose) { + bool preserve_intersection_clipper) +{ + if (LoopCGAL::verbose) + { std::cout << "Cutting mesh with surface." << std::endl; } bool intersection = PMP::do_intersect(_mesh, clipper._mesh); - if (intersection) { + if (intersection) + { // Clip tm with clipper - if (verbose) { + if (LoopCGAL::verbose) + { std::cout << "Clipping tm with clipper." << std::endl; } bool flag = @@ -240,7 +279,7 @@ void TriMesh::cutWithSurface(TriMesh &clipper, bool verbose, } NumpyMesh TriMesh::save(double area_threshold, - double duplicate_vertex_threshold, bool verbose) { - return export_mesh(_mesh, area_threshold, duplicate_vertex_threshold, - verbose); + double duplicate_vertex_threshold) +{ + return export_mesh(_mesh, area_threshold, duplicate_vertex_threshold); } \ No newline at end of file diff --git a/src/mesh.h b/src/mesh.h index 784e00a..ca4c3fc 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -15,35 +15,35 @@ typedef Kernel::Point_3 Point; typedef CGAL::Surface_mesh TriangleMesh; typedef CGAL::Plane_3 Plane; typedef CGAL::Vector_3 Vector; -class TriMesh { +class TriMesh +{ public: - // Constructor - TriMesh(const std::vector> &triangles, - const std::vector> &vertices); - TriMesh(const pybind11::array_t &vertices, - const pybind11::array_t &triangles); + // Constructor + TriMesh(const std::vector> &triangles, + const std::vector> &vertices); + TriMesh(const pybind11::array_t &vertices, + const pybind11::array_t &triangles); - // Method to cut the mesh with another surface object - void cutWithSurface(TriMesh &surface, bool verbose = false, - bool preserve_intersection = false, - bool preserve_intersection_clipper = false); + // Method to cut the mesh with another surface object + void cutWithSurface(TriMesh &surface, + bool preserve_intersection = false, + bool preserve_intersection_clipper = false); - // Method to remesh the triangle mesh - void remesh(bool split_long_edges, bool verbose, double target_edge_length, - int number_of_iterations, bool protect_constraints, - bool relax_constraints); - void init(); - // Getters for mesh properties - void reverseFaceOrientation(); - NumpyMesh save(double area_threshold, double duplicate_vertex_threshold, - bool verbose = false); - void add_fixed_edges(const pybind11::array_t &pairs); + // Method to remesh the triangle mesh + void remesh(bool split_long_edges, double target_edge_length, + int number_of_iterations, bool protect_constraints, + bool relax_constraints); + void init(); + // Getters for mesh properties + void reverseFaceOrientation(); + NumpyMesh save(double area_threshold, double duplicate_vertex_threshold); + void add_fixed_edges(const pybind11::array_t &pairs); private: - std::set _fixedEdges; - TriangleMesh _mesh; // The underlying CGAL surface mesh - CGAL::Boolean_property_map> - _edge_is_constrained_map; + std::set _fixedEdges; + TriangleMesh _mesh; // The underlying CGAL surface mesh + CGAL::Boolean_property_map> + _edge_is_constrained_map; }; #endif // MESH_HANDLER_H \ No newline at end of file diff --git a/src/meshutils.cpp b/src/meshutils.cpp index 17911a0..ea64d87 100644 --- a/src/meshutils.cpp +++ b/src/meshutils.cpp @@ -1,6 +1,6 @@ #include "meshutils.h" #include "mesh.h" - +#include "globals.h" std::set collect_border_edges(const TriangleMesh &tm) { std::set border_edges; @@ -35,7 +35,7 @@ double calculate_triangle_area(const std::array &v1, // Efficient export: linear‑time duplicate detection via quantised hash grid // --------------------------------------------------------------------------- NumpyMesh export_mesh(const TriangleMesh &tm, double area_threshold, - double duplicate_vertex_threshold, bool verbose) { + double duplicate_vertex_threshold) { using VIndex = TriangleMesh::Vertex_index; std::vector> vertices; // unique coords @@ -76,7 +76,7 @@ NumpyMesh export_mesh(const TriangleMesh &tm, double area_threshold, } } - if (verbose) { + if (LoopCGAL::verbose) { std::cout << "Vertices after remeshing: " << vertices.size() << '\n'; std::cout << "Duplicate‑detection grid cells: " << qmap.size() << '\n'; } @@ -93,11 +93,11 @@ NumpyMesh export_mesh(const TriangleMesh &tm, double area_threshold, if (area >= area_threshold) triangles.push_back(tri); - else if (verbose) + else if (LoopCGAL::verbose) std::cout << "Skipping degenerate face (A=" << area << ")\n"; } - if (verbose) + if (LoopCGAL::verbose) std::cout << "Kept " << triangles.size() << " triangles.\n"; // —‑‑‑‑‑ 3. Convert to NumPy arrays ----------------------------------- diff --git a/src/meshutils.h b/src/meshutils.h index 59fa479..c4fc9aa 100644 --- a/src/meshutils.h +++ b/src/meshutils.h @@ -4,7 +4,7 @@ std::set collect_border_edges(const TriangleMesh &tm); NumpyMesh export_mesh(const TriangleMesh &tm, double area_threshold, - double duplicate_vertex_threshold, bool verbose = false); + double duplicate_vertex_threshold); double calculate_triangle_area(const std::array &v1, const std::array &v2, const std::array &v3);