Lagrange
Loading...
Searching...
No Matches
bind_utilities.h
1/*
2 * Copyright 2022 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/AttributeTypes.h>
15#include <lagrange/NormalWeightingType.h>
16#include <lagrange/cast_attribute.h>
17#include <lagrange/combine_meshes.h>
18#include <lagrange/compute_area.h>
19#include <lagrange/compute_centroid.h>
20#include <lagrange/compute_components.h>
21#include <lagrange/compute_dihedral_angles.h>
22#include <lagrange/compute_dijkstra_distance.h>
23#include <lagrange/compute_edge_lengths.h>
24#include <lagrange/compute_facet_circumcenter.h>
25#include <lagrange/compute_facet_normal.h>
26#include <lagrange/compute_greedy_coloring.h>
27#include <lagrange/compute_mesh_covariance.h>
28#include <lagrange/compute_normal.h>
29#include <lagrange/compute_pointcloud_pca.h>
30#include <lagrange/compute_seam_edges.h>
31#include <lagrange/compute_tangent_bitangent.h>
32#include <lagrange/compute_uv_charts.h>
33#include <lagrange/compute_uv_distortion.h>
34#include <lagrange/compute_uv_orientation.h>
35#include <lagrange/compute_vertex_normal.h>
36#include <lagrange/compute_vertex_valence.h>
37#include <lagrange/disconnect_uv_charts.h>
38#include <lagrange/extract_submesh.h>
39#include <lagrange/filter_attributes.h>
40#include <lagrange/get_unique_attribute_name.h>
41#include <lagrange/internal/constants.h>
42#include <lagrange/isoline.h>
43#include <lagrange/map_attribute.h>
44#include <lagrange/normalize_meshes.h>
45#include <lagrange/orient_outward.h>
46#include <lagrange/orientation.h>
47#include <lagrange/permute_facets.h>
48#include <lagrange/permute_vertices.h>
49#include <lagrange/python/binding.h>
50#include <lagrange/python/tensor_utils.h>
51#include <lagrange/python/utils/StackVector.h>
52#include <lagrange/remap_vertices.h>
53#include <lagrange/reorder_mesh.h>
54#include <lagrange/select_facets_by_normal_similarity.h>
55#include <lagrange/select_facets_in_frustum.h>
56#include <lagrange/separate_by_components.h>
57#include <lagrange/separate_by_facet_groups.h>
58#include <lagrange/split_facets_by_material.h>
59#include <lagrange/thicken_and_close_mesh.h>
60#include <lagrange/topology.h>
61#include <lagrange/transform_mesh.h>
62#include <lagrange/triangulate_polygonal_facets.h>
63#include <lagrange/unflip_uv_charts.h>
64#include <lagrange/unify_index_buffer.h>
65#include <lagrange/utils/fmt/format.h>
66#include <lagrange/utils/invalid.h>
67#include <lagrange/uv_mesh.h>
68#include <lagrange/weld_indexed_attribute.h>
69
70#include <optional>
71#include <string_view>
72#include <vector>
73
74namespace lagrange::python {
75
76template <typename Scalar, typename Index>
77void bind_utilities(nanobind::module_& m)
78{
79 namespace nb = nanobind;
80 using namespace nb::literals;
81 using MeshType = SurfaceMesh<Scalar, Index>;
82
83 nb::enum_<NormalWeightingType>(m, "NormalWeightingType", "Normal weighting type.")
84 .value("Uniform", NormalWeightingType::Uniform, "Uniform weighting")
85 .value(
86 "CornerTriangleArea",
88 "Weight by corner triangle area")
89 .value("Angle", NormalWeightingType::Angle, "Weight by corner angle");
90
91 nb::class_<VertexNormalOptions>(
92 m,
93 "VertexNormalOptions",
94 "Options for computing vertex normals")
95 .def(nb::init<>())
96 .def_rw(
97 "output_attribute_name",
99 "Output attribute name. Default is `@vertex_normal`.")
100 .def_rw(
101 "weight_type",
103 "Weighting type for normal computation. Default is Angle.")
104 .def_rw(
105 "weighted_corner_normal_attribute_name",
107 R"(Precomputed weighted corner normals attribute name (default: @weighted_corner_normal).
108
109If attribute exists, the precomputed weighted corner normal will be used.)")
110 .def_rw(
111 "recompute_weighted_corner_normals",
113 "Whether to recompute weighted corner normals (default: false).")
114 .def_rw(
115 "keep_weighted_corner_normals",
117 "Whether to keep the weighted corner normal attribute (default: false).")
118 .def_rw(
119 "distance_tolerance",
121 "Distance tolerance for degenerate edge check in polygon facets.");
122
123 m.def(
124 "compute_vertex_normal",
126 "mesh"_a,
127 "options"_a = VertexNormalOptions(),
128 R"(Compute vertex normal.
129
130:param mesh: Input mesh.
131:param options: Options for computing vertex normals.
132
133:returns: Vertex normal attribute id.)");
134
135 m.def(
136 "compute_vertex_normal",
137 [](MeshType& mesh,
138 std::optional<std::string_view> output_attribute_name,
139 std::optional<NormalWeightingType> weight_type,
140 std::optional<std::string_view> weighted_corner_normal_attribute_name,
141 std::optional<bool> recompute_weighted_corner_normals,
142 std::optional<bool> keep_weighted_corner_normals,
143 std::optional<float> distance_tolerance) {
144 VertexNormalOptions options;
145 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
146 if (weight_type) options.weight_type = *weight_type;
147 if (weighted_corner_normal_attribute_name)
148 options.weighted_corner_normal_attribute_name =
149 *weighted_corner_normal_attribute_name;
150 if (recompute_weighted_corner_normals)
151 options.recompute_weighted_corner_normals = *recompute_weighted_corner_normals;
152 if (keep_weighted_corner_normals)
153 options.keep_weighted_corner_normals = *keep_weighted_corner_normals;
154 if (distance_tolerance) options.distance_tolerance = *distance_tolerance;
155
156 return compute_vertex_normal<Scalar, Index>(mesh, options);
157 },
158 "mesh"_a,
159 "output_attribute_name"_a = nb::none(),
160 "weight_type"_a = nb::none(),
161 "weighted_corner_normal_attribute_name"_a = nb::none(),
162 "recompute_weighted_corner_normals"_a = nb::none(),
163 "keep_weighted_corner_normals"_a = nb::none(),
164 "distance_tolerance"_a = nb::none(),
165 R"(Compute vertex normal (Pythonic API).
166
167:param mesh: Input mesh.
168:param output_attribute_name: Output attribute name.
169:param weight_type: Weighting type for normal computation.
170:param weighted_corner_normal_attribute_name: Precomputed weighted corner normals attribute name.
171:param recompute_weighted_corner_normals: Whether to recompute weighted corner normals.
172:param keep_weighted_corner_normals: Whether to keep the weighted corner normal attribute.
173:param distance_tolerance: Distance tolerance for degenerate edge check.
174 (Only used to bypass degenerate edge in polygon facets.)
175
176:returns: Vertex normal attribute id.)");
177
178 nb::class_<FacetNormalOptions>(m, "FacetNormalOptions", "Facet normal computation options.")
179 .def(nb::init<>())
180 .def_rw(
181 "output_attribute_name",
183 "Output attribute name. Default: `@facet_normal`");
184
185 m.def(
186 "compute_facet_normal",
188 "mesh"_a,
189 "options"_a = FacetNormalOptions(),
190 R"(Compute facet normal.
191
192:param mesh: Input mesh.
193:param options: Options for computing facet normals.
194
195:returns: Facet normal attribute id.)");
196
197 m.def(
198 "compute_facet_normal",
199 [](MeshType& mesh, std::optional<std::string_view> output_attribute_name) {
200 FacetNormalOptions options;
201 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
202 return compute_facet_normal<Scalar, Index>(mesh, options);
203 },
204 "mesh"_a,
205 "output_attribute_name"_a = nb::none(),
206 R"(Compute facet normal (Pythonic API).
207
208:param mesh: Input mesh.
209:param output_attribute_name: Output attribute name.
210
211:returns: Facet normal attribute id.)");
212
213 nb::class_<NormalOptions>(m, "NormalOptions", "Normal computation options.")
214 .def(nb::init<>())
215 .def_rw(
216 "output_attribute_name",
218 "Output attribute name. Default: `@normal`")
219 .def_rw(
220 "weight_type",
222 "Weighting type for normal computation. Default is Angle.")
223 .def_rw(
224 "facet_normal_attribute_name",
226 "Facet normal attribute name to use. Default is `@facet_normal`.")
227 .def_rw(
228 "recompute_facet_normals",
230 "Whether to recompute facet normals. Default is false.")
231 .def_rw(
232 "keep_facet_normals",
234 "Whether to keep the computed facet normal attribute. Default is false.")
235 .def_rw(
236 "distance_tolerance",
238 "Distance tolerance for degenerate edge check. (Only used to bypass degenerate edge in "
239 "polygon facets.)");
240
241 m.def(
242 "compute_normal",
243 [](MeshType& mesh,
244 Scalar feature_angle_threshold,
245 nb::object cone_vertices,
246 std::optional<NormalOptions> normal_options) {
247 NormalOptions options;
248 if (normal_options.has_value()) {
249 options = std::move(normal_options.value());
250 }
251
252 if (cone_vertices.is_none()) {
253 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, {}, options);
254 } else if (nb::isinstance<nb::list>(cone_vertices)) {
255 auto cone_vertices_list = nb::cast<std::vector<Index>>(cone_vertices);
256 span<const Index> data{cone_vertices_list.data(), cone_vertices_list.size()};
257 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
258 } else if (nb::isinstance<Tensor<Index>>(cone_vertices)) {
259 auto cone_vertices_tensor = nb::cast<Tensor<Index>>(cone_vertices);
260 auto [data, shape, stride] = tensor_to_span(cone_vertices_tensor);
261 la_runtime_assert(is_dense(shape, stride));
262 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
263 } else {
264 throw std::runtime_error("Invalid cone_vertices type");
265 }
266 },
267 "mesh"_a,
268 "feature_angle_threshold"_a = lagrange::internal::pi / 4,
269 "cone_vertices"_a = nb::none(),
270 "options"_a = nb::none(),
271 R"(Compute indexed normal attribute.
272
273Edge with dihedral angles larger than `feature_angle_threshold` are considered as sharp edges.
274Vertices listed in `cone_vertices` are considered as cone vertices, which is always sharp.
275
276:param mesh: input mesh
277:param feature_angle_threshold: feature angle threshold
278:param cone_vertices: cone vertices
279:param options: normal options
280
281:returns: the id of the indexed normal attribute.
282)");
283
284 m.def(
285 "compute_normal",
286 [](MeshType& mesh,
287 Scalar feature_angle_threshold,
288 nb::object cone_vertices,
289 std::optional<std::string_view> output_attribute_name,
290 std::optional<NormalWeightingType> weight_type,
291 std::optional<std::string_view> facet_normal_attribute_name,
292 std::optional<bool> recompute_facet_normals,
293 std::optional<bool> keep_facet_normals,
294 std::optional<float> distance_tolerance) {
295 NormalOptions options;
296 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
297 if (weight_type) options.weight_type = *weight_type;
298 if (facet_normal_attribute_name)
299 options.facet_normal_attribute_name = *facet_normal_attribute_name;
300 if (recompute_facet_normals) options.recompute_facet_normals = *recompute_facet_normals;
301 if (keep_facet_normals) options.keep_facet_normals = *keep_facet_normals;
302 if (distance_tolerance) options.distance_tolerance = *distance_tolerance;
303
304 if (cone_vertices.is_none()) {
305 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, {}, options);
306 } else if (nb::isinstance<nb::list>(cone_vertices)) {
307 auto cone_vertices_list = nb::cast<std::vector<Index>>(cone_vertices);
308 span<const Index> data{cone_vertices_list.data(), cone_vertices_list.size()};
309 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
310 } else if (nb::isinstance<Tensor<Index>>(cone_vertices)) {
311 auto cone_vertices_tensor = nb::cast<Tensor<Index>>(cone_vertices);
312 auto [data, shape, stride] = tensor_to_span(cone_vertices_tensor);
313 la_runtime_assert(is_dense(shape, stride));
314 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
315 } else {
316 throw std::runtime_error("Invalid cone_vertices type");
317 }
318 },
319 "mesh"_a,
320 "feature_angle_threshold"_a = lagrange::internal::pi / 4,
321 "cone_vertices"_a = nb::none(),
322 "output_attribute_name"_a = nb::none(),
323 "weight_type"_a = nb::none(),
324 "facet_normal_attribute_name"_a = nb::none(),
325 "recompute_facet_normals"_a = nb::none(),
326 "keep_facet_normals"_a = nb::none(),
327 "distance_tolerance"_a = nb::none(),
328 R"(Compute indexed normal attribute (Pythonic API).
329
330:param mesh: input mesh
331:param feature_angle_threshold: feature angle threshold
332:param cone_vertices: cone vertices
333:param output_attribute_name: output normal attribute name
334:param weight_type: normal weighting type
335:param facet_normal_attribute_name: facet normal attribute name
336:param recompute_facet_normals: whether to recompute facet normals
337:param keep_facet_normals: whether to keep the computed facet normal attribute
338:param distance_tolerance: distance tolerance for degenerate edge check
339 (only used to bypass degenerate edges in polygon facets)
340
341:returns: the id of the indexed normal attribute.)");
342
343 using ConstArray3d = nb::ndarray<const double, nb::shape<-1, 3>, nb::c_contig, nb::device::cpu>;
344 m.def(
345 "compute_pointcloud_pca",
346 [](ConstArray3d points, bool shift_centroid, bool normalize) {
347 ComputePointcloudPCAOptions options;
348 options.shift_centroid = shift_centroid;
349 options.normalize = normalize;
350 PointcloudPCAOutput<Scalar> output =
351 compute_pointcloud_pca<Scalar>({points.data(), points.size()}, options);
352 return std::make_tuple(output.center, output.eigenvectors, output.eigenvalues);
353 },
354 "points"_a,
355 "shift_centroid"_a = ComputePointcloudPCAOptions().shift_centroid,
356 "normalize"_a = ComputePointcloudPCAOptions().normalize,
357 R"(Compute principal components of a point cloud.
358
359:param points: Input points.
360:param shift_centroid: When true: covariance = (P-centroid)^T (P-centroid), when false: covariance = (P)^T (P).
361:param normalize: Should we divide the result by number of points?
362
363:returns: tuple of (center, eigenvectors, eigenvalues).)");
364
365 m.def(
366 "compute_greedy_coloring",
367 [](MeshType& mesh,
368 AttributeElement element_type,
369 size_t num_color_used,
370 std::optional<std::string_view> output_attribute_name) {
371 GreedyColoringOptions options;
372 options.element_type = element_type;
373 options.num_color_used = num_color_used;
374 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
375 return compute_greedy_coloring<Scalar, Index>(mesh, options);
376 },
377 "mesh"_a,
378 "element_type"_a = AttributeElement::Facet,
379 "num_color_used"_a = 8,
380 "output_attribute_name"_a = nb::none(),
381 R"(Compute greedy coloring of mesh elements.
382
383:param mesh: Input mesh.
384:param element_type: Element type to be colored. Can be either Vertex or Facet.
385:param num_color_used: Minimum number of colors to use. The algorithm will cycle through them but may use more.
386:param output_attribute_name: Output attribute name.
387
388:returns: Color attribute id.)");
389
390 m.def(
391 "normalize_mesh_with_transform",
392 [](MeshType& mesh,
393 bool normalize_normals,
394 bool normalize_tangents_bitangents) -> Eigen::Matrix<Scalar, 4, 4> {
395 TransformOptions options;
396 options.normalize_normals = normalize_normals;
397 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
398 return normalize_mesh_with_transform(mesh, options).matrix();
399 },
400 "mesh"_a,
401 "normalize_normals"_a = TransformOptions().normalize_normals,
402 "normalize_tangents_bitangents"_a = TransformOptions().normalize_tangents_bitangents,
403 R"(Normalize a mesh to fit into a unit box centered at the origin.
404
405:param mesh: Input mesh.
406:param normalize_normals: Whether to normalize normals.
407:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.
408
409:return Inverse transform, can be used to undo the normalization process.)");
410
411
412 m.def(
413 "normalize_mesh_with_transform_2d",
414 [](MeshType& mesh,
415 bool normalize_normals,
416 bool normalize_tangents_bitangents) -> Eigen::Matrix<Scalar, 3, 3> {
417 TransformOptions options;
418 options.normalize_normals = normalize_normals;
419 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
420 return normalize_mesh_with_transform<2>(mesh, options).matrix();
421 },
422 "mesh"_a,
423 "normalize_normals"_a = TransformOptions().normalize_normals,
424 "normalize_tangents_bitangents"_a = TransformOptions().normalize_tangents_bitangents,
425 R"(Normalize a mesh to fit into a unit box centered at the origin.
426
427:param mesh: Input mesh.
428:param normalize_normals: Whether to normalize normals.
429:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.
430
431:return Inverse transform, can be used to undo the normalization process.)");
432
433 m.def(
434 "normalize_mesh",
435 [](MeshType& mesh, bool normalize_normals, bool normalize_tangents_bitangents) -> void {
436 TransformOptions options;
437 options.normalize_normals = normalize_normals;
438 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
439 normalize_mesh(mesh, options);
440 },
441 "mesh"_a,
442 "normalize_normals"_a = TransformOptions().normalize_normals,
443 "normalize_tangents_bitangents"_a = TransformOptions().normalize_tangents_bitangents,
444 R"(Normalize a mesh to fit into a unit box centered at the origin.
445
446:param mesh: Input mesh.
447:param normalize_normals: Whether to normalize normals.
448:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.)");
449
450 m.def(
451 "normalize_meshes_with_transform",
452 [](std::vector<MeshType*> meshes,
453 bool normalize_normals,
454 bool normalize_tangents_bitangents) -> Eigen::Matrix<Scalar, 4, 4> {
455 TransformOptions options;
456 options.normalize_normals = normalize_normals;
457 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
458 span<MeshType*> meshes_span(meshes.data(), meshes.size());
459 return normalize_meshes_with_transform(meshes_span, options).matrix();
460 },
461 "meshes"_a,
462 "normalize_normals"_a = TransformOptions().normalize_normals,
463 "normalize_tangents_bitangents"_a = TransformOptions().normalize_tangents_bitangents,
464 R"(Normalize a mesh to fit into a unit box centered at the origin.
465
466:param meshes: Input meshes.
467:param normalize_normals: Whether to normalize normals.
468:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.
469
470:return Inverse transform, can be used to undo the normalization process.)");
471
472 m.def(
473 "normalize_meshes_with_transform_2d",
474 [](std::vector<MeshType*> meshes,
475 bool normalize_normals,
476 bool normalize_tangents_bitangents) -> Eigen::Matrix<Scalar, 3, 3> {
477 TransformOptions options;
478 options.normalize_normals = normalize_normals;
479 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
480 span<MeshType*> meshes_span(meshes.data(), meshes.size());
481 return normalize_meshes_with_transform<2>(meshes_span, options).matrix();
482 },
483 "meshes"_a,
484 "normalize_normals"_a = TransformOptions().normalize_normals,
485 "normalize_tangents_bitangents"_a = TransformOptions().normalize_tangents_bitangents,
486 R"(Normalize a mesh to fit into a unit box centered at the origin.
487
488:param meshes: Input meshes.
489:param normalize_normals: Whether to normalize normals.
490:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.
491
492:return Inverse transform, can be used to undo the normalization process.)");
493
494
495 m.def(
496 "normalize_meshes",
497 [](std::vector<MeshType*> meshes,
498 bool normalize_normals,
499 bool normalize_tangents_bitangents) {
500 TransformOptions options;
501 options.normalize_normals = normalize_normals;
502 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
503 span<MeshType*> meshes_span(meshes.data(), meshes.size());
504 normalize_meshes(meshes_span, options);
505 },
506 "meshes"_a,
507 "normalize_normals"_a = TransformOptions().normalize_normals,
508 "normalize_tangents_bitangents"_a = TransformOptions().normalize_tangents_bitangents,
509 R"(Normalize a list of meshes to fit into a unit box centered at the origin.
510
511:param meshes: Input meshes.
512:param normalize_normals: Whether to normalize normals.
513:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.)");
514
515 m.def(
516 "combine_meshes",
517 [](std::vector<MeshType*> meshes, bool preserve_vertices) {
519 meshes.size(),
520 [&](size_t i) -> const MeshType& { return *meshes[i]; },
521 preserve_vertices);
522 },
523 "meshes"_a,
524 "preserve_attributes"_a = true,
525 R"(Combine a list of meshes into a single mesh.
526
527:param meshes: Input meshes.
528:param preserve_attributes: Whether to preserve attributes.
529
530:returns: The combined mesh.)");
531
532 m.def(
533 "compute_seam_edges",
534 [](MeshType& mesh,
535 AttributeId indexed_attribute_id,
536 std::optional<std::string_view> output_attribute_name,
537 bool include_boundary_edges) {
538 SeamEdgesOptions options;
539 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
540 options.include_boundary_edges = include_boundary_edges;
541 return compute_seam_edges<Scalar, Index>(mesh, indexed_attribute_id, options);
542 },
543 "mesh"_a,
544 "indexed_attribute_id"_a,
545 "output_attribute_name"_a = nb::none(),
546 "include_boundary_edges"_a = SeamEdgesOptions().include_boundary_edges,
547 R"(Compute seam edges for a given indexed attribute.
548
549:param mesh: Input mesh.
550:param indexed_attribute_id: Input indexed attribute id.
551:param output_attribute_name: Output attribute name.
552:param include_boundary_edges: If true, boundary edges are also marked as seam edges.
553
554:returns: Attribute id for the output per-edge seam attribute (1 is a seam, 0 is not).)");
555
556 m.def(
557 "orient_outward",
558 [](MeshType& mesh, bool positive) {
559 OrientOptions options;
560 options.positive = positive;
561 orient_outward<Scalar, Index>(mesh, options);
562 },
563 "mesh"_a,
564 "positive"_a = OrientOptions().positive,
565 R"(Orient mesh facets to ensure positive or negative signed volume.
566
567:param mesh: Input mesh.
568:param positive: Whether to orient volumes positively or negatively.)");
569
570 m.def(
571 "unify_index_buffer",
572 [](MeshType& mesh) { return unify_index_buffer(mesh); },
573 "mesh"_a,
574 R"(Unify the index buffer for all indexed attributes.
575
576:param mesh: Input mesh.
577
578:returns: Unified mesh.)");
579
580 m.def(
581 "unify_index_buffer",
583 "mesh"_a,
584 "attribute_ids"_a,
585 R"(Unify the index buffer for selected attributes.
586
587:param mesh: Input mesh.
588:param attribute_ids: Attribute IDs to unify.
589
590:returns: Unified mesh.)");
591
592 m.def(
593 "unify_index_buffer",
595 "mesh"_a,
596 "attribute_names"_a,
597 R"(Unify the index buffer for selected attributes.
598
599:param mesh: Input mesh.
600:param attribute_names: Attribute names to unify.
601
602:returns: Unified mesh.)");
603
604 m.def(
605 "triangulate_polygonal_facets",
606 [](MeshType& mesh, std::string_view scheme) {
607 lagrange::TriangulationOptions opt;
608 if (scheme == "earcut") {
610 } else if (scheme == "centroid_fan") {
612 } else {
613 throw Error(lagrange::format("Unsupported triangulation scheme {}", scheme));
614 }
616 },
617 "mesh"_a,
618 "scheme"_a = "earcut",
619 R"(Triangulate polygonal facets of the mesh.
620
621:param mesh: The input mesh to be triangulated in place.
622:param scheme: The triangulation scheme (options are 'earcut' and 'centroid_fan'))");
623
624 nb::enum_<ComponentOptions::ConnectivityType>(m, "ConnectivityType", "Mesh connectivity type")
625 .value(
626 "Vertex",
627 ComponentOptions::ConnectivityType::Vertex,
628 "Two facets are connected if they share a vertex")
629 .value(
630 "Edge",
631 ComponentOptions::ConnectivityType::Edge,
632 "Two facets are connected if they share an edge");
633
634 m.def(
635 "compute_components",
636 [](MeshType& mesh,
637 std::optional<std::string_view> output_attribute_name,
638 std::optional<lagrange::ConnectivityType> connectivity_type,
639 std::optional<nb::list>& blocker_elements) {
640 lagrange::ComponentOptions opt;
641 if (output_attribute_name.has_value()) {
642 opt.output_attribute_name = output_attribute_name.value();
643 }
644 if (connectivity_type.has_value()) {
645 opt.connectivity_type = connectivity_type.value();
646 }
647 std::vector<Index> blocker_elements_vec;
648 if (blocker_elements.has_value()) {
649 for (auto val : blocker_elements.value()) {
650 blocker_elements_vec.push_back(nb::cast<Index>(val));
651 }
652 }
653 return lagrange::compute_components<Scalar, Index>(mesh, blocker_elements_vec, opt);
654 },
655 "mesh"_a,
656 "output_attribute_name"_a = nb::none(),
657 "connectivity_type"_a = nb::none(),
658 "blocker_elements"_a = nb::none(),
659 R"(Compute connected components.
660
661This method will create a per-facet component id attribute named by the `output_attribute_name`
662argument. Each component id is in [0, num_components-1] range.
663
664:param mesh: The input mesh.
665:param output_attribute_name: The name of the output attribute.
666:param connectivity_type: The connectivity type. Either "Vertex" or "Edge".
667:param blocker_elements: The list of blocker element indices. If `connectivity_type` is `Edge`, facets adjacent to a blocker edge are not considered as connected through this edge. If `connectivity_type` is `Vertex`, facets sharing a blocker vertex are not considered as connected through this vertex.
668
669:returns: The total number of components.)");
670
671 nb::class_<VertexValenceOptions>(m, "VertexValenceOptions", "Vertex valence options")
672 .def(nb::init<>())
673 .def_rw(
674 "output_attribute_name",
676 "The name of the output attribute")
677 .def_rw(
678 "induced_by_attribute",
680 "Optional per-edge attribute used as indicator function to restrict the graph used for "
681 "vertex valence computation");
682
683 m.def(
684 "compute_vertex_valence",
686 "mesh"_a,
687 "options"_a = VertexValenceOptions(),
688 R"(Compute vertex valence
689
690:param mesh: The input mesh.
691:param options: The vertex valence options.
692
693:returns: The vertex valence attribute id.)");
694
695 m.def(
696 "compute_vertex_valence",
697 [](MeshType& mesh,
698 std::optional<std::string_view> output_attribute_name,
699 std::optional<std::string_view> induced_by_attribute) {
700 VertexValenceOptions opt;
701 if (output_attribute_name.has_value()) {
702 opt.output_attribute_name = output_attribute_name.value();
703 }
704 if (induced_by_attribute.has_value()) {
705 opt.induced_by_attribute = induced_by_attribute.value();
706 }
708 },
709 "mesh"_a,
710 "output_attribute_name"_a = nb::none(),
711 "induced_by_attribute"_a = nb::none(),
712 R"(Compute vertex valence);
713
714:param mesh: The input mesh.
715:param output_attribute_name: The name of the output attribute.
716:param induced_by_attribute: Optional per-edge attribute used as indicator function to restrict the graph used for vertex valence computation.
717
718:returns: The vertex valence attribute id)");
719
720 nb::class_<TangentBitangentOptions>(m, "TangentBitangentOptions", "Tangent bitangent options")
721 .def(nb::init<>())
722 .def_rw(
723 "tangent_attribute_name",
725 "The name of the output tangent attribute, default is `@tangent`")
726 .def_rw(
727 "bitangent_attribute_name",
729 "The name of the output bitangent attribute, default is `@bitangent`")
730 .def_rw(
731 "uv_attribute_name",
733 "The name of the uv attribute")
734 .def_rw(
735 "normal_attribute_name",
737 "The name of the normal attribute")
738 .def_rw(
739 "output_element_type",
741 "The output element type")
742 .def_rw(
743 "pad_with_sign",
745 "Whether to pad the output tangent/bitangent with sign")
746 .def_rw(
747 "orthogonalize_bitangent",
749 "Whether to compute the bitangent as cross(normal, tangent). If false, the bitangent "
750 "is computed as the derivative of v-coordinate")
751 .def_rw(
752 "keep_existing_tangent",
754 "Whether to recompute tangent if the tangent attribute (specified by "
755 "tangent_attribute_name) already exists. If true, bitangent is computed by normalizing "
756 "cross(normal, tangent) and param orthogonalize_bitangent must be true.");
757 nb::class_<TangentBitangentResult>(m, "TangentBitangentResult", "Tangent bitangent result")
758 .def(nb::init<>())
759 .def_rw(
760 "tangent_id",
762 "The output tangent attribute id")
763 .def_rw(
764 "bitangent_id",
766 "The output bitangent attribute id");
767
768 m.def(
769 "compute_tangent_bitangent",
771 "mesh"_a,
772 "options"_a = TangentBitangentOptions(),
773 R"(Compute tangent and bitangent vector attributes.
774
775:param mesh: The input mesh.
776:param options: The tangent bitangent options.
777
778:returns: The tangent and bitangent attribute ids)");
779
780 m.def(
781 "compute_tangent_bitangent",
782 [](MeshType& mesh,
783 std::optional<std::string_view>(tangent_attribute_name),
784 std::optional<std::string_view>(bitangent_attribute_name),
785 std::optional<std::string_view>(uv_attribute_name),
786 std::optional<std::string_view>(normal_attribute_name),
787 std::optional<AttributeElement>(output_attribute_type),
788 std::optional<bool>(pad_with_sign),
789 std::optional<bool>(orthogonalize_bitangent),
790 std::optional<bool>(keep_existing_tangent)) {
791 TangentBitangentOptions opt;
792 if (tangent_attribute_name.has_value()) {
793 opt.tangent_attribute_name = tangent_attribute_name.value();
794 }
795 if (bitangent_attribute_name.has_value()) {
796 opt.bitangent_attribute_name = bitangent_attribute_name.value();
797 }
798 if (uv_attribute_name.has_value()) {
799 opt.uv_attribute_name = uv_attribute_name.value();
800 }
801 if (normal_attribute_name.has_value()) {
802 opt.normal_attribute_name = normal_attribute_name.value();
803 }
804 if (output_attribute_type.has_value()) {
805 opt.output_element_type = output_attribute_type.value();
806 }
807 if (pad_with_sign.has_value()) {
808 opt.pad_with_sign = pad_with_sign.value();
809 }
810 if (orthogonalize_bitangent.has_value()) {
811 opt.orthogonalize_bitangent = orthogonalize_bitangent.value();
812 }
813 if (keep_existing_tangent.has_value()) {
814 opt.keep_existing_tangent = keep_existing_tangent.value();
815 }
816
818 return std::make_tuple(r.tangent_id, r.bitangent_id);
819 },
820 "mesh"_a,
821 "tangent_attribute_name"_a = nb::none(),
822 "bitangent_attribute_name"_a = nb::none(),
823 "uv_attribute_name"_a = nb::none(),
824 "normal_attribute_name"_a = nb::none(),
825 "output_attribute_type"_a = nb::none(),
826 "pad_with_sign"_a = nb::none(),
827 "orthogonalize_bitangent"_a = nb::none(),
828 "keep_existing_tangent"_a = nb::none(),
829 R"(Compute tangent and bitangent vector attributes (Pythonic API).
830
831:param mesh: The input mesh.
832:param tangent_attribute_name: The name of the output tangent attribute.
833:param bitangent_attribute_name: The name of the output bitangent attribute.
834:param uv_attribute_name: The name of the uv attribute.
835:param normal_attribute_name: The name of the normal attribute.
836:param output_attribute_type: The output element type.
837:param pad_with_sign: Whether to pad the output tangent/bitangent with sign.
838:param orthogonalize_bitangent: Whether to compute the bitangent as sign * cross(normal, tangent).
839:param keep_existing_tangent: Whether to recompute tangent if the tangent attribute (specified by tangent_attribute_name) already exists. If true, bitangent is computed by normalizing cross(normal, tangent) and param orthogonalize_bitangent must be true.
840
841:returns: The tangent and bitangent attribute ids)");
842
843 m.def(
844 "map_attribute",
845 static_cast<AttributeId (*)(MeshType&, AttributeId, std::string_view, AttributeElement)>(
847 "mesh"_a,
848 "old_attribute_id"_a,
849 "new_attribute_name"_a,
850 "new_element"_a,
851 R"(Map an attribute to a new element type.
852
853:param mesh: The input mesh.
854:param old_attribute_id: The id of the input attribute.
855:param new_attribute_name: The name of the new attribute.
856:param new_element: The new element type.
857
858:returns: The id of the new attribute.)");
859
860 m.def(
861 "map_attribute",
862 static_cast<
863 AttributeId (*)(MeshType&, std::string_view, std::string_view, AttributeElement)>(
865 "mesh"_a,
866 "old_attribute_name"_a,
867 "new_attribute_name"_a,
868 "new_element"_a,
869 R"(Map an attribute to a new element type.
870
871:param mesh: The input mesh.
872:param old_attribute_name: The name of the input attribute.
873:param new_attribute_name: The name of the new attribute.
874:param new_element: The new element type.
875
876:returns: The id of the new attribute.)");
877
878 m.def(
879 "map_attribute_in_place",
880 static_cast<AttributeId (*)(MeshType&, AttributeId, AttributeElement)>(
882 "mesh"_a,
883 "id"_a,
884 "new_element"_a,
885 R"(Map an attribute to a new element type in place.
886
887:param mesh: The input mesh.
888:param id: The id of the input attribute.
889:param new_element: The new element type.
890
891:returns: The id of the new attribute.)");
892
893 m.def(
894 "map_attribute_in_place",
895 static_cast<AttributeId (*)(MeshType&, std::string_view, AttributeElement)>(
897 "mesh"_a,
898 "name"_a,
899 "new_element"_a,
900 R"(Map an attribute to a new element type in place.
901
902:param mesh: The input mesh.
903:param name: The name of the input attribute.
904:param new_element: The new element type.
905
906:returns: The id of the new attribute.)");
907
908 nb::class_<FacetAreaOptions>(m, "FacetAreaOptions", "Options for computing facet area.")
909 .def(nb::init<>())
910 .def_rw(
911 "output_attribute_name",
913 "The name of the output attribute.");
914
915 m.def(
916 "compute_facet_area",
918 "mesh"_a,
919 "options"_a = FacetAreaOptions(),
920 R"(Compute facet area.
921
922:param mesh: The input mesh.
923:param options: The options for computing facet area.
924
925:returns: The id of the new attribute.)");
926
927 m.def(
928 "compute_facet_area",
929 [](MeshType& mesh, std::optional<std::string_view> name) {
930 FacetAreaOptions opt;
931 if (name.has_value()) {
932 opt.output_attribute_name = name.value();
933 }
935 },
936 "mesh"_a,
937 "output_attribute_name"_a = nb::none(),
938 R"(Compute facet area (Pythonic API).
939
940:param mesh: The input mesh.
941:param output_attribute_name: The name of the output attribute.
942
943:returns: The id of the new attribute.)");
944
945 m.def(
946 "compute_facet_vector_area",
947 [](MeshType& mesh, std::optional<std::string_view> name) {
948 FacetVectorAreaOptions opt;
949 if (name.has_value()) {
950 opt.output_attribute_name = name.value();
951 }
953 },
954 "mesh"_a,
955 "output_attribute_name"_a = nb::none(),
956 R"(Compute facet vector area (Pythonic API).
957
958Vector area is defined as the area multiplied by the facet normal.
959For triangular facets, it is equivalent to half of the cross product of two edges.
960For non-planar polygonal facets, the vector area offers a robust way to compute the area and normal.
961The magnitude of the vector area is the largest area of any orthogonal projection of the facet.
962The direction of the vector area is the normal direction that maximizes the projected area [1, 2].
963
964[1] Sullivan, John M. "Curvatures of smooth and discrete surfaces." Discrete differential geometry.
965Basel: Birkhäuser Basel, 2008. 175-188.
966
967[2] Alexa, Marc, and Max Wardetzky. "Discrete Laplacians on general polygonal meshes." ACM SIGGRAPH
9682011 papers. 2011. 1-10.
969
970:param mesh: The input mesh.
971:param output_attribute_name: The name of the output attribute.
972
973:returns: The id of the new attribute.)");
974
975 nb::class_<MeshAreaOptions>(m, "MeshAreaOptions", "Options for computing mesh area.")
976 .def(nb::init<>())
977 .def_rw(
978 "input_attribute_name",
980 "The name of the pre-computed facet area attribute, default is `@facet_area`.")
981 .def_rw(
982 "use_signed_area",
984 "Whether to use signed area.");
985
986 m.def(
987 "compute_mesh_area",
989 "mesh"_a,
990 "options"_a = MeshAreaOptions(),
991 R"(Compute mesh area.
992
993:param mesh: The input mesh.
994:param options: The options for computing mesh area.
995
996:returns: The mesh area.)");
997
998 m.def(
999 "compute_uv_area",
1001 "mesh"_a,
1002 "options"_a = MeshAreaOptions(),
1003 R"(Compute UV mesh area.
1004
1005:param mesh: The input mesh.
1006:param options: The options for computing mesh area.
1007
1008:returns: The UV mesh area.)");
1009
1010 m.def(
1011 "compute_mesh_area",
1012 [](MeshType& mesh,
1013 std::optional<std::string_view> input_attribute_name,
1014 std::optional<bool> use_signed_area) {
1015 MeshAreaOptions opt;
1016 if (input_attribute_name.has_value()) {
1017 opt.input_attribute_name = input_attribute_name.value();
1018 }
1019 if (use_signed_area.has_value()) {
1020 opt.use_signed_area = use_signed_area.value();
1021 }
1022 return compute_mesh_area(mesh, opt);
1023 },
1024 "mesh"_a,
1025 "input_attribute_name"_a = nb::none(),
1026 "use_signed_area"_a = nb::none(),
1027 R"(Compute mesh area (Pythonic API).
1028
1029:param mesh: The input mesh.
1030:param input_attribute_name: The name of the pre-computed facet area attribute.
1031:param use_signed_area: Whether to use signed area.
1032
1033:returns: The mesh area.)");
1034
1035 nb::class_<FacetCentroidOptions>(m, "FacetCentroidOptions", "Facet centroid options.")
1036 .def(nb::init<>())
1037 .def_rw(
1038 "output_attribute_name",
1040 "The name of the output attribute.");
1041 m.def(
1042 "compute_facet_centroid",
1044 "mesh"_a,
1045 "options"_a = FacetCentroidOptions(),
1046 R"(Compute facet centroid.
1047
1048:param mesh: The input mesh.
1049:param options: The options for computing facet centroid.
1050
1051:returns: The id of the new attribute.)");
1052
1053 m.def(
1054 "compute_facet_centroid",
1055 [](MeshType& mesh, std::optional<std::string_view> output_attribute_name) {
1056 FacetCentroidOptions opt;
1057 if (output_attribute_name.has_value()) {
1058 opt.output_attribute_name = output_attribute_name.value();
1059 }
1061 },
1062 "mesh"_a,
1063 "output_attribute_name"_a = nb::none(),
1064 R"(Compute facet centroid (Pythonic API).
1065
1066:param mesh: Input mesh.
1067:param output_attribute_name: Output attribute name.
1068
1069:returns: Attribute ID.)");
1070
1071 m.def(
1072 "compute_facet_circumcenter",
1073 [](MeshType& mesh, std::optional<std::string_view> output_attribute_name) {
1074 FacetCircumcenterOptions opt;
1075 if (output_attribute_name.has_value()) {
1076 opt.output_attribute_name = output_attribute_name.value();
1077 }
1079 },
1080 "mesh"_a,
1081 "output_attribute_name"_a = nb::none(),
1082 R"(Compute facet circumcenter (Pythonic API).
1083
1084:param mesh: The input mesh.
1085:param output_attribute_name: The name of the output attribute.
1086
1087:returns: The id of the new attribute.)");
1088
1089 nb::enum_<MeshCentroidOptions::WeightingType>(
1090 m,
1091 "CentroidWeightingType",
1092 "Centroid weighting type.")
1093 .value("Uniform", MeshCentroidOptions::Uniform, "Uniform weighting.")
1094 .value("Area", MeshCentroidOptions::Area, "Area weighting.");
1095
1096 nb::class_<MeshCentroidOptions>(m, "MeshCentroidOptions", "Mesh centroid options.")
1097 .def(nb::init<>())
1098 .def_rw("weighting_type", &MeshCentroidOptions::weighting_type, "The weighting type.")
1099 .def_rw(
1100 "facet_centroid_attribute_name",
1102 "The name of the pre-computed facet centroid attribute if available.")
1103 .def_rw(
1104 "facet_area_attribute_name",
1106 "The name of the pre-computed facet area attribute if available.");
1107
1108 m.def(
1109 "compute_mesh_centroid",
1110 [](const MeshType& mesh, MeshCentroidOptions opt) {
1111 const Index dim = mesh.get_dimension();
1112 std::vector<Scalar> centroid(dim, invalid<Scalar>());
1113 compute_mesh_centroid<Scalar, Index>(mesh, centroid, opt);
1114 return centroid;
1115 },
1116 "mesh"_a,
1117 "options"_a = MeshCentroidOptions(),
1118 R"(Compute mesh centroid.
1119
1120:param mesh: Input mesh.
1121:param options: Centroid computation options.
1122
1123:returns: Mesh centroid coordinates.)");
1124
1125 m.def(
1126 "compute_mesh_centroid",
1127 [](MeshType& mesh,
1128 std::optional<MeshCentroidOptions::WeightingType> weighting_type,
1129 std::optional<std::string_view> facet_centroid_attribute_name,
1130 std::optional<std::string_view> facet_area_attribute_name) {
1131 MeshCentroidOptions opt;
1132 if (weighting_type.has_value()) {
1133 opt.weighting_type = weighting_type.value();
1134 }
1135 if (facet_centroid_attribute_name.has_value()) {
1136 opt.facet_centroid_attribute_name = facet_centroid_attribute_name.value();
1137 }
1138 if (facet_area_attribute_name.has_value()) {
1139 opt.facet_area_attribute_name = facet_area_attribute_name.value();
1140 }
1141 const Index dim = mesh.get_dimension();
1142 std::vector<Scalar> centroid(dim, invalid<Scalar>());
1143 compute_mesh_centroid<Scalar, Index>(mesh, centroid, opt);
1144 return centroid;
1145 },
1146 "mesh"_a,
1147 "weighting_type"_a = nb::none(),
1148 "facet_centroid_attribute_name"_a = nb::none(),
1149 "facet_area_attribute_name"_a = nb::none(),
1150 R"(Compute mesh centroid (Pythonic API).
1151
1152:param mesh: Input mesh.
1153:param weighting_type: Weighting type (default: Area).
1154:param facet_centroid_attribute_name: Pre-computed facet centroid attribute name.
1155:param facet_area_attribute_name: Pre-computed facet area attribute name.
1156
1157:returns: Mesh centroid coordinates.)");
1158
1159 m.def(
1160 "permute_vertices",
1161 [](MeshType& mesh, Tensor<Index> new_to_old) {
1162 auto [data, shape, stride] = tensor_to_span(new_to_old);
1163 la_runtime_assert(is_dense(shape, stride));
1165 },
1166 "mesh"_a,
1167 "new_to_old"_a,
1168 R"(Reorder vertices of a mesh in place based on a permutation.
1169
1170:param mesh: input mesh
1171:param new_to_old: permutation vector for vertices)");
1172
1173 m.def(
1174 "permute_facets",
1175 [](MeshType& mesh, Tensor<Index> new_to_old) {
1176 auto [data, shape, stride] = tensor_to_span(new_to_old);
1177 la_runtime_assert(is_dense(shape, stride));
1179 },
1180 "mesh"_a,
1181 "new_to_old"_a,
1182 R"(Reorder facets of a mesh in place based on a permutation.
1183
1184:param mesh: input mesh
1185:param new_to_old: permutation vector for facets)");
1186
1187 nb::enum_<MappingPolicy>(m, "MappingPolicy", "Mapping policy for handling collisions.")
1188 .value("Average", MappingPolicy::Average, "Compute the average of the collided values.")
1189 .value("KeepFirst", MappingPolicy::KeepFirst, "Keep the first collided value.")
1190 .value("Error", MappingPolicy::Error, "Throw an error when collision happens.");
1191
1192 nb::class_<RemapVerticesOptions>(m, "RemapVerticesOptions", "Options for remapping vertices.")
1193 .def(nb::init<>())
1194 .def_rw(
1195 "collision_policy_float",
1197 "The collision policy for float attributes.")
1198 .def_rw(
1199 "collision_policy_integral",
1201 "The collision policy for integral attributes.");
1202
1203 m.def(
1204 "remap_vertices",
1205 [](MeshType& mesh, Tensor<Index> old_to_new, RemapVerticesOptions opt) {
1206 auto [data, shape, stride] = tensor_to_span(old_to_new);
1207 la_runtime_assert(is_dense(shape, stride));
1208 remap_vertices<Scalar, Index>(mesh, data, opt);
1209 },
1210 "mesh"_a,
1211 "old_to_new"_a,
1212 "options"_a = RemapVerticesOptions(),
1213 R"(Remap vertices of a mesh in place based on a permutation.
1214
1215:param mesh: input mesh
1216:param old_to_new: permutation vector for vertices
1217:param options: options for remapping vertices)");
1218
1219 m.def(
1220 "remap_vertices",
1221 [](MeshType& mesh,
1222 Tensor<Index> old_to_new,
1223 std::optional<MappingPolicy> collision_policy_float,
1224 std::optional<MappingPolicy> collision_policy_integral) {
1225 RemapVerticesOptions opt;
1226 if (collision_policy_float.has_value()) {
1227 opt.collision_policy_float = collision_policy_float.value();
1228 }
1229 if (collision_policy_integral.has_value()) {
1230 opt.collision_policy_integral = collision_policy_integral.value();
1231 }
1232 auto [data, shape, stride] = tensor_to_span(old_to_new);
1233 la_runtime_assert(is_dense(shape, stride));
1234 remap_vertices<Scalar, Index>(mesh, data, opt);
1235 },
1236 "mesh"_a,
1237 "old_to_new"_a,
1238 "collision_policy_float"_a = nb::none(),
1239 "collision_policy_integral"_a = nb::none(),
1240 R"(Remap vertices of a mesh in place based on a permutation (Pythonic API).
1241
1242:param mesh: input mesh
1243:param old_to_new: permutation vector for vertices
1244:param collision_policy_float: The collision policy for float attributes.
1245:param collision_policy_integral: The collision policy for integral attributes.)");
1246
1247 m.def(
1248 "reorder_mesh",
1249 [](MeshType& mesh, std::string_view method) {
1250 lagrange::ReorderingMethod reorder_method;
1251 if (method == "Lexicographic" || method == "lexicographic") {
1252 reorder_method = ReorderingMethod::Lexicographic;
1253 } else if (method == "Morton" || method == "morton") {
1254 reorder_method = ReorderingMethod::Morton;
1255 } else if (method == "Hilbert" || method == "hilbert") {
1256 reorder_method = ReorderingMethod::Hilbert;
1257 } else if (method == "None" || method == "none") {
1258 reorder_method = ReorderingMethod::None;
1259 } else {
1260 throw std::runtime_error(lagrange::format("Invalid reordering method: {}", method));
1261 }
1262
1263 lagrange::reorder_mesh(mesh, reorder_method);
1264 },
1265 "mesh"_a,
1266 "method"_a = "Morton",
1267 R"(Reorder a mesh in place.
1268
1269:param mesh: input mesh
1270:param method: reordering method, options are 'Lexicographic', 'Morton', 'Hilbert', 'None' (default is 'Morton').)",
1271 nb::sig(
1272 "def reorder_mesh(mesh: SurfaceMesh, "
1273 "method: typing.Literal['Lexicographic', 'Morton', 'Hilbert', 'None']) -> None"));
1274
1275 m.def(
1276 "separate_by_facet_groups",
1277 [](MeshType& mesh,
1278 Tensor<Index> facet_group_indices,
1279 std::string_view source_vertex_attr_name,
1280 std::string_view source_facet_attr_name,
1281 bool map_attributes) {
1282 SeparateByFacetGroupsOptions options;
1283 options.source_vertex_attr_name = source_vertex_attr_name;
1284 options.source_facet_attr_name = source_facet_attr_name;
1285 options.map_attributes = map_attributes;
1286 auto [data, shape, stride] = tensor_to_span(facet_group_indices);
1287 la_runtime_assert(is_dense(shape, stride));
1288 return separate_by_facet_groups<Scalar, Index>(mesh, data, options);
1289 },
1290 "mesh"_a,
1291 "facet_group_indices"_a,
1292 "source_vertex_attr_name"_a = "",
1293 "source_facet_attr_name"_a = "",
1294 "map_attributes"_a = false,
1295 R"(Extract a set of submeshes based on facet groups.
1296
1297:param mesh: The source mesh.
1298:param facet_group_indices: The group index for each facet. Each group index must be in the range of [0, max(facet_group_indices)]
1299:param source_vertex_attr_name: The optional attribute name to track source vertices.
1300:param source_facet_attr_name: The optional attribute name to track source facets.
1301
1302:returns: A list of meshes, one for each facet group.
1303)");
1304
1305 m.def(
1306 "separate_by_components",
1307 [](MeshType& mesh,
1308 std::string_view source_vertex_attr_name,
1309 std::string_view source_facet_attr_name,
1310 bool map_attributes,
1311 ConnectivityType connectivity_type) {
1312 SeparateByComponentsOptions options;
1313 options.source_vertex_attr_name = source_vertex_attr_name;
1314 options.source_facet_attr_name = source_facet_attr_name;
1315 options.map_attributes = map_attributes;
1316 options.connectivity_type = connectivity_type;
1317 return separate_by_components(mesh, options);
1318 },
1319 "mesh"_a,
1320 "source_vertex_attr_name"_a = "",
1321 "source_facet_attr_name"_a = "",
1322 "map_attributes"_a = false,
1323 "connectivity_type"_a = ConnectivityType::Edge,
1324 R"(Extract a set of submeshes based on connected components.
1325
1326:param mesh: The source mesh.
1327:param source_vertex_attr_name: The optional attribute name to track source vertices.
1328:param source_facet_attr_name: The optional attribute name to track source facets.
1329:param map_attributes: Map attributes from the source to target meshes.
1330:param connectivity_type: The connectivity used for component computation.
1331
1332:returns: A list of meshes, one for each connected component.
1333)");
1334
1335 m.def(
1336 "extract_submesh",
1337 [](MeshType& mesh,
1338 std::variant<Tensor<Index>, nb::list> selected_facets,
1339 std::string_view source_vertex_attr_name,
1340 std::string_view source_facet_attr_name,
1341 bool map_attributes) {
1342 SubmeshOptions options;
1343 options.source_vertex_attr_name = source_vertex_attr_name;
1344 options.source_facet_attr_name = source_facet_attr_name;
1345 options.map_attributes = map_attributes;
1346 if (std::holds_alternative<nb::list>(selected_facets)) {
1347 auto selected_facets_list =
1348 nb::cast<std::vector<Index>>(std::get<nb::list>(selected_facets));
1349 span<const Index> data{selected_facets_list.data(), selected_facets_list.size()};
1350 return extract_submesh<Scalar, Index>(mesh, data, options);
1351 } else {
1352 auto selected_facets_tensor = std::get<Tensor<Index>>(selected_facets);
1353 auto [data, shape, stride] = tensor_to_span(selected_facets_tensor);
1354 la_runtime_assert(is_dense(shape, stride));
1355 return extract_submesh<Scalar, Index>(mesh, data, options);
1356 }
1357 },
1358 "mesh"_a,
1359 "selected_facets"_a,
1360 "source_vertex_attr_name"_a = "",
1361 "source_facet_attr_name"_a = "",
1362 "map_attributes"_a = false,
1363 R"(Extract a submesh based on the selected facets.
1364
1365:param mesh: The source mesh.
1366:param selected_facets: A list or tensor of facet ids to extract.
1367:param source_vertex_attr_name: The optional attribute name to track source vertices.
1368:param source_facet_attr_name: The optional attribute name to track source facets.
1369:param map_attributes: Map attributes from the source to target meshes.
1370
1371:returns: A mesh that contains only the selected facets.
1372)");
1373
1374 m.def(
1375 "compute_dihedral_angles",
1376 [](MeshType& mesh,
1377 std::optional<std::string_view> output_attribute_name,
1378 std::optional<std::string_view> facet_normal_attribute_name,
1379 std::optional<bool> recompute_facet_normals,
1380 std::optional<bool> keep_facet_normals) {
1381 DihedralAngleOptions options;
1382 if (output_attribute_name.has_value()) {
1383 options.output_attribute_name = output_attribute_name.value();
1384 }
1385 if (facet_normal_attribute_name.has_value()) {
1386 options.facet_normal_attribute_name = facet_normal_attribute_name.value();
1387 }
1388 if (recompute_facet_normals.has_value()) {
1389 options.recompute_facet_normals = recompute_facet_normals.value();
1390 }
1391 if (keep_facet_normals.has_value()) {
1392 options.keep_facet_normals = keep_facet_normals.value();
1393 }
1394 return compute_dihedral_angles(mesh, options);
1395 },
1396 "mesh"_a,
1397 "output_attribute_name"_a = nb::none(),
1398 "facet_normal_attribute_name"_a = nb::none(),
1399 "recompute_facet_normals"_a = nb::none(),
1400 "keep_facet_normals"_a = nb::none(),
1401 R"(Compute dihedral angles for each edge.
1402
1403The dihedral angle of an edge is defined as the angle between the __normals__ of two facets adjacent
1404to the edge. The dihedral angle is always in the range [0, pi] for manifold edges. For boundary
1405edges, the dihedral angle defaults to 0. For non-manifold edges, the dihedral angle is not
1406well-defined and will be set to the special value 2 * π.
1407
1408:param mesh: The source mesh.
1409:param output_attribute_name: The optional edge attribute name to store the dihedral angles.
1410:param facet_normal_attribute_name: The optional attribute name to store the facet normals.
1411:param recompute_facet_normals: Whether to recompute facet normals.
1412:param keep_facet_normals: Whether to keep newly computed facet normals. It has no effect on pre-existing facet normals.
1413
1414:return: The edge attribute id of dihedral angles.)");
1415
1416 m.def(
1417 "compute_edge_lengths",
1418 [](MeshType& mesh, std::optional<std::string_view> output_attribute_name) {
1419 EdgeLengthOptions options;
1420 if (output_attribute_name.has_value())
1421 options.output_attribute_name = output_attribute_name.value();
1422 return compute_edge_lengths(mesh, options);
1423 },
1424 "mesh"_a,
1425 "output_attribute_name"_a = nb::none(),
1426 R"(Compute edge lengths.
1427
1428:param mesh: The source mesh.
1429:param output_attribute_name: The optional edge attribute name to store the edge lengths.
1430
1431:return: The edge attribute id of edge lengths.)");
1432
1433 m.def(
1434 "compute_dijkstra_distance",
1435 [](MeshType& mesh,
1436 Index seed_facet,
1437 const nb::list& barycentric_coords,
1438 std::optional<Scalar> radius,
1439 std::string_view output_attribute_name,
1440 bool output_involved_vertices) {
1441 DijkstraDistanceOptions<Scalar, Index> options;
1442 options.seed_facet = seed_facet;
1443 for (auto val : barycentric_coords) {
1444 options.barycentric_coords.push_back(nb::cast<Scalar>(val));
1445 }
1446 if (radius.has_value()) {
1447 options.radius = radius.value();
1448 }
1449 options.output_attribute_name = output_attribute_name;
1450 options.output_involved_vertices = output_involved_vertices;
1451 return compute_dijkstra_distance(mesh, options);
1452 },
1453 "mesh"_a,
1454 "seed_facet"_a,
1455 "barycentric_coords"_a,
1456 "radius"_a = nb::none(),
1457 "output_attribute_name"_a = DijkstraDistanceOptions<Scalar, Index>{}.output_attribute_name,
1458 "output_involved_vertices"_a =
1459 DijkstraDistanceOptions<Scalar, Index>{}.output_involved_vertices,
1460 R"(Compute Dijkstra distance from a seed facet.
1461
1462:param mesh: The source mesh.
1463:param seed_facet: The seed facet index.
1464:param barycentric_coords: The barycentric coordinates of the seed facet.
1465:param radius: The maximum radius of the dijkstra distance.
1466:param output_attribute_name: The output attribute name to store the dijkstra distance.
1467:param output_involved_vertices: Whether to output the list of involved vertices.)");
1468
1469 m.def(
1470 "weld_indexed_attribute",
1471 [](MeshType& mesh,
1472 AttributeId attribute_id,
1473 std::optional<double> epsilon_rel,
1474 std::optional<double> epsilon_abs,
1475 std::optional<double> angle_abs,
1476 std::optional<std::vector<size_t>> exclude_vertices) {
1477 WeldOptions options;
1478 options.epsilon_rel = epsilon_rel;
1479 options.epsilon_abs = epsilon_abs;
1480 options.angle_abs = angle_abs;
1481 if (exclude_vertices.has_value()) {
1482 const auto& exclude_vertices_vec = exclude_vertices.value();
1483 options.exclude_vertices = {
1484 exclude_vertices_vec.data(),
1485 exclude_vertices_vec.size()};
1486 }
1487 return weld_indexed_attribute(mesh, attribute_id, options);
1488 },
1489 "mesh"_a,
1490 "attribute_id"_a,
1491 "epsilon_rel"_a = nb::none(),
1492 "epsilon_abs"_a = nb::none(),
1493 "angle_abs"_a = nb::none(),
1494 "exclude_vertices"_a = nb::none(),
1495 R"(Weld indexed attribute.
1496
1497:param mesh: The source mesh to be updated in place.
1498:param attribute_id: The indexed attribute id to weld.
1499:param epsilon_rel: The relative tolerance for welding.
1500:param epsilon_abs: The absolute tolerance for welding.
1501:param angle_abs: The absolute angle tolerance for welding.
1502:param exclude_vertices: Optional list of vertex indices to exclude from welding.)");
1503
1504 m.def(
1505 "compute_euler",
1507 "mesh"_a,
1508 R"(Compute the Euler characteristic.
1509
1510:param mesh: The source mesh.
1511
1512:return: The Euler characteristic.)");
1513
1514 m.def(
1515 "is_closed",
1517 "mesh"_a,
1518 R"(Check if the mesh is closed.
1519
1520A mesh is considered closed if it has no boundary edges.
1521
1522:param mesh: The source mesh.
1523
1524:return: Whether the mesh is closed.)");
1525
1526 m.def(
1527 "is_vertex_manifold",
1529 "mesh"_a,
1530 R"(Check if the mesh is vertex manifold.
1531
1532:param mesh: The source mesh.
1533
1534:return: Whether the mesh is vertex manifold.)");
1535
1536 m.def(
1537 "is_edge_manifold",
1539 "mesh"_a,
1540 R"(Check if the mesh is edge manifold.
1541
1542:param mesh: The source mesh.
1543
1544:return: Whether the mesh is edge manifold.)");
1545
1546 m.def("is_manifold", &is_manifold<Scalar, Index>, "mesh"_a, R"(Check if the mesh is manifold.
1547
1548A mesh considered as manifold if it is both vertex and edge manifold.
1549
1550:param mesh: The source mesh.
1551
1552:return: Whether the mesh is manifold.)");
1553
1554 m.def(
1555 "compute_vertex_is_manifold",
1556 [](MeshType& mesh, std::string_view output_attribute_name) {
1557 VertexManifoldOptions options;
1558 options.output_attribute_name = output_attribute_name;
1559 return compute_vertex_is_manifold(mesh, options);
1560 },
1561 "mesh"_a,
1562 "output_attribute_name"_a = VertexManifoldOptions().output_attribute_name,
1563 R"(Compute whether each vertex is manifold.
1564
1565A vertex is considered manifold if its one-ring neighborhood is homeomorphic to a disk.
1566
1567:param mesh: The source mesh.
1568:param output_attribute_name: The output vertex attribute name.
1569
1570:return: The attribute id of a vertex attribute indicating whether a vertex is manifold.)");
1571
1572 m.def(
1573 "compute_edge_is_manifold",
1574 [](MeshType& mesh, std::string_view output_attribute_name) {
1575 EdgeManifoldOptions options;
1576 options.output_attribute_name = output_attribute_name;
1577 return compute_edge_is_manifold(mesh, options);
1578 },
1579 "mesh"_a,
1580 "output_attribute_name"_a = EdgeManifoldOptions().output_attribute_name,
1581 R"(Compute whether each edge is manifold.
1582
1583An edge is considered manifold if it is adjacent to one or two facets.
1584
1585:param mesh: The source mesh.
1586:param output_attribute_name: The output edge attribute name.
1587
1588:return: The attribute id of an edge attribute indicating whether an edge is manifold.)");
1589
1590 m.def(
1591 "is_oriented",
1593 "mesh"_a,
1594 R"(Check if the mesh is oriented.
1595
1596A mesh is oriented if all interior edges are oriented. An interior edge is considered as
1597oriented if it has the same number of half-edges for each edge direction. I.e. the number of
1598facets that use the edge in one direction equals the number of facets that use the edge in the
1599opposite direction. Boundary edges are always considered as oriented.
1600
1601:param mesh: The source mesh.
1602
1603:return: Whether the mesh is oriented.)");
1604
1605 m.def(
1606 "compute_edge_is_oriented",
1607 [](MeshType& mesh, std::string_view output_attribute_name) {
1608 OrientationOptions options;
1609 options.output_attribute_name = output_attribute_name;
1610 return compute_edge_is_oriented(mesh, options);
1611 },
1612 "mesh"_a,
1613 "output_attribute_name"_a = OrientationOptions().output_attribute_name,
1614 R"(Compute whether each edge is oriented.
1615
1616An interior edge is considered as oriented if it has the same number of half-edges for each edge
1617direction. I.e. the number of facets that use the edge in one direction equals to the number of
1618facets that use the edge in the opposite direction. Boundary edges are always considered as
1619oriented.
1620
1621:param mesh: The source mesh.
1622:param output_attribute_name: The output edge attribute name.
1623
1624:return: The attribute id of an edge attribute indicating whether an edge is oriented.)");
1625
1626 m.def(
1627 "transform_mesh",
1628 [](MeshType& mesh,
1629 Eigen::Matrix<Scalar, 4, 4> affine_transform,
1630 bool normalize_normals,
1631 bool normalize_tangents_bitangents,
1632 bool in_place) -> std::optional<MeshType> {
1633 Eigen::Transform<Scalar, 3, Eigen::Affine> M(affine_transform);
1634 TransformOptions options;
1635 options.normalize_normals = normalize_normals;
1636 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
1637
1638 std::optional<MeshType> result;
1639 if (in_place) {
1640 transform_mesh(mesh, M, options);
1641 } else {
1642 result = transformed_mesh(mesh, M, options);
1643 }
1644 return result;
1645 },
1646 "mesh"_a,
1647 "affine_transform"_a,
1648 "normalize_normals"_a = TransformOptions().normalize_normals,
1649 "normalize_tangents_bitangents"_a = TransformOptions().normalize_tangents_bitangents,
1650 "in_place"_a = true,
1651 R"(Apply affine transformation to a mesh.
1652
1653:param mesh: Input mesh.
1654:param affine_transform: Affine transformation matrix.
1655:param normalize_normals: Whether to normalize normals.
1656:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.
1657:param in_place: Whether to apply transformation in place.
1658
1659:returns: Transformed mesh if in_place is False.)");
1660
1661 nb::enum_<DistortionMetric>(m, "DistortionMetric", "Distortion metric.")
1662 .value("Dirichlet", DistortionMetric::Dirichlet, "Dirichlet energy")
1663 .value("InverseDirichlet", DistortionMetric::InverseDirichlet, "Inverse Dirichlet energy")
1664 .value(
1665 "SymmetricDirichlet",
1667 "Symmetric Dirichlet energy")
1668 .value("AreaRatio", DistortionMetric::AreaRatio, "Area ratio")
1669 .value("MIPS", DistortionMetric::MIPS, "Most isotropic parameterization energy");
1670
1671 m.def(
1672 "compute_uv_distortion",
1673 [](MeshType& mesh,
1674 std::string_view uv_attribute_name,
1675 std::string_view output_attribute_name,
1676 DistortionMetric metric) {
1677 UVDistortionOptions opt;
1678 opt.uv_attribute_name = uv_attribute_name;
1679 opt.output_attribute_name = output_attribute_name;
1680 opt.metric = metric;
1681 return compute_uv_distortion(mesh, opt);
1682 },
1683 "mesh"_a,
1684 "uv_attribute_name"_a = "@uv",
1685 "output_attribute_name"_a = "@uv_measure",
1687 R"(Compute UV distortion.
1688
1689:param mesh: Input mesh.
1690:param uv_attribute_name: UV attribute name (default: "@uv").
1691:param output_attribute_name: Output attribute name (default: "@uv_measure").
1692:param metric: Distortion metric (default: MIPS).
1693
1694:returns: Facet attribute ID for distortion.)");
1695
1696 m.def(
1697 "trim_by_isoline",
1698 [](const MeshType& mesh,
1699 std::variant<AttributeId, std::string_view> attribute,
1700 double isovalue,
1701 bool keep_below) {
1702 IsolineOptions opt;
1703 if (std::holds_alternative<AttributeId>(attribute)) {
1704 opt.attribute_id = std::get<AttributeId>(attribute);
1705 } else {
1706 opt.attribute_id = mesh.get_attribute_id(std::get<std::string_view>(attribute));
1707 }
1708 opt.isovalue = isovalue;
1709 opt.keep_below = keep_below;
1710 return trim_by_isoline(mesh, opt);
1711 },
1712 "mesh"_a,
1713 "attribute"_a,
1714 "isovalue"_a = IsolineOptions().isovalue,
1715 "keep_below"_a = IsolineOptions().keep_below,
1716 R"(Trim a triangle mesh by an isoline.
1717
1718:param mesh: Input triangle mesh.
1719:param attribute: Attribute ID or name of scalar field (vertex or indexed).
1720:param isovalue: Isovalue to trim with.
1721:param keep_below: Whether to keep the part below the isoline.
1722
1723:returns: Trimmed mesh.)");
1724
1725 m.def(
1726 "extract_isoline",
1727 [](const MeshType& mesh,
1728 std::variant<AttributeId, std::string_view> attribute,
1729 double isovalue) {
1730 IsolineOptions opt;
1731 if (std::holds_alternative<AttributeId>(attribute)) {
1732 opt.attribute_id = std::get<AttributeId>(attribute);
1733 } else {
1734 opt.attribute_id = mesh.get_attribute_id(std::get<std::string_view>(attribute));
1735 }
1736 opt.isovalue = isovalue;
1737 return extract_isoline(mesh, opt);
1738 },
1739 "mesh"_a,
1740 "attribute"_a,
1741 "isovalue"_a = IsolineOptions().isovalue,
1742 R"(Extract the isoline of an implicit function defined on the mesh vertices/corners.
1743
1744The input mesh must be a triangle mesh.
1745
1746:param mesh: Input triangle mesh to extract the isoline from.
1747:param attribute: Attribute id or name of the scalar field to use. Can be a vertex or indexed attribute.
1748:param isovalue: Isovalue to extract.
1749
1750:return: A mesh whose facets is a collection of size 2 elements representing the extracted isoline.)");
1751
1752 using AttributeNameOrId = AttributeFilter::AttributeNameOrId;
1753 m.def(
1754 "filter_attributes",
1755 [](MeshType& mesh,
1756 std::optional<std::vector<AttributeNameOrId>> included_attributes,
1757 std::optional<std::vector<AttributeNameOrId>> excluded_attributes,
1758 std::optional<std::unordered_set<AttributeUsage>> included_usages,
1759 std::optional<std::unordered_set<AttributeElement>> included_element_types) {
1760 AttributeFilter filter;
1761 if (included_attributes.has_value()) {
1762 filter.included_attributes = included_attributes.value();
1763 }
1764 if (excluded_attributes.has_value()) {
1765 filter.excluded_attributes = excluded_attributes.value();
1766 }
1767 if (included_usages.has_value()) {
1768 filter.included_usages.clear_all();
1769 for (auto usage : included_usages.value()) {
1770 filter.included_usages.set(usage);
1771 }
1772 }
1773 if (included_element_types.has_value()) {
1774 filter.included_element_types.clear_all();
1775 for (auto element_type : included_element_types.value()) {
1776 filter.included_element_types.set(element_type);
1777 }
1778 }
1779 return filter_attributes(mesh, filter);
1780 },
1781 "mesh"_a,
1782 "included_attributes"_a = nb::none(),
1783 "excluded_attributes"_a = nb::none(),
1784 "included_usages"_a = nb::none(),
1785 "included_element_types"_a = nb::none(),
1786 R"(Filters the attributes of mesh according to user specifications.
1787
1788:param mesh: Input mesh.
1789:param included_attributes: List of attribute names or ids to include. By default, all attributes are included.
1790:param excluded_attributes: List of attribute names or ids to exclude. By default, no attribute is excluded.
1791:param included_usages: List of attribute usages to include. By default, all usages are included.
1792:param included_element_types: List of attribute element types to include. By default, all element types are included.)");
1793
1794 m.def(
1795 "cast_attribute",
1796 [](MeshType& mesh,
1797 std::variant<AttributeId, std::string_view> input_attribute,
1798 nb::type_object dtype,
1799 std::optional<std::string_view> output_attribute_name) {
1801 auto cast = [&](AttributeId attr_id) {
1802 auto np = nb::module_::import_("numpy");
1803 if (output_attribute_name.has_value()) {
1804 auto name = output_attribute_name.value();
1805 if (dtype.is(&PyFloat_Type)) {
1806 // Native python float is a C double.
1807 return cast_attribute<double>(mesh, attr_id, name);
1808 } else if (dtype.is(&PyLong_Type)) {
1809 // Native python int maps to int64.
1810 return cast_attribute<int64_t>(mesh, attr_id, name);
1811 } else if (dtype.is(np.attr("float32"))) {
1812 return cast_attribute<float>(mesh, attr_id, name);
1813 } else if (dtype.is(np.attr("float64"))) {
1814 return cast_attribute<double>(mesh, attr_id, name);
1815 } else if (dtype.is(np.attr("int8"))) {
1816 return cast_attribute<int8_t>(mesh, attr_id, name);
1817 } else if (dtype.is(np.attr("int16"))) {
1818 return cast_attribute<int16_t>(mesh, attr_id, name);
1819 } else if (dtype.is(np.attr("int32"))) {
1820 return cast_attribute<int32_t>(mesh, attr_id, name);
1821 } else if (dtype.is(np.attr("int64"))) {
1822 return cast_attribute<int64_t>(mesh, attr_id, name);
1823 } else if (dtype.is(np.attr("uint8"))) {
1824 return cast_attribute<uint8_t>(mesh, attr_id, name);
1825 } else if (dtype.is(np.attr("uint16"))) {
1826 return cast_attribute<uint16_t>(mesh, attr_id, name);
1827 } else if (dtype.is(np.attr("uint32"))) {
1828 return cast_attribute<uint32_t>(mesh, attr_id, name);
1829 } else if (dtype.is(np.attr("uint64"))) {
1830 return cast_attribute<uint64_t>(mesh, attr_id, name);
1831 } else {
1832 throw nb::type_error("Unsupported `dtype`!");
1833 }
1834 } else {
1835 if (dtype.is(&PyFloat_Type)) {
1836 // Native python float is a C double.
1837 return cast_attribute_in_place<double>(mesh, attr_id);
1838 } else if (dtype.is(&PyLong_Type)) {
1839 // Native python int maps to int64.
1840 return cast_attribute_in_place<int64_t>(mesh, attr_id);
1841 } else if (dtype.is(np.attr("float32"))) {
1842 return cast_attribute_in_place<float>(mesh, attr_id);
1843 } else if (dtype.is(np.attr("float64"))) {
1844 return cast_attribute_in_place<double>(mesh, attr_id);
1845 } else if (dtype.is(np.attr("int8"))) {
1846 return cast_attribute_in_place<int8_t>(mesh, attr_id);
1847 } else if (dtype.is(np.attr("int16"))) {
1848 return cast_attribute_in_place<int16_t>(mesh, attr_id);
1849 } else if (dtype.is(np.attr("int32"))) {
1850 return cast_attribute_in_place<int32_t>(mesh, attr_id);
1851 } else if (dtype.is(np.attr("int64"))) {
1852 return cast_attribute_in_place<int64_t>(mesh, attr_id);
1853 } else if (dtype.is(np.attr("uint8"))) {
1854 return cast_attribute_in_place<uint8_t>(mesh, attr_id);
1855 } else if (dtype.is(np.attr("uint16"))) {
1856 return cast_attribute_in_place<uint16_t>(mesh, attr_id);
1857 } else if (dtype.is(np.attr("uint32"))) {
1858 return cast_attribute_in_place<uint32_t>(mesh, attr_id);
1859 } else if (dtype.is(np.attr("uint64"))) {
1860 return cast_attribute_in_place<uint64_t>(mesh, attr_id);
1861 } else {
1862 throw nb::type_error("Unsupported `dtype`!");
1863 }
1864 }
1865 };
1866
1867 if (std::holds_alternative<AttributeId>(input_attribute)) {
1868 return cast(std::get<AttributeId>(input_attribute));
1869 } else {
1870 AttributeId id = mesh.get_attribute_id(std::get<std::string_view>(input_attribute));
1871 return cast(id);
1872 }
1873 },
1874 "mesh"_a,
1875 "input_attribute"_a,
1876 "dtype"_a,
1877 "output_attribute_name"_a = nb::none(),
1878 R"(Cast an attribute to a new dtype.
1879
1880:param mesh: The input mesh.
1881:param input_attribute: The input attribute id or name.
1882:param dtype: The new dtype.
1883:param output_attribute_name: The output attribute name. If none, cast will replace the input attribute.
1884
1885:returns: The id of the new attribute.)");
1886
1887 m.def(
1888 "get_unique_attribute_name",
1889 [](const MeshType& mesh,
1890 std::string_view name,
1891 std::string separator,
1892 std::string postfix,
1893 int max_increment,
1894 bool emit_warning) {
1895 UniqueAttributeNameOptions options;
1896 options.separator = std::move(separator);
1897 options.postfix = std::move(postfix);
1898 options.max_increment = max_increment;
1899 options.emit_warning = emit_warning;
1900 return get_unique_attribute_name(mesh, name, options);
1901 },
1902 "mesh"_a,
1903 "name"_a,
1904 "separator"_a = UniqueAttributeNameOptions().separator,
1905 "postfix"_a = UniqueAttributeNameOptions().postfix,
1906 "max_increment"_a = UniqueAttributeNameOptions().max_increment,
1907 "emit_warning"_a = UniqueAttributeNameOptions().emit_warning,
1908 R"(Get a unique attribute name for a mesh.
1909
1910If the desired name does not exist on the mesh it is returned as-is. If it
1911already exists, a suffix of the form ``{separator}{count}{postfix}`` is appended
1912until a unique name is found. An exception is raised if no unique name can be
1913found after ``max_increment`` attempts.
1914
1915:param mesh: The input mesh.
1916:param name: The desired attribute name.
1917:param separator: Separator between the base name and counter (default: ".").
1918:param postfix: Postfix to append after the counter (default: "").
1919:param max_increment: Maximum number of attempts to find a unique name (default: 1000).
1920:param emit_warning: Whether to log a warning when a collision is detected (default: True).
1921
1922:returns: A unique attribute name.)");
1923
1924 m.def(
1925 "compute_mesh_covariance",
1926 [](MeshType& mesh,
1927 std::array<Scalar, 3> center,
1928 std::optional<std::string_view> active_facets_attribute_name)
1929 -> std::array<std::array<Scalar, 3>, 3> {
1930 MeshCovarianceOptions options;
1931 options.center = center;
1932 options.active_facets_attribute_name = active_facets_attribute_name;
1933 return compute_mesh_covariance<Scalar, Index>(mesh, options);
1934 },
1935 "mesh"_a,
1936 "center"_a,
1937 "active_facets_attribute_name"_a = nb::none(),
1938 R"(Compute the covariance matrix of a mesh w.r.t. a center (Pythonic API).
1939
1940:param mesh: Input mesh.
1941:param center: The center of the covariance computation.
1942:param active_facets_attribute_name: (optional) Attribute name of whether a facet should be considered in the computation.
1943
1944:returns: The 3 by 3 covariance matrix, which should be symmetric.)");
1945
1946 m.def(
1947 "select_facets_by_normal_similarity",
1948 [](MeshType& mesh,
1949 Index seed_facet_id,
1950 std::optional<double> flood_error_limit,
1951 std::optional<double> flood_second_to_first_order_limit_ratio,
1952 std::optional<std::string_view> facet_normal_attribute_name,
1953 std::optional<std::string_view> is_facet_selectable_attribute_name,
1954 std::optional<std::string_view> output_attribute_name,
1955 std::optional<std::string_view> search_type,
1956 std::optional<int> num_smooth_iterations) {
1957 // Set options in the C++ struct
1958 SelectFacetsByNormalSimilarityOptions options;
1959 if (flood_error_limit.has_value())
1960 options.flood_error_limit = flood_error_limit.value();
1961 if (flood_second_to_first_order_limit_ratio.has_value())
1962 options.flood_second_to_first_order_limit_ratio =
1963 flood_second_to_first_order_limit_ratio.value();
1964 if (facet_normal_attribute_name.has_value())
1965 options.facet_normal_attribute_name = facet_normal_attribute_name.value();
1966 if (is_facet_selectable_attribute_name.has_value()) {
1967 options.is_facet_selectable_attribute_name = is_facet_selectable_attribute_name;
1968 }
1969 if (output_attribute_name.has_value())
1970 options.output_attribute_name = output_attribute_name.value();
1971 if (search_type.has_value()) {
1972 if (search_type.value() == "BFS")
1974 else if (search_type.value() == "DFS")
1976 else
1977 throw std::runtime_error(
1978 lagrange::format("Invalid search type: {}", search_type.value()));
1979 }
1980 if (num_smooth_iterations.has_value())
1981 options.num_smooth_iterations = num_smooth_iterations.value();
1982
1983 return select_facets_by_normal_similarity<Scalar, Index>(mesh, seed_facet_id, options);
1984 },
1985 "mesh"_a, /* `_a` is a literal for nanobind to create nb::args, a required argument */
1986 "seed_facet_id"_a,
1987 "flood_error_limit"_a = nb::none(),
1988 "flood_second_to_first_order_limit_ratio"_a = nb::none(),
1989 "facet_normal_attribute_name"_a = nb::none(),
1990 "is_facet_selectable_attribute_name"_a = nb::none(),
1991 "output_attribute_name"_a = nb::none(),
1992 "search_type"_a = nb::none(),
1993 "num_smooth_iterations"_a = nb::none(),
1994 R"(Select facets by normal similarity (Pythonic API).
1995
1996:param mesh: Input mesh.
1997:param seed_facet_id: Index of the seed facet.
1998:param flood_error_limit: Tolerance for normals of the seed and the selected facets. Higher limit leads to larger selected region.
1999:param flood_second_to_first_order_limit_ratio: Ratio of the flood_error_limit and the tolerance for normals of neighboring selected facets. Higher ratio leads to more curvature in selected region.
2000:param facet_normal_attribute_name: Attribute name of the facets normal. If the mesh doesn't have this attribute, it will call compute_facet_normal to compute it.
2001:param is_facet_selectable_attribute_name: If provided, this function will look for this attribute to determine if a facet is selectable.
2002:param output_attribute_name: Attribute name of whether a facet is selected.
2003:param search_type: Use 'BFS' for breadth-first search or 'DFS' for depth-first search.
2004:param num_smooth_iterations: Number of iterations to smooth the boundary of the selected region.
2005
2006:returns: Id of the attribute on whether a facet is selected.)",
2007 nb::sig(
2008 "def select_facets_by_normal_similarity(mesh: SurfaceMesh, "
2009 "seed_facet_id: int, "
2010 "flood_error_limit: float | None = None, "
2011 "flood_second_to_first_order_limit_ratio: float | None = None, "
2012 "facet_normal_attribute_name: str | None = None, "
2013 "is_facet_selectable_attribute_name: str | None = None, "
2014 "output_attribute_name: str | None = None, "
2015 "search_type: typing.Literal['BFS', 'DFS'] | None = None,"
2016 "num_smooth_iterations: int | None = None) -> int"));
2017
2018 m.def(
2019 "select_facets_in_frustum",
2020 [](MeshType& mesh,
2021 std::array<std::array<Scalar, 3>, 4> frustum_plane_points,
2022 std::array<std::array<Scalar, 3>, 4> frustum_plane_normals,
2023 std::optional<bool> greedy,
2024 std::optional<std::string_view> output_attribute_name) {
2025 // Set options in the C++ struct
2026 Frustum<Scalar> frustum;
2027 for (size_t i = 0; i < 4; ++i) {
2028 frustum.planes[i].point = frustum_plane_points[i];
2029 frustum.planes[i].normal = frustum_plane_normals[i];
2030 }
2031 FrustumSelectionOptions options;
2032 if (greedy.has_value()) options.greedy = greedy.value();
2033 if (output_attribute_name.has_value())
2034 options.output_attribute_name = output_attribute_name.value();
2035
2036 return select_facets_in_frustum<Scalar, Index>(mesh, frustum, options);
2037 },
2038 "mesh"_a,
2039 "frustum_plane_points"_a,
2040 "frustum_plane_normals"_a,
2041 "greedy"_a = nb::none(),
2042 "output_attribute_name"_a = nb::none(),
2043 R"(Select facets in a frustum (Pythonic API).
2044
2045:param mesh: Input mesh.
2046:param frustum_plane_points: Four points on each of the frustum planes.
2047:param frustum_plane_normals: Four normals of each of the frustum planes.
2048:param greedy: If true, the function returns as soon as the first facet is found.
2049:param output_attribute_name: Attribute name of whether a facet is selected.
2050
2051:returns: Whether any facets got selected.)");
2052
2053 m.def(
2054 "thicken_and_close_mesh",
2055 [](MeshType& mesh,
2056 std::optional<Scalar> offset_amount,
2057 std::variant<std::monostate, std::array<double, 3>, std::string_view> direction,
2058 std::optional<double> mirror_ratio,
2059 std::optional<size_t> num_segments,
2060 std::optional<std::vector<std::string>> indexed_attributes) {
2061 ThickenAndCloseOptions options;
2062
2063 if (auto array_val = std::get_if<std::array<double, 3>>(&direction)) {
2064 options.direction = *array_val;
2065 } else if (auto string_val = std::get_if<std::string_view>(&direction)) {
2066 options.direction = *string_val;
2067 }
2068 options.offset_amount = offset_amount.value_or(options.offset_amount);
2069 options.mirror_ratio = std::move(mirror_ratio);
2070 options.num_segments = num_segments.value_or(options.num_segments);
2071 options.indexed_attributes = indexed_attributes.value_or(options.indexed_attributes);
2072
2073 return thicken_and_close_mesh<Scalar, Index>(mesh, options);
2074 },
2075 "mesh"_a,
2076 "offset_amount"_a = nb::none(),
2077 "direction"_a = nb::none(),
2078 "mirror_ratio"_a = nb::none(),
2079 "num_segments"_a = nb::none(),
2080 "indexed_attributes"_a = nb::none(),
2081 R"(Thicken a mesh by offsetting it, and close the shape into a thick 3D solid.
2082
2083:param mesh: Input mesh.
2084:param direction: Direction of the offset. Can be an attribute name or a fixed 3D vector.
2085:param offset_amount: Amount of offset.
2086:param mirror_ratio: Ratio of the offset amount to mirror the mesh.
2087:param num_segments: Number of segments to use for the thickening.
2088:param indexed_attributes: List of indexed attributes to copy to the new mesh.
2089
2090:returns: The thickened and closed mesh.)");
2091
2092 m.def(
2093 "extract_boundary_loops",
2095 "mesh"_a,
2096 R"(Extract boundary loops from a mesh.
2097
2098:param mesh: Input mesh.
2099
2100:returns: A list of boundary loops, each represented as a list of vertex indices.)");
2101
2102 m.def(
2103 "extract_boundary_edges",
2104 [](MeshType& mesh) {
2105 mesh.initialize_edges();
2106 Index num_edges = mesh.get_num_edges();
2107 std::vector<Index> bd_edges;
2108 bd_edges.reserve(num_edges);
2109 for (Index ei = 0; ei < num_edges; ++ei) {
2110 if (mesh.is_boundary_edge(ei)) {
2111 bd_edges.push_back(ei);
2112 }
2113 }
2114 return bd_edges;
2115 },
2116 "mesh"_a,
2117 R"(Extract boundary edges from a mesh.
2118
2119:param mesh: Input mesh.
2120
2121:returns: A list of boundary edge indices.)");
2122
2123 m.def(
2124 "compute_uv_charts",
2125 [](MeshType& mesh,
2126 std::string_view uv_attribute_name,
2127 std::string_view output_attribute_name,
2128 std::string_view connectivity_type) {
2129 UVChartOptions options;
2130 options.uv_attribute_name = uv_attribute_name;
2131 options.output_attribute_name = output_attribute_name;
2132 if (connectivity_type == "Vertex") {
2133 options.connectivity_type = UVChartOptions::ConnectivityType::Vertex;
2134 } else if (connectivity_type == "Edge") {
2135 options.connectivity_type = UVChartOptions::ConnectivityType::Edge;
2136 } else {
2137 throw std::runtime_error(
2138 lagrange::format("Invalid connectivity type: {}", connectivity_type));
2139 }
2140 return compute_uv_charts(mesh, options);
2141 },
2142 "mesh"_a,
2143 "uv_attribute_name"_a = UVChartOptions().uv_attribute_name,
2144 "output_attribute_name"_a = UVChartOptions().output_attribute_name,
2145 "connectivity_type"_a = "Edge",
2146 R"(Compute UV charts.
2147
2148:param mesh: Input mesh.
2149:param uv_attribute_name: Name of the UV attribute.
2150:param output_attribute_name: Name of the output attribute to store the chart ids.
2151:param connectivity_type: Type of connectivity to use for chart computation. Can be "Vertex" or "Edge".
2152
2153:returns: The number of charts.)");
2154
2155 nb::class_<UVOrientationCount>(m, "UVOrientationCount", "Counts of per-facet UV orientations.")
2156 .def(nb::init<>())
2157 .def_rw(
2158 "positive",
2160 "Number of CCW (positively oriented) facets.")
2161 .def_rw(
2162 "degenerate",
2164 "Number of degenerate (zero-area) facets.")
2165 .def_rw(
2166 "negative",
2168 "Number of CW (negatively oriented / flipped) facets.");
2169
2170 m.def(
2171 "compute_uv_orientation",
2172 [](MeshType& mesh,
2173 std::string_view uv_attribute_name,
2174 std::string_view output_attribute_name) {
2175 UVOrientationOptions options;
2176 options.uv_attribute_name = uv_attribute_name;
2177 options.output_attribute_name = output_attribute_name;
2178 return compute_uv_orientation(mesh, options);
2179 },
2180 "mesh"_a,
2181 "uv_attribute_name"_a = UVOrientationOptions().uv_attribute_name,
2182 "output_attribute_name"_a = UVOrientationOptions().output_attribute_name,
2183 R"(Compute a per-facet orientation attribute using Shewchuk's exact ``orient2D`` predicate.
2184
2185Each facet is assigned an ``int8`` value: ``+1`` for CCW (positively oriented), ``0`` for
2186degenerate, ``-1`` for CW (negatively oriented / flipped).
2187
2188:param mesh: Input triangle mesh.
2189:param uv_attribute_name: Name of the UV attribute. If empty, uses the first UV attribute.
2190:param output_attribute_name: Name of the output per-facet attribute (int8).
2191
2192:returns: A :class:`UVOrientationCount` with counts of positive, degenerate, and negative facets.)");
2193
2194 m.def(
2195 "unflip_uv_charts",
2196 [](MeshType& mesh,
2197 std::string_view uv_attribute_name,
2198 std::string_view chart_id_attribute_name) {
2199 UnflipUVChartsOptions options;
2200 options.uv_attribute_name = uv_attribute_name;
2201 options.chart_id_attribute_name = chart_id_attribute_name;
2202 return unflip_uv_charts(mesh, options);
2203 },
2204 "mesh"_a,
2205 "uv_attribute_name"_a = UnflipUVChartsOptions().uv_attribute_name,
2206 "chart_id_attribute_name"_a = UnflipUVChartsOptions().chart_id_attribute_name,
2207 R"(Mirror the UV positions of every UV vertex in any chart that is "flipped" by negating
2208its U coordinate. A chart is considered flipped when either its total signed UV area is negative,
2209OR every triangle in the chart is individually flipped (per :func:`compute_uv_orientation`); the
2210latter rule catches charts whose floating-point area sum is non-negative due to nearly-degenerate
2211triangles. Assumes UV vertices are not shared across charts.
2212
2213:param mesh: Input triangle mesh. The UV attribute must be indexed.
2214:param uv_attribute_name: Name of the UV attribute. If empty, uses the first indexed UV attribute.
2215:param chart_id_attribute_name: Optional per-facet chart id attribute name. If empty, charts are
2216 computed automatically using edge connectivity on the UV mesh.
2217
2218:returns: The number of charts that were unflipped.)");
2219
2220 m.def(
2221 "disconnect_uv_charts",
2222 [](MeshType& mesh,
2223 std::string_view uv_attribute_name,
2224 std::string_view chart_id_attribute_name) {
2225 DisconnectUVChartsOptions options;
2226 options.uv_attribute_name = uv_attribute_name;
2227 options.chart_id_attribute_name = chart_id_attribute_name;
2228 return disconnect_uv_charts(mesh, options);
2229 },
2230 "mesh"_a,
2231 "uv_attribute_name"_a = DisconnectUVChartsOptions().uv_attribute_name,
2232 "chart_id_attribute_name"_a = DisconnectUVChartsOptions().chart_id_attribute_name,
2233 R"(Disconnect UV charts by duplicating UV vertices shared across different charts.
2234
2235After this operation, no two facets belonging to different UV charts will share a UV vertex
2236index. Without any input chart id attribute, this eliminates non-manifold UV vertices (pinch
2237points) where charts touch at a single vertex.
2238
2239:param mesh: Input mesh. The UV attribute must be indexed.
2240:param uv_attribute_name: Name of the UV attribute. If empty, uses the first indexed UV attribute.
2241:param chart_id_attribute_name: Optional per-facet chart id attribute name. If empty, chart ids
2242 are computed automatically using edge connectivity on the UV mesh.
2243
2244:returns: The number of UV vertices that were duplicated.)");
2245
2246 m.def(
2247 "uv_mesh_view",
2248 [](const MeshType& mesh, std::string_view uv_attribute_name) {
2249 UVMeshOptions options;
2250 options.uv_attribute_name = uv_attribute_name;
2251 return uv_mesh_view(mesh, options);
2252 },
2253 "mesh"_a,
2254 "uv_attribute_name"_a = UVMeshOptions().uv_attribute_name,
2255 R"(Extract a UV mesh view from a 3D mesh.
2256
2257:param mesh: Input mesh.
2258:param uv_attribute_name: Name of the (indexed or vertex) UV attribute.
2259
2260:return: A new mesh representing the UV mesh.)");
2261 m.def(
2262 "uv_mesh_ref",
2263 [](MeshType& mesh, std::string_view uv_attribute_name) {
2264 UVMeshOptions options;
2265 options.uv_attribute_name = uv_attribute_name;
2266 return uv_mesh_ref(mesh, options);
2267 },
2268 "mesh"_a,
2269 "uv_attribute_name"_a = UVMeshOptions().uv_attribute_name,
2270 R"(Extract a UV mesh reference from a 3D mesh.
2271
2272:param mesh: Input mesh.
2273:param uv_attribute_name: Name of the (indexed or vertex) UV attribute.
2274
2275:return: A new mesh representing the UV mesh.)");
2276
2277 m.def(
2278 "split_facets_by_material",
2280 "mesh"_a,
2281 "material_attribute_name"_a,
2282 R"(Split mesh facets based on a material attribute.
2283
2284@param mesh: Input mesh on which material segmentation will be applied in place.
2285@param material_attribute_name: Name of the material attribute to use for inserting boundaries.
2286
2287@note The material attribute should be n by k vertex attribute, where n is the number of vertices,
2288and k is the number of materials. The value at row i and column j indicates the probability of vertex
2289i belonging to material j. The function will insert boundaries between different materials based on
2290the material attribute.
2291)");
2292}
2293
2294} // namespace lagrange::python
SurfaceMesh< Scalar, Index > unify_named_index_buffer(const SurfaceMesh< Scalar, Index > &mesh, const std::vector< std::string_view > &attribute_names)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition unify_index_buffer.cpp:279
AttributeId map_attribute_in_place(SurfaceMesh< Scalar, Index > &mesh, AttributeId id, AttributeElement new_element)
Map attribute values to a different element type.
Definition map_attribute.cpp:292
AttributeId map_attribute(SurfaceMesh< Scalar, Index > &mesh, AttributeId id, std::string_view new_name, AttributeElement new_element)
Map attribute values to a new attribute with a different element type.
Definition map_attribute.cpp:265
SurfaceMesh< Scalar, Index > unify_index_buffer(const SurfaceMesh< Scalar, Index > &mesh, const std::vector< AttributeId > &attribute_ids={})
Unify index buffers of the input mesh for all attributes specified in attribute_ids.
Definition unify_index_buffer.cpp:34
uint32_t AttributeId
Identified to be used to access an attribute.
Definition AttributeFwd.h:73
AttributeElement
Type of element to which the attribute is attached.
Definition AttributeFwd.h:26
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
@ Facet
Per-facet mesh attributes.
Definition AttributeFwd.h:31
AttributeId compute_normal(SurfaceMesh< Scalar, Index > &mesh, function_ref< bool(Index)> is_edge_smooth, span< const Index > cone_vertices={}, NormalOptions options={})
Compute smooth normals based on specified sharp edges and cone vertices.
Definition compute_normal.cpp:198
SurfaceMesh< Scalar, Index > trim_by_isoline(const SurfaceMesh< Scalar, Index > &mesh, const IsolineOptions &options={})
Trim a mesh by the isoline of an implicit function defined on the mesh vertices/corners.
Definition isoline.cpp:325
AttributeId cast_attribute_in_place(SurfaceMesh< Scalar, Index > &mesh, AttributeId attribute_id)
Cast an attribute in place to a different value type.
Definition cast_attribute.cpp:68
bool is_closed(const SurfaceMesh< Scalar, Index > &mesh)
Check if a mesh is closed.
Definition topology.cpp:51
Scalar compute_uv_area(const SurfaceMesh< Scalar, Index > &mesh, MeshAreaOptions options={})
Compute UV mesh area.
Definition compute_area.cpp:429
std::array< std::array< Scalar, 3 >, 3 > compute_mesh_covariance(const SurfaceMesh< Scalar, Index > &mesh, const MeshCovarianceOptions &options={})
Compute the covariance matrix w.r.t.
Definition compute_mesh_covariance.cpp:98
size_t compute_uv_charts(SurfaceMesh< Scalar, Index > &mesh, const UVChartOptions &options={})
Compute UV charts of an input mesh.
Definition compute_uv_charts.cpp:24
int compute_euler(const SurfaceMesh< Scalar, Index > &mesh)
Compute Euler characteristic of a mesh.
Definition topology.cpp:35
bool select_facets_in_frustum(SurfaceMesh< Scalar, Index > &mesh, const Frustum< Scalar > &frustum, const FrustumSelectionOptions &options={})
Select all facets that intersect the cone/frustrum bounded by 4 planes defined by (n_i,...
Definition select_facets_in_frustum.cpp:44
AttributeId compute_greedy_coloring(SurfaceMesh< Scalar, Index > &mesh, const GreedyColoringOptions &options={})
Compute a greedy graph coloring of the mesh.
Definition compute_greedy_coloring.cpp:153
AttributeId compute_edge_is_oriented(SurfaceMesh< Scalar, Index > &mesh, const OrientationOptions &options={})
Compute a mesh attribute indicating whether an edge is oriented.
Definition orientation.cpp:82
std::string get_unique_attribute_name(const SurfaceMesh< Scalar, Index > &mesh, std::string_view name, const UniqueAttributeNameOptions &options={})
Returns a unique attribute name by appending a suffix if necessary.
Definition get_unique_attribute_name.cpp:23
SurfaceMesh< Scalar, Index > combine_meshes(std::initializer_list< const SurfaceMesh< Scalar, Index > * > meshes, bool preserve_attributes=true)
Combine multiple meshes into a single mesh.
Definition combine_meshes.cpp:330
std::vector< SurfaceMesh< Scalar, Index > > separate_by_facet_groups(const SurfaceMesh< Scalar, Index > &mesh, size_t num_groups, span< const Index > facet_group_indices, const SeparateByFacetGroupsOptions &options={})
Extract a set of submeshes based on facet groups.
Definition separate_by_facet_groups.cpp:24
ReorderingMethod
Mesh reordering method to apply before decimation.
Definition reorder_mesh.h:26
size_t disconnect_uv_charts(SurfaceMesh< Scalar, Index > &mesh, const DisconnectUVChartsOptions &options={})
Disconnect UV charts by duplicating UV vertices shared across different charts.
Definition disconnect_uv_charts.cpp:221
bool is_oriented(const SurfaceMesh< Scalar, Index > &mesh)
Check if a mesh is oriented.
Definition orientation.cpp:57
SurfaceMesh< Scalar, Index > thicken_and_close_mesh(SurfaceMesh< Scalar, Index > input_mesh, const ThickenAndCloseOptions &options={})
Thicken a mesh by offsetting it, and close the shape into a thick 3D solid.
Definition thicken_and_close_mesh.cpp:271
bool is_manifold(const SurfaceMesh< Scalar, Index > &mesh)
Check if a mesh is both vertex-manifold and edge-manifold.
Definition topology.h:98
void permute_facets(SurfaceMesh< Scalar, Index > &mesh, span< const Index > new_to_old)
Reorder facets of a mesh based on a given permutation.
Definition permute_facets.cpp:26
AttributeId compute_facet_normal(SurfaceMesh< Scalar, Index > &mesh, FacetNormalOptions options={})
Compute facet normals.
Definition compute_facet_normal.cpp:34
void orient_outward(lagrange::SurfaceMesh< Scalar, Index > &mesh, const OrientOptions &options={})
Orient the facets of a mesh so that the signed volume of each connected component is positive or nega...
Definition orient_outward.cpp:126
bool is_edge_manifold(const SurfaceMesh< Scalar, Index > &mesh)
Check if a mesh is edge-manifold.
Definition topology.cpp:125
size_t unflip_uv_charts(SurfaceMesh< Scalar, Index > &mesh, const UnflipUVChartsOptions &options={})
Mirror the UV positions of every UV vertex in any chart that is "flipped".
Definition unflip_uv_charts.cpp:159
AttributeId compute_facet_area(SurfaceMesh< Scalar, Index > &mesh, FacetAreaOptions options={})
Compute per-facet area.
Definition compute_area.cpp:307
AttributeId compute_edge_lengths(SurfaceMesh< Scalar, Index > &mesh, const EdgeLengthOptions &options={})
Computes edge lengths attribute.
Definition compute_edge_lengths.cpp:28
AttributeId cast_attribute(SurfaceMesh< Scalar, Index > &mesh, AttributeId source_id, std::string_view target_name)
Cast an attribute in place to a different value type.
Definition cast_attribute.cpp:25
std::optional< std::vector< Index > > compute_dijkstra_distance(SurfaceMesh< Scalar, Index > &mesh, const DijkstraDistanceOptions< Scalar, Index > &options={})
Computes dijkstra distance from a seed facet.
Definition compute_dijkstra_distance.cpp:24
Scalar compute_mesh_area(const SurfaceMesh< Scalar, Index > &mesh, MeshAreaOptions options={})
Compute mesh area.
Definition compute_area.cpp:407
AttributeId compute_vertex_valence(SurfaceMesh< Scalar, Index > &mesh, VertexValenceOptions options={})
Compute vertex valence.
Definition compute_vertex_valence.cpp:27
SurfaceMesh< Scalar, Index > extract_submesh(const SurfaceMesh< Scalar, Index > &mesh, span< const Index > selected_facets, const SubmeshOptions &options={})
Extract a submesh that consists of a subset of the facets of the source mesh.
Definition extract_submesh.cpp:26
void normalize_mesh(SurfaceMesh< Scalar, Index > &mesh, const TransformOptions &options={})
Normalize a mesh to fit in a unit box centered at the origin.
Definition normalize_meshes.cpp:56
AttributeId compute_facet_vector_area(SurfaceMesh< Scalar, Index > &mesh, FacetVectorAreaOptions options={})
Compute per-facet vector area.
Definition compute_area.cpp:325
void split_facets_by_material(SurfaceMesh< Scalar, Index > &mesh, std::string_view material_attribute_name)
Split mesh facets based on material labels.
Definition split_facets_by_material.cpp:57
void remap_vertices(SurfaceMesh< Scalar, Index > &mesh, span< const Index > forward_mapping, RemapVerticesOptions options={})
Remap vertices of a mesh based on provided forward mapping.
Definition remap_vertices.cpp:137
void triangulate_polygonal_facets(SurfaceMesh< Scalar, Index > &mesh, const TriangulationOptions &options={})
Triangulate polygonal facets of a mesh using a prescribed set of rules.
Definition triangulate_polygonal_facets.cpp:533
auto normalize_mesh_with_transform(SurfaceMesh< Scalar, Index > &mesh, const TransformOptions &options={}) -> Eigen::Transform< Scalar, Dimension, Eigen::Affine >
Normalize a mesh to fit in a unit box centered at the origin.
Definition normalize_meshes.cpp:29
void permute_vertices(SurfaceMesh< Scalar, Index > &mesh, span< const Index > new_to_old)
Reorder vertices of a mesh based on a given permutation.
Definition permute_vertices.cpp:26
AttributeId compute_vertex_normal(SurfaceMesh< Scalar, Index > &mesh, VertexNormalOptions options={})
Compute per-vertex normals based on specified weighting type.
Definition compute_vertex_normal.cpp:34
bool is_vertex_manifold(const SurfaceMesh< Scalar, Index > &mesh)
Check if a mesh is vertex-manifold.
Definition topology.cpp:98
AttributeId compute_facet_centroid(SurfaceMesh< Scalar, Index > &mesh, FacetCentroidOptions options={})
Compute per-facet centroid.
Definition compute_centroid.cpp:31
PointcloudPCAOutput< Scalar > compute_pointcloud_pca(span< const Scalar > points, ComputePointcloudPCAOptions options={})
Finds the principal components for a pointcloud.
Definition compute_pointcloud_pca.cpp:23
std::vector< SurfaceMesh< Scalar, Index > > separate_by_components(const SurfaceMesh< Scalar, Index > &mesh, const SeparateByComponentsOptions &options={})
Separate a mesh by connected components.
Definition separate_by_components.cpp:21
SurfaceMesh< UVScalar, Index > uv_mesh_view(const SurfaceMesh< Scalar, Index > &mesh, const UVMeshOptions &options={})
Extract a UV mesh view from an input mesh.
Definition uv_mesh.cpp:86
AttributeId compute_vertex_is_manifold(SurfaceMesh< Scalar, Index > &mesh, const VertexManifoldOptions &options={})
Compute a mesh attribute of value type uint8_t indicating vertex manifoldness.
Definition topology.cpp:142
AttributeId select_facets_by_normal_similarity(SurfaceMesh< Scalar, Index > &mesh, const Index seed_facet_id, const SelectFacetsByNormalSimilarityOptions &options={})
Given a seed facet, selects facets around it based on the change in triangle normals.
Definition select_facets_by_normal_similarity.cpp:27
std::vector< std::vector< Index > > extract_boundary_loops(const SurfaceMesh< Scalar, Index > &mesh)
Extract boundary loops from a surface mesh.
Definition extract_boundary_loops.cpp:24
AttributeId compute_dihedral_angles(SurfaceMesh< Scalar, Index > &mesh, const DihedralAngleOptions &options={})
Computes dihedral angles for each edge in the mesh.
Definition compute_dihedral_angles.cpp:33
SurfaceMesh< ToScalar, ToIndex > cast(const SurfaceMesh< FromScalar, FromIndex > &source_mesh, const AttributeFilter &convertible_attributes={}, std::vector< std::string > *converted_attributes_names=nullptr)
Cast a mesh to a mesh of different scalar and/or index type.
TangentBitangentResult compute_tangent_bitangent(SurfaceMesh< Scalar, Index > &mesh, TangentBitangentOptions options={})
Compute mesh tangent and bitangent vectors orthogonal to the input mesh normals.
Definition compute_tangent_bitangent.cpp:534
AttributeId compute_edge_is_manifold(SurfaceMesh< Scalar, Index > &mesh, const EdgeManifoldOptions &options={})
Compute a mesh attribute of value type uint8_t indicating edge manifoldness.
Definition topology.cpp:168
SurfaceMesh< Scalar, Index > transformed_mesh(SurfaceMesh< Scalar, Index > mesh, const Eigen::Transform< Scalar, Dimension, Eigen::Affine > &transform, const TransformOptions &options={})
Apply an affine transform to a mesh and return the transformed mesh.
Definition transform_mesh.cpp:146
SurfaceMesh< Scalar, Index > filter_attributes(SurfaceMesh< Scalar, Index > source_mesh, const AttributeFilter &options={})
Filters the attributes of mesh according to user specifications.
Definition filter_attributes.cpp:116
void normalize_meshes(span< SurfaceMesh< Scalar, Index > * > meshes, const TransformOptions &options={})
Normalize a list of meshes to fit in a unit box centered at the origin.
Definition normalize_meshes.cpp:106
void transform_mesh(SurfaceMesh< Scalar, Index > &mesh, const Eigen::Transform< Scalar, Dimension, Eigen::Affine > &transform, const TransformOptions &options={})
Apply an affine transform to a mesh in-place.
Definition transform_mesh.cpp:137
AttributeId compute_facet_circumcenter(SurfaceMesh< Scalar, Index > &mesh, FacetCircumcenterOptions options={})
Compute per-facet circumcenter.
Definition compute_facet_circumcenter.cpp:32
AttributeId compute_uv_distortion(SurfaceMesh< Scalar, Index > &mesh, const UVDistortionOptions &options={})
Compute uv distortion using the selected distortion measure.
Definition compute_uv_distortion.cpp:31
DistortionMetric
UV distortion metric type.
Definition DistortionMetric.h:26
void compute_mesh_centroid(const SurfaceMesh< Scalar, Index > &mesh, span< Scalar > centroid, MeshCentroidOptions options={})
Compute mesh centroid, where mesh centroid is defined as the weighted sum of facet centroids.
Definition compute_centroid.cpp:74
SurfaceMesh< UVScalar, Index > uv_mesh_ref(SurfaceMesh< Scalar, Index > &mesh, const UVMeshOptions &options={})
Extract a UV mesh reference from an input mesh.
Definition uv_mesh.cpp:40
UVOrientationCount compute_uv_orientation(SurfaceMesh< Scalar, Index > &mesh, const UVOrientationOptions &options={})
Compute a per-facet orientation attribute using Shewchuk's exact orient2D predicate.
Definition compute_uv_orientation.cpp:96
AttributeId compute_seam_edges(SurfaceMesh< Scalar, Index > &mesh, AttributeId indexed_attribute_id, const SeamEdgesOptions &options={})
Computes the seam edges for a given indexed attribute.
Definition compute_seam_edges.cpp:35
void reorder_mesh(SurfaceMesh< Scalar, Index > &mesh, ReorderingMethod method)
Mesh reordering to improve cache locality.
Definition reorder_mesh.cpp:178
size_t compute_components(SurfaceMesh< Scalar, Index > &mesh, ComponentOptions options={})
Compute connected components of an input mesh.
Definition compute_components.cpp:127
SurfaceMesh< Scalar, Index > extract_isoline(const SurfaceMesh< Scalar, Index > &mesh, const IsolineOptions &options={})
Extract the isoline of an implicit function defined on the mesh vertices/corners.
Definition isoline.cpp:333
auto normalize_meshes_with_transform(span< SurfaceMesh< Scalar, Index > * > meshes, const TransformOptions &options={}) -> Eigen::Transform< Scalar, Dimension, Eigen::Affine >
Normalize a list of meshes to fit in a unit box centered at the origin.
Definition normalize_meshes.cpp:66
@ Lexicographic
Sort vertices/facets lexicographically.
Definition reorder_mesh.h:27
@ None
Do not reorder mesh vertices/facets.
Definition reorder_mesh.h:30
@ Hilbert
Spatial sort vertices/facets using Hilbert curve.
Definition reorder_mesh.h:29
@ Morton
Spatial sort vertices/facets using Morton encoding.
Definition reorder_mesh.h:28
@ Angle
Incident face normals are averaged weighted by incident angle of vertex.
Definition NormalWeightingType.h:36
@ CornerTriangleArea
Incident face normals are averaged weighted by area of the corner triangle.
Definition NormalWeightingType.h:33
@ Uniform
Incident face normals have uniform influence on vertex normal.
Definition NormalWeightingType.h:29
@ MIPS
UV triangle area / 3D triangle area.
Definition DistortionMetric.h:31
@ InverseDirichlet
Inverse Dirichlet energy.
Definition DistortionMetric.h:28
@ SymmetricDirichlet
Symmetric Dirichlet energy.
Definition DistortionMetric.h:29
@ Dirichlet
Dirichlet energy.
Definition DistortionMetric.h:27
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:175
::nonstd::span< T, Extent > span
A bounds-safe view for sequences of objects.
Definition span.h:27
constexpr T invalid()
You can use invalid<T>() to get a value that can represent "invalid" values, such as invalid indices ...
Definition invalid.h:40
void map_attributes(const SurfaceMesh< Scalar, Index > &source_mesh, SurfaceMesh< Scalar, Index > &target_mesh, span< const Index > mapping_data, span< const Index > mapping_offsets={}, const MapAttributesOptions &options={})
Map attributes from the source mesh to the target mesh.
Definition map_attributes.cpp:27
ConnectivityType
This type defines the condition when two facets are considered as "connected".
Definition ConnectivityType.h:19
@ Edge
Two facets are considered connected if they share an edge.
Definition ConnectivityType.h:21
@ KeepFirst
Keep the value of the first elements.
Definition MappingPolicy.h:23
@ Error
Throw an error if collision is detected.
Definition MappingPolicy.h:24
@ Average
Take the average of all involved elements.
Definition MappingPolicy.h:22
std::variant< AttributeId, std::string > AttributeNameOrId
Variant identifying an attribute by its name or id.
Definition filter_attributes.h:39
ConnectivityType connectivity_type
Connectivity type used for component computation.
Definition compute_components.h:38
std::string_view output_attribute_name
Output component id attribute name.
Definition compute_components.h:35
std::string_view output_attribute_name
Output attribute name for facet area.
Definition compute_area.h:34
std::string_view output_attribute_name
Ouptut facet centroid attribute name.
Definition compute_centroid.h:33
std::string_view output_attribute_name
Output normal attribute name.
Definition compute_facet_normal.h:35
std::string_view input_attribute_name
Precomputed facet area attribute name.
Definition compute_area.h:146
bool use_signed_area
For 2D mesh only: whether the computed facet area (if any) should be signed.
Definition compute_area.h:149
std::string_view facet_centroid_attribute_name
Precomputed facet centroid attribute name.
Definition compute_centroid.h:66
@ Area
Per-facet centroid are weighted by facet area.
Definition compute_centroid.h:61
@ Uniform
Per-facet centroid are weighted uniformly.
Definition compute_centroid.h:60
std::string_view facet_area_attribute_name
Precomputed facet area attribute name.
Definition compute_centroid.h:70
bool keep_facet_normals
Whether to keep any newly added facet normal attribute.
Definition compute_normal.h:55
std::string_view facet_normal_attribute_name
Precomputed facet normal attribute name.
Definition compute_normal.h:48
bool recompute_facet_normals
Whether to recompute the facet normal attribute, or reuse existing cached values if present.
Definition compute_normal.h:51
std::string_view output_attribute_name
Output normal attribute name.
Definition compute_normal.h:41
float distance_tolerance
Tolerance for degenerate edge check. (only used to bypass degenerate edges in polygon facets)
Definition compute_normal.h:58
NormalWeightingType weight_type
Per-vertex normal averaging weighting type.
Definition compute_normal.h:44
CollisionPolicy collision_policy_integral
Collision policy for integral valued attributes.
Definition remap_vertices.h:39
CollisionPolicy collision_policy_float
Collision policy for float or double valued attributes.
Definition remap_vertices.h:36
@ BFS
Breadth-First Search.
Definition select_facets_by_normal_similarity.h:62
@ DFS
Depth-First Search.
Definition select_facets_by_normal_similarity.h:63
std::string_view bitangent_attribute_name
Output bitangent attribute name.
Definition compute_tangent_bitangent.h:41
bool keep_existing_tangent
Whether to recompute tangent if the tangent attribute (specified by tangent_attribute_name) already e...
Definition compute_tangent_bitangent.h:70
std::string_view normal_attribute_name
Normal attribute name used to compute the BTN frame.
Definition compute_tangent_bitangent.h:52
std::string_view tangent_attribute_name
Output tangent attribute name.
Definition compute_tangent_bitangent.h:38
AttributeElement output_element_type
Output element type. Can be either Corner or Indexed.
Definition compute_tangent_bitangent.h:55
bool pad_with_sign
Whether to pad the tangent/bitangent vectors with a 4th coordinate indicating the sign of the UV tria...
Definition compute_tangent_bitangent.h:59
bool orthogonalize_bitangent
Whether to compute the bitangent as sign * cross(normal, tangent) If false, the bitangent is computed...
Definition compute_tangent_bitangent.h:63
std::string_view uv_attribute_name
UV attribute name used to orient the BTN frame.
Definition compute_tangent_bitangent.h:45
AttributeId tangent_id
Tangent vector attribute id.
Definition compute_tangent_bitangent.h:77
AttributeId bitangent_id
Bitangent vector attribute id.
Definition compute_tangent_bitangent.h:80
@ Earcut
Use earcut algorithm to triangulate polygons.
Definition triangulate_polygonal_facets.h:31
@ CentroidFan
Connect facet centroid to polygon edges to form a fan of triangles.
Definition triangulate_polygonal_facets.h:32
Scheme scheme
Triangulation scheme to use.
Definition triangulate_polygonal_facets.h:35
size_t degenerate
Number of degenerate (zero-area) facets.
Definition compute_uv_orientation.h:41
size_t positive
Number of CCW (positively oriented) facets.
Definition compute_uv_orientation.h:40
size_t negative
Number of CW (negatively oriented / flipped) facets.
Definition compute_uv_orientation.h:42
bool keep_weighted_corner_normals
Whether to keep any newly added weighted corner normal attribute.
Definition compute_vertex_normal.h:56
std::string_view weighted_corner_normal_attribute_name
Precomputed weighted corner attribute name.
Definition compute_vertex_normal.h:47
std::string_view output_attribute_name
Output normal attribute name.
Definition compute_vertex_normal.h:39
float distance_tolerance
Tolerance for degenerate edge check. (only used to bypass degenerate edges in polygon facets)
Definition compute_vertex_normal.h:59
bool recompute_weighted_corner_normals
Whether to recompute the weighted corner normal attribute, or reuse existing cached values if present...
Definition compute_vertex_normal.h:51
NormalWeightingType weight_type
Per-vertex normal averaging weighting type.
Definition compute_vertex_normal.h:42
std::string_view induced_by_attribute
Optional per-edge attribute used as indicator function to restrict the graph used for vertex valence ...
Definition compute_vertex_valence.h:39
std::string_view output_attribute_name
Output vertex valence attribute name.
Definition compute_vertex_valence.h:42