Lagrange
Loading...
Searching...
No Matches
bind_mesh_cleanup.h
1/*
2 * Copyright 2023 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <lagrange/mesh_cleanup/close_small_holes.h>
15#include <lagrange/mesh_cleanup/detect_degenerate_facets.h>
16#include <lagrange/mesh_cleanup/remove_degenerate_facets.h>
17#include <lagrange/mesh_cleanup/remove_duplicate_facets.h>
18#include <lagrange/mesh_cleanup/remove_duplicate_vertices.h>
19#include <lagrange/mesh_cleanup/remove_isolated_vertices.h>
20#include <lagrange/mesh_cleanup/remove_null_area_facets.h>
21#include <lagrange/mesh_cleanup/remove_short_edges.h>
22#include <lagrange/mesh_cleanup/remove_topologically_degenerate_facets.h>
23#include <lagrange/mesh_cleanup/rescale_uv_charts.h>
24#include <lagrange/mesh_cleanup/resolve_nonmanifoldness.h>
25#include <lagrange/mesh_cleanup/resolve_vertex_nonmanifoldness.h>
26#include <lagrange/mesh_cleanup/split_long_edges.h>
27#include <lagrange/mesh_cleanup/split_obtuse_triangles.h>
28#include <lagrange/python/binding.h>
29
30#include <vector>
31
32namespace lagrange::python {
33
34template <typename Scalar, typename Index>
35void bind_mesh_cleanup(nanobind::module_& m)
36{
37 namespace nb = nanobind;
38 using namespace nb::literals;
39 using MeshType = SurfaceMesh<Scalar, Index>;
40
41 m.def(
42 "remove_isolated_vertices",
44 "mesh"_a,
45 R"(Remove isolated vertices from a mesh.
46
47.. note::
48 A vertex is considered isolated if it is not referenced by any facet.
49
50:param mesh: Input mesh (modified in place).)");
51
52 m.def(
53 "detect_degenerate_facets",
55 "mesh"_a,
56 R"(Detect degenerate facets in a mesh.
57
58.. note::
59 Only exactly degenerate facets are detected.
60
61:param mesh: Input mesh.
62
63:returns: List of degenerate facet indices.)");
64
65 m.def(
66 "remove_null_area_facets",
67 [](MeshType& mesh, double null_area_threshold, bool remove_isolated_vertices) {
68 RemoveNullAreaFacetsOptions opts;
69 opts.null_area_threshold = null_area_threshold;
70 opts.remove_isolated_vertices = remove_isolated_vertices;
71 remove_null_area_facets(mesh, opts);
72 },
73 "mesh"_a,
74 "null_area_threshold"_a = 0,
75 "remove_isolated_vertices"_a = false,
76 R"(Remove facets with unsigned facets area <= `null_area_threhsold`.
77
78:param mesh: Input mesh (modified in place).
79:param null_area_threshold: Area threshold below which facets are considered null.
80:param remove_isolated_vertices: Whether to remove isolated vertices after removing null area facets.)");
81
82 m.def(
83 "remove_duplicate_vertices",
84 [](MeshType& mesh,
85 std::optional<std::vector<AttributeId>> extra_attributes,
86 bool boundary_only) {
87 RemoveDuplicateVerticesOptions opts;
88 if (extra_attributes.has_value()) {
89 opts.extra_attributes = std::move(extra_attributes.value());
90 }
91 opts.boundary_only = boundary_only;
92 remove_duplicate_vertices(mesh, opts);
93 },
94 "mesh"_a,
95 "extra_attributes"_a = nb::none(),
96 "boundary_only"_a = false,
97 R"(Remove duplicate vertices from a mesh.
98
99:param mesh: Input mesh (modified in place).
100:param extra_attributes: Additional attributes to consider when detecting duplicates.
101:param boundary_only: Only remove duplicate vertices on the boundary.)");
102
103 m.def(
104 "remove_duplicate_facets",
105 [](MeshType& mesh, bool consider_orientation) {
106 RemoveDuplicateFacetOptions opts;
107 opts.consider_orientation = consider_orientation;
108 remove_duplicate_facets(mesh, opts);
109 },
110 "mesh"_a,
111 "consider_orientation"_a = false,
112 R"(Remove duplicate facets from a mesh.
113
114Facets with different orientations (e.g. (0,1,2) and (2,1,0)) are considered duplicates.
115If both orientations have equal counts, all are removed.
116If one orientation has more duplicates, all but one of the majority orientation are kept.
117
118:param mesh: Input mesh (modified in place).
119:param consider_orientation: Whether to consider orientation when detecting duplicates.)");
120
121 m.def(
122 "remove_topologically_degenerate_facets",
124 "mesh"_a,
125 R"(Remove topologically degenerate facets such as (0,1,1).
126
127For polygons, topological degeneracy means the polygon has at most two unique vertices.
128E.g. quad (0,0,1,1) is degenerate, while (1,1,2,3) is not.
129
130:param mesh: Input mesh (modified in place).)");
131
132 m.def(
133 "remove_short_edges",
134 [](MeshType& mesh,
135 double threshold,
136 std::optional<std::string_view> vertex_importance_attribute) {
137 lagrange::RemoveShortEdgesOptions opts;
138 opts.threshold = threshold;
139 if (vertex_importance_attribute.has_value())
140 opts.vertex_importance_attribute_name = vertex_importance_attribute.value();
141 remove_short_edges(mesh, opts);
142 },
143 "mesh"_a,
144 nb::kw_only(),
145 "threshold"_a = 0,
146 "vertex_importance_attribute"_a = nb::none(),
147 R"(Remove short edges from a mesh.
148
149:param mesh: Input mesh (modified in place).
150:param threshold: Minimum edge length below which edges are considered short.
151:param vertex_importance_attribute: Optional vertex attribute name for importance values used to determine which vertex to keep during edge collapse.)");
152
153 m.def(
154 "resolve_vertex_nonmanifoldness",
156 "mesh"_a,
157 R"(Resolve vertex non-manifoldness in a mesh.
158
159:param mesh: Input mesh (modified in place).
160
161:raises RuntimeError: If the input mesh is not edge-manifold.)");
162
163 m.def(
164 "resolve_nonmanifoldness",
166 "mesh"_a,
167 R"(Resolve both vertex and edge nonmanifoldness in a mesh.
168
169:param mesh: Input mesh (modified in place).)");
170
171 m.def(
172 "split_long_edges",
173 [](MeshType& mesh,
174 float max_edge_length,
175 bool recursive,
176 std::optional<std::string_view> active_region_attribute,
177 std::optional<std::string_view> edge_length_attribute) {
178 SplitLongEdgesOptions opts;
179 opts.max_edge_length = max_edge_length;
180 opts.recursive = recursive;
181 if (active_region_attribute.has_value())
182 opts.active_region_attribute = active_region_attribute.value();
183 if (edge_length_attribute.has_value())
184 opts.edge_length_attribute = edge_length_attribute.value();
185 split_long_edges(mesh, std::move(opts));
186 },
187 "mesh"_a,
188 "max_edge_length"_a = 0.1f,
189 "recursive"_a = true,
190 "active_region_attribute"_a = nb::none(),
191 "edge_length_attribute"_a = nb::none(),
192 R"(Split edges longer than max_edge_length.
193
194:param mesh: Input mesh (modified in place).
195:param max_edge_length: Maximum edge length threshold.
196:param recursive: If true, apply recursively until no edge exceeds threshold.
197:param active_region_attribute: Facet attribute name for active region (uint8_t type).
198 If None, all edges are considered.
199:param edge_length_attribute: Edge length attribute name.
200 If None, edge lengths are computed.
201)");
202
203 m.def(
204 "split_obtuse_triangles",
205 [](MeshType& mesh,
206 float max_angle,
207 size_t max_iterations,
208 std::optional<std::string_view> active_region_attribute) {
209 SplitObtuseTrianglesOptions opts;
210 opts.max_angle = max_angle;
211 opts.max_iterations = max_iterations;
212 if (active_region_attribute.has_value())
213 opts.active_region_attribute = active_region_attribute.value();
214 return split_obtuse_triangles(mesh, std::move(opts));
215 },
216 "mesh"_a,
217 nb::kw_only(),
218 "max_angle"_a = SplitObtuseTrianglesOptions().max_angle,
219 "max_iterations"_a = SplitObtuseTrianglesOptions().max_iterations,
220 "active_region_attribute"_a = nb::none(),
221 R"(Iteratively split obtuse triangles by splitting their longest edge at the
222projection of the obtuse vertex.
223
224:param mesh: Input mesh (modified in place).
225:param max_angle: Maximum allowed interior angle in radians. Triangles with any interior
226 angle strictly greater than this value are split. Default is pi/2.
227:param max_iterations: Maximum number of split passes. Use 0 to iterate until convergence.
228:param active_region_attribute: Optional facet attribute name (uint8_t) restricting which
229 facets are considered. If None, all facets are checked.
230
231:returns: Total number of triangle splits performed across all iterations (a triangle
232 re-split in a later iteration is counted again).
233)");
234
235 m.def(
236 "remove_degenerate_facets",
238 "mesh"_a,
239 R"(Remove degenerate facets from a mesh.
240
241.. note::
242 Assumes triangular mesh. Use `triangulate_polygonal_facets` for non-triangular meshes.
243 Adjacent non-degenerate facets may be re-triangulated during removal.
244
245:param mesh: Input mesh (modified in place).)");
246
247 m.def(
248 "close_small_holes",
249 [](MeshType& mesh, size_t max_hole_size, bool triangulate_holes) {
250 CloseSmallHolesOptions options;
251 options.max_hole_size = max_hole_size;
252 options.triangulate_holes = triangulate_holes;
253 close_small_holes(mesh, options);
254 },
255 "mesh"_a,
256 "max_hole_size"_a = CloseSmallHolesOptions().max_hole_size,
257 "triangulate_holes"_a = CloseSmallHolesOptions().triangulate_holes,
258 R"(Close small holes in a mesh.
259
260:param mesh: Input mesh (modified in place).
261:param max_hole_size: Maximum number of vertices on a hole to be closed.
262:param triangulate_holes: Whether to triangulate holes (if false, fill with polygons).)");
263
264 m.def(
265 "rescale_uv_charts",
266 [](MeshType& mesh,
267 std::string_view uv_attribute_name,
268 std::string_view chart_id_attribute_name,
269 double uv_area_threshold) {
270 RescaleUVOptions options;
271 options.uv_attribute_name = uv_attribute_name;
272 options.chart_id_attribute_name = chart_id_attribute_name;
273 options.uv_area_threshold = uv_area_threshold;
274 rescale_uv_charts(mesh, options);
275 },
276 "mesh"_a,
277 "uv_attribute_name"_a = RescaleUVOptions().uv_attribute_name,
278 "chart_id_attribute_name"_a = RescaleUVOptions().chart_id_attribute_name,
279 "uv_area_threshold"_a = RescaleUVOptions().uv_area_threshold,
280 R"(Rescale UV charts to match their 3D aspect ratios.
281
282:param mesh: Input mesh (modified in place).
283:param uv_attribute_name: UV attribute name for rescaling.
284 If empty, uses first UV attribute found.
285:param chart_id_attribute_name: Patch ID attribute name.
286 If empty, computes patches from UV chart connectivity.
287:param uv_area_threshold: UV area threshold.
288 Triangles below this threshold don't contribute to scale computation.
289)");
290}
291
292} // namespace lagrange::python
void remove_duplicate_vertices(SurfaceMesh< Scalar, Index > &mesh, const RemoveDuplicateVerticesOptions &options={})
Removes duplicate vertices from a mesh.
Definition remove_duplicate_vertices.cpp:33
void remove_duplicate_facets(SurfaceMesh< Scalar, Index > &mesh, const RemoveDuplicateFacetOptions &opts={})
Remove duplicate facets in the mesh.
Definition remove_duplicate_facets.cpp:235
void resolve_vertex_nonmanifoldness(SurfaceMesh< Scalar, Index > &mesh)
Resolve nonmanifold vertices by pulling disconnected 1-ring neighborhood apart.
Definition resolve_vertex_nonmanifoldness.cpp:35
void rescale_uv_charts(SurfaceMesh< Scalar, Index > &mesh, const RescaleUVOptions &options={})
Rescale UV charts such that they are isotropic to their 3D images.
Definition rescale_uv_charts.cpp:70
void remove_topologically_degenerate_facets(SurfaceMesh< Scalar, Index > &mesh)
Remove topologically degenerate facets (i.e.
Definition remove_topologically_degenerate_facets.cpp:21
void remove_isolated_vertices(SurfaceMesh< Scalar, Index > &mesh)
Removes isolated vertices of a mesh.
Definition remove_isolated_vertices.cpp:20
void split_long_edges(SurfaceMesh< Scalar, Index > &mesh, SplitLongEdgesOptions options={})
Split edges that are longer than options.max_edge_length.
Definition split_long_edges.cpp:37
void close_small_holes(SurfaceMesh< Scalar, Index > &mesh, CloseSmallHolesOptions options={})
Close small topological holes.
Definition close_small_holes.cpp:538
void remove_null_area_facets(SurfaceMesh< Scalar, Index > &mesh, const RemoveNullAreaFacetsOptions &options={})
Removes all facets with unsigned area <= options.null_area_threshold.
Definition remove_null_area_facets.cpp:22
void remove_degenerate_facets(SurfaceMesh< Scalar, Index > &mesh)
Removes degenerate facets from a mesh.
Definition remove_degenerate_facets.cpp:32
std::vector< Index > detect_degenerate_facets(const SurfaceMesh< Scalar, Index > &mesh)
Detects degenerate facets in a mesh.
Definition detect_degenerate_facets.cpp:32
void remove_short_edges(SurfaceMesh< Scalar, Index > &mesh, Scalar threshold=0)
Collapse all edges shorter than a given tolerance.
Definition remove_short_edges.cpp:424
size_t split_obtuse_triangles(SurfaceMesh< Scalar, Index > &mesh, SplitObtuseTrianglesOptions options={})
Iteratively split obtuse triangles by splitting their longest edge at the projection of the opposite ...
Definition split_obtuse_triangles.cpp:41
void resolve_nonmanifoldness(SurfaceMesh< Scalar, Index > &mesh)
Resolve both non-manifold vertices and non-manifold edges in the input mesh.
Definition resolve_nonmanifoldness.cpp:35
std::string_view vertex_importance_attribute_name
Optional: User-defined per-vertex importance attribute name.
Definition remove_short_edges.h:44
double threshold
Edge length threshold for removal. Edges with length <= threshold will be removed.
Definition remove_short_edges.h:36