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