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