Lagrange
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Modules Pages
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_normal.h>
25#include <lagrange/compute_greedy_coloring.h>
26#include <lagrange/compute_mesh_covariance.h>
27#include <lagrange/compute_normal.h>
28#include <lagrange/compute_pointcloud_pca.h>
29#include <lagrange/compute_seam_edges.h>
30#include <lagrange/compute_tangent_bitangent.h>
31#include <lagrange/compute_uv_charts.h>
32#include <lagrange/compute_uv_distortion.h>
33#include <lagrange/compute_vertex_normal.h>
34#include <lagrange/compute_vertex_valence.h>
35#include <lagrange/extract_submesh.h>
36#include <lagrange/filter_attributes.h>
37#include <lagrange/isoline.h>
38#include <lagrange/map_attribute.h>
39#include <lagrange/normalize_meshes.h>
40#include <lagrange/orient_outward.h>
41#include <lagrange/orientation.h>
42#include <lagrange/permute_facets.h>
43#include <lagrange/permute_vertices.h>
44#include <lagrange/python/tensor_utils.h>
45#include <lagrange/python/utils/StackVector.h>
46#include <lagrange/remap_vertices.h>
47#include <lagrange/reorder_mesh.h>
48#include <lagrange/select_facets_by_normal_similarity.h>
49#include <lagrange/select_facets_in_frustum.h>
50#include <lagrange/separate_by_components.h>
51#include <lagrange/separate_by_facet_groups.h>
52#include <lagrange/thicken_and_close_mesh.h>
53#include <lagrange/topology.h>
54#include <lagrange/transform_mesh.h>
55#include <lagrange/triangulate_polygonal_facets.h>
56#include <lagrange/unify_index_buffer.h>
57#include <lagrange/utils/invalid.h>
58#include <lagrange/uv_mesh.h>
59#include <lagrange/weld_indexed_attribute.h>
60
61// clang-format off
62#include <lagrange/utils/warnoff.h>
63#include <nanobind/nanobind.h>
64#include <nanobind/eigen/dense.h>
65#include <nanobind/stl/optional.h>
66#include <nanobind/stl/string_view.h>
67#include <nanobind/stl/string.h>
68#include <nanobind/stl/variant.h>
69#include <nanobind/stl/tuple.h>
70#include <nanobind/stl/vector.h>
71#include <nanobind/stl/unordered_set.h>
72#include <nanobind/stl/array.h>
73#include <lagrange/utils/warnon.h>
74// clang-format on
75
76#include <optional>
77#include <string_view>
78#include <vector>
79
80namespace lagrange::python {
81
82template <typename Scalar, typename Index>
83void bind_utilities(nanobind::module_& m)
84{
85 namespace nb = nanobind;
86 using namespace nb::literals;
88
89 nb::enum_<NormalWeightingType>(m, "NormalWeightingType", "Normal weighting type.")
90 .value("Uniform", NormalWeightingType::Uniform, "Uniform weighting")
91 .value(
92 "CornerTriangleArea",
94 "Weight by corner triangle area")
95 .value("Angle", NormalWeightingType::Angle, "Weight by corner angle");
96
97 nb::class_<VertexNormalOptions>(
98 m,
99 "VertexNormalOptions",
100 "Options for computing vertex normals")
101 .def(nb::init<>())
102 .def_rw(
103 "output_attribute_name",
105 "Output attribute name. Default is `@vertex_normal`.")
106 .def_rw(
107 "weight_type",
109 "Weighting type for normal computation. Default is Angle.")
110 .def_rw(
111 "weighted_corner_normal_attribute_name",
113 "Precomputed weighted corner normals attribute name."
114 "Default is `@weighted_corner_normal`. "
115 "If attribute exists, the weighted corner normals will be "
116 "used instead of recomputing them.")
117 .def_rw(
118 "recompute_weighted_corner_normals",
120 "Whether to recompute weighted corner normals. Default is false")
121 .def_rw(
122 "keep_weighted_corner_normals",
124 "Whether to keep the weighted corner normal attribute. Default is false.");
125
126 m.def(
127 "compute_vertex_normal",
128 &compute_vertex_normal<Scalar, Index>,
129 "mesh"_a,
130 "options"_a = VertexNormalOptions(),
131 R"(Computer vertex normal.
132
133:param mesh: Input mesh.
134:param options: Options for computing vertex normals.
135
136:returns: Vertex normal attribute id.)");
137
138 m.def(
139 "compute_vertex_normal",
140 [](MeshType& mesh,
141 std::optional<std::string_view> output_attribute_name,
142 std::optional<NormalWeightingType> weight_type,
143 std::optional<std::string_view> weighted_corner_normal_attribute_name,
144 std::optional<bool> recompute_weighted_corner_normals,
145 std::optional<bool> keep_weighted_corner_normals) {
146 VertexNormalOptions options;
147 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
148 if (weight_type) options.weight_type = *weight_type;
149 if (weighted_corner_normal_attribute_name)
150 options.weighted_corner_normal_attribute_name =
151 *weighted_corner_normal_attribute_name;
152 if (recompute_weighted_corner_normals)
153 options.recompute_weighted_corner_normals = *recompute_weighted_corner_normals;
154 if (keep_weighted_corner_normals)
155 options.keep_weighted_corner_normals = *keep_weighted_corner_normals;
156
157 return compute_vertex_normal<Scalar, Index>(mesh, options);
158 },
159 "mesh"_a,
160 "output_attribute_name"_a = nb::none(),
161 "weight_type"_a = nb::none(),
162 "weighted_corner_normal_attribute_name"_a = nb::none(),
163 "recompute_weighted_corner_normals"_a = nb::none(),
164 "keep_weighted_corner_normals"_a = nb::none(),
165 R"(Computer vertex normal (Pythonic API).
166
167:param mesh: Input mesh.
168:param output_attribute_name: Output attribute name.
169:param weight_type: Weighting type for normal computation.
170:param weighted_corner_normal_attribute_name: Precomputed weighted corner normals attribute name.
171:param recompute_weighted_corner_normals: Whether to recompute weighted corner normals.
172:param keep_weighted_corner_normals: Whether to keep the weighted corner normal attribute.
173
174:returns: Vertex normal attribute id.)");
175
176 nb::class_<FacetNormalOptions>(m, "FacetNormalOptions", "Facet normal computation options.")
177 .def(nb::init<>())
178 .def_rw(
179 "output_attribute_name",
181 "Output attribute name. Default: `@facet_normal`");
182
183 m.def(
184 "compute_facet_normal",
185 &compute_facet_normal<Scalar, Index>,
186 "mesh"_a,
187 "options"_a = FacetNormalOptions(),
188 R"(Compute facet normal.
189
190:param mesh: Input mesh.
191:param options: Options for computing facet normals.
192
193:returns: Facet normal attribute id.)");
194
195 m.def(
196 "compute_facet_normal",
197 [](MeshType& mesh, std::optional<std::string_view> output_attribute_name) {
198 FacetNormalOptions options;
199 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
200 return compute_facet_normal<Scalar, Index>(mesh, options);
201 },
202 "mesh"_a,
203 "output_attribute_name"_a = nb::none(),
204 R"(Compute facet normal (Pythonic API).
205
206:param mesh: Input mesh.
207:param output_attribute_name: Output attribute name.
208
209:returns: Facet normal attribute id.)");
210
211 nb::class_<NormalOptions>(m, "NormalOptions", "Normal computation options.")
212 .def(nb::init<>())
213 .def_rw(
214 "output_attribute_name",
216 "Output attribute name. Default: `@normal`")
217 .def_rw(
218 "weight_type",
220 "Weighting type for normal computation. Default is Angle.")
221 .def_rw(
222 "facet_normal_attribute_name",
224 "Facet normal attribute name to use. Default is `@facet_normal`.")
225 .def_rw(
226 "recompute_facet_normals",
228 "Whether to recompute facet normals. Default is false.")
229 .def_rw(
230 "keep_facet_normals",
232 "Whether to keep the computed facet normal attribute. Default is false.");
233
234 m.def(
235 "compute_normal",
236 [](MeshType& mesh,
237 Scalar feature_angle_threshold,
238 nb::object cone_vertices,
239 std::optional<NormalOptions> normal_options) {
240 NormalOptions options;
241 if (normal_options.has_value()) {
242 options = std::move(normal_options.value());
243 }
244
245 if (cone_vertices.is_none()) {
246 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, {}, options);
247 } else if (nb::isinstance<nb::list>(cone_vertices)) {
248 auto cone_vertices_list = nb::cast<std::vector<Index>>(cone_vertices);
249 span<const Index> data{cone_vertices_list.data(), cone_vertices_list.size()};
250 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
251 } else if (nb::isinstance<Tensor<Index>>(cone_vertices)) {
252 auto cone_vertices_tensor = nb::cast<Tensor<Index>>(cone_vertices);
253 auto [data, shape, stride] = tensor_to_span(cone_vertices_tensor);
254 la_runtime_assert(is_dense(shape, stride));
255 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
256 } else {
257 throw std::runtime_error("Invalid cone_vertices type");
258 }
259 },
260 "mesh"_a,
261 "feature_angle_threshold"_a = M_PI / 4,
262 "cone_vertices"_a = nb::none(),
263 "options"_a = nb::none(),
264 R"(Compute indexed normal attribute.
265
266Edge with dihedral angles larger than `feature_angle_threshold` are considered as sharp edges.
267Vertices listed in `cone_vertices` are considered as cone vertices, which is always sharp.
268
269:param mesh: input mesh
270:param feature_angle_threshold: feature angle threshold
271:param cone_vertices: cone vertices
272:param options: normal options
273
274:returns: the id of the indexed normal attribute.
275)");
276
277 m.def(
278 "compute_normal",
279 [](MeshType& mesh,
280 Scalar feature_angle_threshold,
281 nb::object cone_vertices,
282 std::optional<std::string_view> output_attribute_name,
283 std::optional<NormalWeightingType> weight_type,
284 std::optional<std::string_view> facet_normal_attribute_name,
285 std::optional<bool> recompute_facet_normals,
286 std::optional<bool> keep_facet_normals) {
287 NormalOptions options;
288 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
289 if (weight_type) options.weight_type = *weight_type;
290 if (facet_normal_attribute_name)
291 options.facet_normal_attribute_name = *facet_normal_attribute_name;
292 if (recompute_facet_normals) options.recompute_facet_normals = *recompute_facet_normals;
293 if (keep_facet_normals) options.keep_facet_normals = *keep_facet_normals;
294
295 if (cone_vertices.is_none()) {
296 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, {}, options);
297 } else if (nb::isinstance<nb::list>(cone_vertices)) {
298 auto cone_vertices_list = nb::cast<std::vector<Index>>(cone_vertices);
299 span<const Index> data{cone_vertices_list.data(), cone_vertices_list.size()};
300 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
301 } else if (nb::isinstance<Tensor<Index>>(cone_vertices)) {
302 auto cone_vertices_tensor = nb::cast<Tensor<Index>>(cone_vertices);
303 auto [data, shape, stride] = tensor_to_span(cone_vertices_tensor);
304 la_runtime_assert(is_dense(shape, stride));
305 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
306 } else {
307 throw std::runtime_error("Invalid cone_vertices type");
308 }
309 },
310 "mesh"_a,
311 "feature_angle_threshold"_a = M_PI / 4,
312 "cone_vertices"_a = nb::none(),
313 "output_attribute_name"_a = nb::none(),
314 "weight_type"_a = nb::none(),
315 "facet_normal_attribute_name"_a = nb::none(),
316 "recompute_facet_normals"_a = nb::none(),
317 "keep_facet_normals"_a = nb::none(),
318 R"(Compute indexed normal attribute (Pythonic API).
319
320:param mesh: input mesh
321:param feature_angle_threshold: feature angle threshold
322:param cone_vertices: cone vertices
323:param output_attribute_name: output normal attribute name
324:param weight_type: normal weighting type
325:param facet_normal_attribute_name: facet normal attribute name
326:param recompute_facet_normals: whether to recompute facet normals
327:param keep_facet_normals: whether to keep the computed facet normal attribute
328
329:returns: the id of the indexed normal attribute.)");
330
331 using ConstArray3d = nb::ndarray<const double, nb::shape<-1, 3>, nb::c_contig, nb::device::cpu>;
332 m.def(
333 "compute_pointcloud_pca",
334 [](ConstArray3d points, bool shift_centroid, bool normalize) {
335 ComputePointcloudPCAOptions options;
336 options.shift_centroid = shift_centroid;
337 options.normalize = normalize;
338 PointcloudPCAOutput<Scalar> output =
339 compute_pointcloud_pca<Scalar>({points.data(), points.size()}, options);
340 return std::make_tuple(output.center, output.eigenvectors, output.eigenvalues);
341 },
342 "points"_a,
343 "shift_centroid"_a = ComputePointcloudPCAOptions().shift_centroid,
344 "normalize"_a = ComputePointcloudPCAOptions().normalize,
345 R"(Compute principal components of a point cloud.
346
347:param points: Input points.
348:param shift_centroid: When true: covariance = (P-centroid)^T (P-centroid), when false: covariance = (P)^T (P).
349:param normalize: Should we divide the result by number of points?
350
351:returns: tuple of (center, eigenvectors, eigenvalues).)");
352
353 m.def(
354 "compute_greedy_coloring",
355 [](MeshType& mesh,
356 AttributeElement element_type,
357 size_t num_color_used,
358 std::optional<std::string_view> output_attribute_name) {
359 GreedyColoringOptions options;
360 options.element_type = element_type;
361 options.num_color_used = num_color_used;
362 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
363 return compute_greedy_coloring<Scalar, Index>(mesh, options);
364 },
365 "mesh"_a,
366 "element_type"_a = AttributeElement::Facet,
367 "num_color_used"_a = 8,
368 "output_attribute_name"_a = nb::none(),
369 R"(Compute greedy coloring of mesh elements.
370
371:param mesh: Input mesh.
372:param element_type: Element type to be colored. Can be either Vertex or Facet.
373:param num_color_used: Minimum number of colors to use. The algorithm will cycle through them but may use more.
374:param output_attribute_name: Output attribute name.
375
376:returns: Color attribute id.)");
377
378 m.def(
379 "normalize_mesh",
380 &normalize_mesh<Scalar, Index>,
381 "mesh"_a,
382 R"(Normalize a mesh to fit into a unit box centered at the origin.
383
384:param mesh: input mesh)");
385
386 m.def(
387 "normalize_meshes",
388 [](std::vector<SurfaceMesh<Scalar, Index>*> meshes) {
389 span<SurfaceMesh<Scalar, Index>*> meshes_span(meshes.data(), meshes.size());
390 normalize_meshes(meshes_span);
391 },
392 "meshes"_a,
393 R"(Normalize a list of meshes to fit into a unit box centered at the origin.
394
395:param meshes: input meshes)");
396
397 m.def(
398 "combine_meshes",
399 [](std::vector<SurfaceMesh<Scalar, Index>*> meshes, bool preserve_vertices) {
400 return combine_meshes<Scalar, Index>(
401 meshes.size(),
402 [&](size_t i) -> const SurfaceMesh<Scalar, Index>& { return *meshes[i]; },
403 preserve_vertices);
404 },
405 "meshes"_a,
406 "preserve_attributes"_a = true,
407 R"(Combine a list of meshes into a single mesh.
408
409:param meshes: input meshes
410:param preserve_attributes: whether to preserve attributes
411
412:returns: the combined mesh)");
413
414 m.def(
415 "compute_seam_edges",
416 [](MeshType& mesh,
417 AttributeId indexed_attribute_id,
418 std::optional<std::string_view> output_attribute_name) {
419 SeamEdgesOptions options;
420 if (output_attribute_name) options.output_attribute_name = *output_attribute_name;
421 return compute_seam_edges<Scalar, Index>(mesh, indexed_attribute_id, options);
422 },
423 "mesh"_a,
424 "indexed_attribute_id"_a,
425 "output_attribute_name"_a = nb::none(),
426 R"(Computer seam edges for a given indexed attribute.
427
428:param mesh: Input mesh.
429:param indexed_attribute_id: Input indexed attribute id.
430:param output_attribute_name: Output attribute name.
431
432:returns: Attribute id for the output per-edge seam attribute (1 is a seam, 0 is not).)");
433
434 m.def(
435 "orient_outward",
436 [](MeshType& mesh, bool positive) {
437 OrientOptions options;
438 options.positive = positive;
439 orient_outward<Scalar, Index>(mesh, options);
440 },
441 "mesh"_a,
442 "positive"_a = OrientOptions().positive,
443 R"(Orient the facets of a mesh so that the signed volume of each connected component is positive or negative.
444
445:param mesh: Input mesh.
446:param positive: Whether to orient each volume positively or negatively.)");
447
448 m.def(
449 "unify_index_buffer",
450 [](MeshType& mesh) { return unify_index_buffer(mesh); },
451 "mesh"_a,
452 R"(Unify the index buffer of the mesh. All indexed attributes will be unified.
453
454:param mesh: The mesh to unify.
455
456:returns: The unified mesh.)");
457
458 m.def(
459 "unify_index_buffer",
460 &lagrange::unify_index_buffer<Scalar, Index>,
461 "mesh"_a,
462 "attribute_ids"_a,
463 R"(Unify the index buffer of the mesh for selected attributes.
464
465:param mesh: The mesh to unify.
466:param attribute_ids: The selected attribute ids to unify.
467
468:returns: The unified mesh.)");
469
470 m.def(
471 "unify_index_buffer",
472 &lagrange::unify_named_index_buffer<Scalar, Index>,
473 "mesh"_a,
474 "attribute_names"_a,
475 R"(Unify the index buffer of the mesh for selected attributes.
476
477:param mesh: The mesh to unify.
478:param attribute_names: The selected attribute names to unify.
479
480:returns: The unified mesh.)");
481
482 m.def(
483 "triangulate_polygonal_facets",
484 &lagrange::triangulate_polygonal_facets<Scalar, Index>,
485 "mesh"_a,
486 R"(Triangulate polygonal facets of the mesh.
487
488:param mesh: The input mesh.)");
489
490 nb::enum_<ComponentOptions::ConnectivityType>(m, "ConnectivityType", "Mesh connectivity type")
491 .value(
492 "Vertex",
494 "Two facets are connected if they share a vertex")
495 .value(
496 "Edge",
498 "Two facets are connected if they share an edge");
499
500 m.def(
501 "compute_components",
502 [](MeshType& mesh,
503 std::optional<std::string_view> output_attribute_name,
504 std::optional<lagrange::ConnectivityType> connectivity_type,
505 std::optional<nb::list>& blocker_elements) {
507 if (output_attribute_name.has_value()) {
508 opt.output_attribute_name = output_attribute_name.value();
509 }
510 if (connectivity_type.has_value()) {
511 opt.connectivity_type = connectivity_type.value();
512 }
513 std::vector<Index> blocker_elements_vec;
514 if (blocker_elements.has_value()) {
515 for (auto val : blocker_elements.value()) {
516 blocker_elements_vec.push_back(nb::cast<Index>(val));
517 }
518 }
519 return lagrange::compute_components<Scalar, Index>(mesh, blocker_elements_vec, opt);
520 },
521 "mesh"_a,
522 "output_attribute_name"_a = nb::none(),
523 "connectivity_type"_a = nb::none(),
524 "blocker_elements"_a = nb::none(),
525 R"(Compute connected components.
526
527This method will create a per-facet component id attribute named by the `output_attribute_name`
528argument. Each component id is in [0, num_components-1] range.
529
530:param mesh: The input mesh.
531:param output_attribute_name: The name of the output attribute.
532:param connectivity_type: The connectivity type. Either "Vertex" or "Edge".
533: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.
534
535:returns: The total number of components.)");
536
537 nb::class_<VertexValenceOptions>(m, "VertexValenceOptions", "Vertex valence options")
538 .def(nb::init<>())
539 .def_rw(
540 "output_attribute_name",
541 &VertexValenceOptions::output_attribute_name,
542 "The name of the output attribute")
543 .def_rw(
544 "induced_by_attribute",
545 &VertexValenceOptions::induced_by_attribute,
546 "Optional per-edge attribute used as indicator function to restrict the graph used for "
547 "vertex valence computation");
548
549 m.def(
550 "compute_vertex_valence",
551 &lagrange::compute_vertex_valence<Scalar, Index>,
552 "mesh"_a,
553 "options"_a = VertexValenceOptions(),
554 R"(Compute vertex valence
555
556:param mesh: The input mesh.
557:param options: The vertex valence options.
558
559:returns: The vertex valence attribute id.)");
560
561 m.def(
562 "compute_vertex_valence",
563 [](MeshType& mesh,
564 std::optional<std::string_view> output_attribute_name,
565 std::optional<std::string_view> induced_by_attribute) {
566 VertexValenceOptions opt;
567 if (output_attribute_name.has_value()) {
568 opt.output_attribute_name = output_attribute_name.value();
569 }
570 if (induced_by_attribute.has_value()) {
571 opt.induced_by_attribute = induced_by_attribute.value();
572 }
573 return lagrange::compute_vertex_valence<Scalar, Index>(mesh, opt);
574 },
575 "mesh"_a,
576 "output_attribute_name"_a = nb::none(),
577 "induced_by_attribute"_a = nb::none(),
578 R"(Compute vertex valence);
579
580:param mesh: The input mesh.
581:param output_attribute_name: The name of the output attribute.
582:param induced_by_attribute: Optional per-edge attribute used as indicator function to restrict the graph used for vertex valence computation.
583
584:returns: The vertex valence attribute id)");
585
586 nb::class_<TangentBitangentOptions>(m, "TangentBitangentOptions", "Tangent bitangent options")
587 .def(nb::init<>())
588 .def_rw(
589 "tangent_attribute_name",
590 &TangentBitangentOptions::tangent_attribute_name,
591 "The name of the output tangent attribute, default is `@tangent`")
592 .def_rw(
593 "bitangent_attribute_name",
594 &TangentBitangentOptions::bitangent_attribute_name,
595 "The name of the output bitangent attribute, default is `@bitangent`")
596 .def_rw(
597 "uv_attribute_name",
598 &TangentBitangentOptions::uv_attribute_name,
599 "The name of the uv attribute")
600 .def_rw(
601 "normal_attribute_name",
602 &TangentBitangentOptions::normal_attribute_name,
603 "The name of the normal attribute")
604 .def_rw(
605 "output_element_type",
606 &TangentBitangentOptions::output_element_type,
607 "The output element type")
608 .def_rw(
609 "pad_with_sign",
610 &TangentBitangentOptions::pad_with_sign,
611 "Whether to pad the output tangent/bitangent with sign");
612
613 nb::class_<TangentBitangentResult>(m, "TangentBitangentResult", "Tangent bitangent result")
614 .def(nb::init<>())
615 .def_rw(
616 "tangent_id",
617 &TangentBitangentResult::tangent_id,
618 "The output tangent attribute id")
619 .def_rw(
620 "bitangent_id",
621 &TangentBitangentResult::bitangent_id,
622 "The output bitangent attribute id");
623
624 m.def(
625 "compute_tangent_bitangent",
626 &lagrange::compute_tangent_bitangent<Scalar, Index>,
627 "mesh"_a,
628 "options"_a = TangentBitangentOptions(),
629 R"(Compute tangent and bitangent vector attributes.
630
631:param mesh: The input mesh.
632:param options: The tangent bitangent options.
633
634:returns: The tangent and bitangent attribute ids)");
635
636 m.def(
637 "compute_tangent_bitangent",
638 [](MeshType& mesh,
639 std::optional<std::string_view>(tangent_attribute_name),
640 std::optional<std::string_view>(bitangent_attribute_name),
641 std::optional<std::string_view>(uv_attribute_name),
642 std::optional<std::string_view>(normal_attribute_name),
643 std::optional<AttributeElement>(output_attribute_type),
644 std::optional<bool>(pad_with_sign)) {
645 TangentBitangentOptions opt;
646 if (tangent_attribute_name.has_value()) {
647 opt.tangent_attribute_name = tangent_attribute_name.value();
648 }
649 if (bitangent_attribute_name.has_value()) {
650 opt.bitangent_attribute_name = bitangent_attribute_name.value();
651 }
652 if (uv_attribute_name.has_value()) {
653 opt.uv_attribute_name = uv_attribute_name.value();
654 }
655 if (normal_attribute_name.has_value()) {
656 opt.normal_attribute_name = normal_attribute_name.value();
657 }
658 if (output_attribute_type.has_value()) {
659 opt.output_element_type = output_attribute_type.value();
660 }
661 if (pad_with_sign.has_value()) {
662 opt.pad_with_sign = pad_with_sign.value();
663 }
664
665 auto r = compute_tangent_bitangent<Scalar, Index>(mesh, opt);
666 return std::make_tuple(r.tangent_id, r.bitangent_id);
667 },
668 "mesh"_a,
669 "tangent_attribute_name"_a = nb::none(),
670 "bitangent_attribute_name"_a = nb::none(),
671 "uv_attribute_name"_a = nb::none(),
672 "normal_attribute_name"_a = nb::none(),
673 "output_attribute_type"_a = nb::none(),
674 "pad_with_sign"_a = nb::none(),
675 R"(Compute tangent and bitangent vector attributes (Pythonic API).
676
677:param mesh: The input mesh.
678:param tangent_attribute_name: The name of the output tangent attribute.
679:param bitangent_attribute_name: The name of the output bitangent attribute.
680:param uv_attribute_name: The name of the uv attribute.
681:param normal_attribute_name: The name of the normal attribute.
682:param output_attribute_type: The output element type.
683:param pad_with_sign: Whether to pad the output tangent/bitangent with sign.
684
685:returns: The tangent and bitangent attribute ids)");
686
687 m.def(
688 "map_attribute",
689 static_cast<AttributeId (*)(
692 std::string_view,
693 AttributeElement)>(&lagrange::map_attribute<Scalar, Index>),
694 "mesh"_a,
695 "old_attribute_id"_a,
696 "new_attribute_name"_a,
697 "new_element"_a,
698 R"(Map an attribute to a new element type.
699
700:param mesh: The input mesh.
701:param old_attribute_id: The id of the input attribute.
702:param new_attribute_name: The name of the new attribute.
703:param new_element: The new element type.
704
705:returns: The id of the new attribute.)");
706
707 m.def(
708 "map_attribute",
709 static_cast<AttributeId (*)(
711 std::string_view,
712 std::string_view,
713 AttributeElement)>(&lagrange::map_attribute<Scalar, Index>),
714 "mesh"_a,
715 "old_attribute_name"_a,
716 "new_attribute_name"_a,
717 "new_element"_a,
718 R"(Map an attribute to a new element type.
719
720:param mesh: The input mesh.
721:param old_attribute_name: The name of the input attribute.
722:param new_attribute_name: The name of the new attribute.
723:param new_element: The new element type.
724
725:returns: The id of the new attribute.)");
726
727 m.def(
728 "map_attribute_in_place",
730 &map_attribute_in_place<Scalar, Index>),
731 "mesh"_a,
732 "id"_a,
733 "new_element"_a,
734 R"(Map an attribute to a new element type in place.
735
736:param mesh: The input mesh.
737:param id: The id of the input attribute.
738:param new_element: The new element type.
739
740:returns: The id of the new attribute.)");
741
742 m.def(
743 "map_attribute_in_place",
744 static_cast<
746 &map_attribute_in_place<Scalar, Index>),
747 "mesh"_a,
748 "name"_a,
749 "new_element"_a,
750 R"(Map an attribute to a new element type in place.
751
752:param mesh: The input mesh.
753:param name: The name of the input attribute.
754:param new_element: The new element type.
755
756:returns: The id of the new attribute.)");
757
758 nb::class_<FacetAreaOptions>(m, "FacetAreaOptions", "Options for computing facet area.")
759 .def(nb::init<>())
760 .def_rw(
761 "output_attribute_name",
762 &FacetAreaOptions::output_attribute_name,
763 "The name of the output attribute.");
764
765 m.def(
766 "compute_facet_area",
767 &lagrange::compute_facet_area<Scalar, Index>,
768 "mesh"_a,
769 "options"_a = FacetAreaOptions(),
770 R"(Compute facet area.
771
772:param mesh: The input mesh.
773:param options: The options for computing facet area.
774
775:returns: The id of the new attribute.)");
776
777 m.def(
778 "compute_facet_area",
779 [](MeshType& mesh, std::optional<std::string_view> name) {
780 FacetAreaOptions opt;
781 if (name.has_value()) {
782 opt.output_attribute_name = name.value();
783 }
784 return lagrange::compute_facet_area<Scalar, Index>(mesh, opt);
785 },
786 "mesh"_a,
787 "output_attribute_name"_a = nb::none(),
788 R"(Compute facet area (Pythonic API).
789
790:param mesh: The input mesh.
791:param output_attribute_name: The name of the output attribute.
792
793:returns: The id of the new attribute.)");
794
795 nb::class_<MeshAreaOptions>(m, "MeshAreaOptions", "Options for computing mesh area.")
796 .def(nb::init<>())
797 .def_rw(
798 "input_attribute_name",
799 &MeshAreaOptions::input_attribute_name,
800 "The name of the pre-computed facet area attribute, default is `@facet_area`.")
801 .def_rw(
802 "use_signed_area",
803 &MeshAreaOptions::use_signed_area,
804 "Whether to use signed area.");
805
806 m.def(
807 "compute_mesh_area",
808 &lagrange::compute_mesh_area<Scalar, Index>,
809 "mesh"_a,
810 "options"_a = MeshAreaOptions(),
811 R"(Compute mesh area.
812
813:param mesh: The input mesh.
814:param options: The options for computing mesh area.
815
816:returns: The mesh area.)");
817
818 m.def(
819 "compute_mesh_area",
820 [](MeshType& mesh,
821 std::optional<std::string_view> input_attribute_name,
822 std::optional<bool> use_signed_area) {
823 MeshAreaOptions opt;
824 if (input_attribute_name.has_value()) {
825 opt.input_attribute_name = input_attribute_name.value();
826 }
827 if (use_signed_area.has_value()) {
828 opt.use_signed_area = use_signed_area.value();
829 }
830 return compute_mesh_area(mesh, opt);
831 },
832 "mesh"_a,
833 "input_attribute_name"_a = nb::none(),
834 "use_signed_area"_a = nb::none(),
835 R"(Compute mesh area (Pythonic API).
836
837:param mesh: The input mesh.
838:param input_attribute_name: The name of the pre-computed facet area attribute.
839:param use_signed_area: Whether to use signed area.
840
841:returns: The mesh area.)");
842
843 nb::class_<FacetCentroidOptions>(m, "FacetCentroidOptions", "Facet centroid options.")
844 .def(nb::init<>())
845 .def_rw(
846 "output_attribute_name",
847 &FacetCentroidOptions::output_attribute_name,
848 "The name of the output attribute.");
849 m.def(
850 "compute_facet_centroid",
851 &lagrange::compute_facet_centroid<Scalar, Index>,
852 "mesh"_a,
853 "options"_a = FacetCentroidOptions(),
854 R"(Compute facet centroid.
855
856:param mesh: The input mesh.
857:param options: The options for computing facet centroid.
858
859:returns: The id of the new attribute.)");
860
861 m.def(
862 "compute_facet_centroid",
863 [](MeshType& mesh, std::optional<std::string_view> output_attribute_name) {
864 FacetCentroidOptions opt;
865 if (output_attribute_name.has_value()) {
866 opt.output_attribute_name = output_attribute_name.value();
867 }
868 return lagrange::compute_facet_centroid<Scalar, Index>(mesh, opt);
869 },
870 "mesh"_a,
871 "output_attribute_name"_a = nb::none(),
872 R"(Compute facet centroid (Pythonic API).
873
874:param mesh: The input mesh.
875:param output_attribute_name: The name of the output attribute.
876
877:returns: The id of the new attribute.)");
878
879 nb::enum_<MeshCentroidOptions::WeightingType>(
880 m,
881 "CentroidWeightingType",
882 "Centroid weighting type.")
883 .value("Uniform", MeshCentroidOptions::Uniform, "Uniform weighting.")
884 .value("Area", MeshCentroidOptions::Area, "Area weighting.");
885
886 nb::class_<MeshCentroidOptions>(m, "MeshCentroidOptions", "Mesh centroid options.")
887 .def(nb::init<>())
888 .def_rw("weighting_type", &MeshCentroidOptions::weighting_type, "The weighting type.")
889 .def_rw(
890 "facet_centroid_attribute_name",
891 &MeshCentroidOptions::facet_centroid_attribute_name,
892 "The name of the pre-computed facet centroid attribute if available.")
893 .def_rw(
894 "facet_area_attribute_name",
895 &MeshCentroidOptions::facet_area_attribute_name,
896 "The name of the pre-computed facet area attribute if available.");
897
898 m.def(
899 "compute_mesh_centroid",
900 [](const SurfaceMesh<Scalar, Index>& mesh, MeshCentroidOptions opt) {
901 const Index dim = mesh.get_dimension();
902 std::vector<Scalar> centroid(dim, invalid<Scalar>());
903 compute_mesh_centroid<Scalar, Index>(mesh, centroid, opt);
904 return centroid;
905 },
906 "mesh"_a,
907 "options"_a = MeshCentroidOptions(),
908 R"(Compute mesh centroid.
909
910:param mesh: The input mesh.
911:param options: The options for computing mesh centroid.
912
913:returns: The mesh centroid.)");
914
915 m.def(
916 "compute_mesh_centroid",
917 [](MeshType& mesh,
918 std::optional<MeshCentroidOptions::WeightingType> weighting_type,
919 std::optional<std::string_view> facet_centroid_attribute_name,
920 std::optional<std::string_view> facet_area_attribute_name) {
921 MeshCentroidOptions opt;
922 if (weighting_type.has_value()) {
923 opt.weighting_type = weighting_type.value();
924 }
925 if (facet_centroid_attribute_name.has_value()) {
926 opt.facet_centroid_attribute_name = facet_centroid_attribute_name.value();
927 }
928 if (facet_area_attribute_name.has_value()) {
929 opt.facet_area_attribute_name = facet_area_attribute_name.value();
930 }
931 const Index dim = mesh.get_dimension();
932 std::vector<Scalar> centroid(dim, invalid<Scalar>());
933 compute_mesh_centroid<Scalar, Index>(mesh, centroid, opt);
934 return centroid;
935 },
936 "mesh"_a,
937 "weighting_type"_a = nb::none(),
938 "facet_centroid_attribute_name"_a = nb::none(),
939 "facet_area_attribute_name"_a = nb::none(),
940 R"(Compute mesh centroid (Pythonic API).
941
942:param mesh: The input mesh.
943:param weighting_type: The weighting type. Default is `Area`.
944:param facet_centroid_attribute_name: The name of the pre-computed facet centroid attribute if available. Default is `@facet_centroid`.
945:param facet_area_attribute_name: The name of the pre-computed facet area attribute if available. Default is `@facet_area`.
946
947:returns: The mesh centroid.)");
948
949 m.def(
950 "permute_vertices",
951 [](MeshType& mesh, Tensor<Index> new_to_old) {
952 auto [data, shape, stride] = tensor_to_span(new_to_old);
953 la_runtime_assert(is_dense(shape, stride));
954 permute_vertices<Scalar, Index>(mesh, data);
955 },
956 "mesh"_a,
957 "new_to_old"_a,
958 R"(Reorder vertices of a mesh in place based on a permutation.
959
960:param mesh: input mesh
961:param new_to_old: permutation vector for vertices)");
962
963 m.def(
964 "permute_facets",
965 [](MeshType& mesh, Tensor<Index> new_to_old) {
966 auto [data, shape, stride] = tensor_to_span(new_to_old);
967 la_runtime_assert(is_dense(shape, stride));
968 permute_facets<Scalar, Index>(mesh, data);
969 },
970 "mesh"_a,
971 "new_to_old"_a,
972 R"(Reorder facets of a mesh in place based on a permutation.
973
974:param mesh: input mesh
975:param new_to_old: permutation vector for facets)");
976
977 nb::enum_<MappingPolicy>(m, "MappingPolicy", "Mapping policy for handling collisions.")
978 .value("Average", MappingPolicy::Average, "Compute the average of the collided values.")
979 .value("KeepFirst", MappingPolicy::KeepFirst, "Keep the first collided value.")
980 .value("Error", MappingPolicy::Error, "Throw an error when collision happens.");
981
982 nb::class_<RemapVerticesOptions>(m, "RemapVerticesOptions", "Options for remapping vertices.")
983 .def(nb::init<>())
984 .def_rw(
985 "collision_policy_float",
986 &RemapVerticesOptions::collision_policy_float,
987 "The collision policy for float attributes.")
988 .def_rw(
989 "collision_policy_integral",
990 &RemapVerticesOptions::collision_policy_integral,
991 "The collision policy for integral attributes.");
992
993 m.def(
994 "remap_vertices",
995 [](MeshType& mesh, Tensor<Index> old_to_new, RemapVerticesOptions opt) {
996 auto [data, shape, stride] = tensor_to_span(old_to_new);
997 la_runtime_assert(is_dense(shape, stride));
998 remap_vertices<Scalar, Index>(mesh, data, opt);
999 },
1000 "mesh"_a,
1001 "old_to_new"_a,
1002 "options"_a = RemapVerticesOptions(),
1003 R"(Remap vertices of a mesh in place based on a permutation.
1004
1005:param mesh: input mesh
1006:param old_to_new: permutation vector for vertices
1007:param options: options for remapping vertices)");
1008
1009 m.def(
1010 "remap_vertices",
1011 [](MeshType& mesh,
1012 Tensor<Index> old_to_new,
1013 std::optional<MappingPolicy> collision_policy_float,
1014 std::optional<MappingPolicy> collision_policy_integral) {
1015 RemapVerticesOptions opt;
1016 if (collision_policy_float.has_value()) {
1017 opt.collision_policy_float = collision_policy_float.value();
1018 }
1019 if (collision_policy_integral.has_value()) {
1020 opt.collision_policy_integral = collision_policy_integral.value();
1021 }
1022 auto [data, shape, stride] = tensor_to_span(old_to_new);
1023 la_runtime_assert(is_dense(shape, stride));
1024 remap_vertices<Scalar, Index>(mesh, data, opt);
1025 },
1026 "mesh"_a,
1027 "old_to_new"_a,
1028 "collision_policy_float"_a = nb::none(),
1029 "collision_policy_integral"_a = nb::none(),
1030 R"(Remap vertices of a mesh in place based on a permutation (Pythonic API).
1031
1032:param mesh: input mesh
1033:param old_to_new: permutation vector for vertices
1034:param collision_policy_float: The collision policy for float attributes.
1035:param collision_policy_integral: The collision policy for integral attributes.)");
1036
1037 m.def(
1038 "reorder_mesh",
1039 [](MeshType& mesh, std::string_view method) {
1040 lagrange::ReorderingMethod reorder_method;
1041 if (method == "Lexicographic" || method == "lexicographic") {
1042 reorder_method = ReorderingMethod::Lexicographic;
1043 } else if (method == "Morton" || method == "morton") {
1044 reorder_method = ReorderingMethod::Morton;
1045 } else if (method == "Hilbert" || method == "hilbert") {
1046 reorder_method = ReorderingMethod::Hilbert;
1047 } else if (method == "None" || method == "none") {
1048 reorder_method = ReorderingMethod::None;
1049 } else {
1050 throw std::runtime_error(fmt::format("Invalid reordering method: {}", method));
1051 }
1052
1053 lagrange::reorder_mesh(mesh, reorder_method);
1054 },
1055 "mesh"_a,
1056 "method"_a = "Morton",
1057 R"(Reorder a mesh in place.
1058
1059:param mesh: input mesh
1060:param method: reordering method, options are 'Lexicographic', 'Morton', 'Hilbert', 'None' (default is 'Morton').)",
1061 nb::sig("def reorder_mesh(mesh: SurfaceMesh, "
1062 "method: typing.Literal['Lexicographic', 'Morton', 'Hilbert', 'None']) -> None"));
1063
1064 m.def(
1065 "separate_by_facet_groups",
1066 [](MeshType& mesh,
1067 Tensor<Index> facet_group_indices,
1068 std::string_view source_vertex_attr_name,
1069 std::string_view source_facet_attr_name,
1070 bool map_attributes) {
1071 SeparateByFacetGroupsOptions options;
1072 options.source_vertex_attr_name = source_vertex_attr_name;
1073 options.source_facet_attr_name = source_facet_attr_name;
1074 options.map_attributes = map_attributes;
1075 auto [data, shape, stride] = tensor_to_span(facet_group_indices);
1076 la_runtime_assert(is_dense(shape, stride));
1077 return separate_by_facet_groups<Scalar, Index>(mesh, data, options);
1078 },
1079 "mesh"_a,
1080 "facet_group_indices"_a,
1081 "source_vertex_attr_name"_a = "",
1082 "source_facet_attr_name"_a = "",
1083 "map_attributes"_a = false,
1084 R"(Extract a set of submeshes based on facet groups.
1085
1086:param mesh: The source mesh.
1087:param facet_group_indices: The group index for each facet. Each group index must be in the range of [0, max(facet_group_indices)]
1088:param source_vertex_attr_name: The optional attribute name to track source vertices.
1089:param source_facet_attr_name: The optional attribute name to track source facets.
1090
1091:returns: A list of meshes, one for each facet group.
1092)");
1093
1094 m.def(
1095 "separate_by_components",
1096 [](MeshType& mesh,
1097 std::string_view source_vertex_attr_name,
1098 std::string_view source_facet_attr_name,
1099 bool map_attributes,
1100 ConnectivityType connectivity_type) {
1101 SeparateByComponentsOptions options;
1102 options.source_vertex_attr_name = source_vertex_attr_name;
1103 options.source_facet_attr_name = source_facet_attr_name;
1104 options.map_attributes = map_attributes;
1105 options.connectivity_type = connectivity_type;
1106 return separate_by_components(mesh, options);
1107 },
1108 "mesh"_a,
1109 "source_vertex_attr_name"_a = "",
1110 "source_facet_attr_name"_a = "",
1111 "map_attributes"_a = false,
1112 "connectivity_type"_a = ConnectivityType::Edge,
1113 R"(Extract a set of submeshes based on connected components.
1114
1115:param mesh: The source mesh.
1116:param source_vertex_attr_name: The optional attribute name to track source vertices.
1117:param source_facet_attr_name: The optional attribute name to track source facets.
1118:param map_attributes: Map attributes from the source to target meshes.
1119:param connectivity_type: The connectivity used for component computation.
1120
1121:returns: A list of meshes, one for each connected component.
1122)");
1123
1124 m.def(
1125 "extract_submesh",
1126 [](MeshType& mesh,
1127 Tensor<Index> selected_facets,
1128 std::string_view source_vertex_attr_name,
1129 std::string_view source_facet_attr_name,
1130 bool map_attributes) {
1131 SubmeshOptions options;
1132 options.source_vertex_attr_name = source_vertex_attr_name;
1133 options.source_facet_attr_name = source_facet_attr_name;
1134 options.map_attributes = map_attributes;
1135 auto [data, shape, stride] = tensor_to_span(selected_facets);
1136 la_runtime_assert(is_dense(shape, stride));
1137 return extract_submesh<Scalar, Index>(mesh, data, options);
1138 },
1139 "mesh"_a,
1140 "selected_facets"_a,
1141 "source_vertex_attr_name"_a = "",
1142 "source_facet_attr_name"_a = "",
1143 "map_attributes"_a = false,
1144 R"(Extract a submesh based on the selected facets.
1145
1146:param mesh: The source mesh.
1147:param selected_facets: A listed of facet ids to extract.
1148:param source_vertex_attr_name: The optional attribute name to track source vertices.
1149:param source_facet_attr_name: The optional attribute name to track source facets.
1150:param map_attributes: Map attributes from the source to target meshes.
1151
1152:returns: A mesh that contains only the selected facets.
1153)");
1154
1155 m.def(
1156 "compute_dihedral_angles",
1157 [](MeshType& mesh,
1158 std::optional<std::string_view> output_attribute_name,
1159 std::optional<std::string_view> facet_normal_attribute_name,
1160 std::optional<bool> recompute_facet_normals,
1161 std::optional<bool> keep_facet_normals) {
1162 DihedralAngleOptions options;
1163 if (output_attribute_name.has_value()) {
1164 options.output_attribute_name = output_attribute_name.value();
1165 }
1166 if (facet_normal_attribute_name.has_value()) {
1167 options.facet_normal_attribute_name = facet_normal_attribute_name.value();
1168 }
1169 if (recompute_facet_normals.has_value()) {
1170 options.recompute_facet_normals = recompute_facet_normals.value();
1171 }
1172 if (keep_facet_normals.has_value()) {
1173 options.keep_facet_normals = keep_facet_normals.value();
1174 }
1175 return compute_dihedral_angles(mesh, options);
1176 },
1177 "mesh"_a,
1178 "output_attribute_name"_a = nb::none(),
1179 "facet_normal_attribute_name"_a = nb::none(),
1180 "recompute_facet_normals"_a = nb::none(),
1181 "keep_facet_normals"_a = nb::none(),
1182 R"(Compute dihedral angles for each edge.
1183
1184The dihedral angle of an edge is defined as the angle between the __normals__ of two facets adjacent
1185to the edge. The dihedral angle is always in the range [0, pi] for manifold edges. For boundary
1186edges, the dihedral angle defaults to 0. For non-manifold edges, the dihedral angle is not
1187well-defined and will be set to the special value 2 * M_PI.
1188
1189:param mesh: The source mesh.
1190:param output_attribute_name: The optional edge attribute name to store the dihedral angles.
1191:param facet_normal_attribute_name: The optional attribute name to store the facet normals.
1192:param recompute_facet_normals: Whether to recompute facet normals.
1193:param keep_facet_normals: Whether to keep newly computed facet normals. It has no effect on pre-existing facet normals.
1194
1195:return: The edge attribute id of dihedral angles.)");
1196
1197 m.def(
1198 "compute_edge_lengths",
1199 [](MeshType& mesh, std::optional<std::string_view> output_attribute_name) {
1200 EdgeLengthOptions options;
1201 if (output_attribute_name.has_value())
1202 options.output_attribute_name = output_attribute_name.value();
1203 return compute_edge_lengths(mesh, options);
1204 },
1205 "mesh"_a,
1206 "output_attribute_name"_a = nb::none(),
1207 R"(Compute edge lengths.
1208
1209:param mesh: The source mesh.
1210:param output_attribute_name: The optional edge attribute name to store the edge lengths.
1211
1212:return: The edge attribute id of edge lengths.)");
1213
1214 m.def(
1215 "compute_dijkstra_distance",
1216 [](MeshType& mesh,
1217 Index seed_facet,
1218 const nb::list& barycentric_coords,
1219 std::optional<Scalar> radius,
1220 std::string_view output_attribute_name,
1221 bool output_involved_vertices) {
1222 DijkstraDistanceOptions<Scalar, Index> options;
1223 options.seed_facet = seed_facet;
1224 for (auto val : barycentric_coords) {
1225 options.barycentric_coords.push_back(nb::cast<Scalar>(val));
1226 }
1227 if (radius.has_value()) {
1228 options.radius = radius.value();
1229 }
1230 options.output_attribute_name = output_attribute_name;
1231 options.output_involved_vertices = output_involved_vertices;
1232 return compute_dijkstra_distance(mesh, options);
1233 },
1234 "mesh"_a,
1235 "seed_facet"_a,
1236 "barycentric_coords"_a,
1237 "radius"_a = nb::none(),
1238 "output_attribute_name"_a = DijkstraDistanceOptions<Scalar, Index>{}.output_attribute_name,
1239 "output_involved_vertices"_a =
1240 DijkstraDistanceOptions<Scalar, Index>{}.output_involved_vertices,
1241 R"(Compute Dijkstra distance from a seed facet.
1242
1243:param mesh: The source mesh.
1244:param seed_facet: The seed facet index.
1245:param barycentric_coords: The barycentric coordinates of the seed facet.
1246:param radius: The maximum radius of the dijkstra distance.
1247:param output_attribute_name: The output attribute name to store the dijkstra distance.
1248:param output_involved_vertices: Whether to output the list of involved vertices.)");
1249
1250 m.def(
1251 "weld_indexed_attribute",
1253 AttributeId attribute_id,
1254 std::optional<double> epsilon_rel,
1255 std::optional<double> epsilon_abs) {
1256 WeldOptions options;
1257 options.epsilon_rel = epsilon_rel;
1258 options.epsilon_abs = epsilon_abs;
1259 return weld_indexed_attribute(mesh, attribute_id, options);
1260 },
1261 "mesh"_a,
1262 "attribute_id"_a,
1263 "epsilon_rel"_a = nb::none(),
1264 "epsilon_abs"_a = nb::none(),
1265 R"(Weld indexed attribute.
1266
1267:param mesh: The source mesh.
1268:param attribute_id: The indexed attribute id to weld.
1269:param epsilon_rel: The relative tolerance for welding.
1270:param epsilon_abs: The absolute tolerance for welding.)");
1271
1272 m.def(
1273 "compute_euler",
1274 &compute_euler<Scalar, Index>,
1275 "mesh"_a,
1276 R"(Compute the Euler characteristic.
1277
1278:param mesh: The source mesh.
1279
1280:return: The Euler characteristic.)");
1281
1282 m.def(
1283 "is_vertex_manifold",
1284 &is_vertex_manifold<Scalar, Index>,
1285 "mesh"_a,
1286 R"(Check if the mesh is vertex manifold.
1287
1288:param mesh: The source mesh.
1289
1290:return: Whether the mesh is vertex manifold.)");
1291
1292 m.def(
1293 "is_edge_manifold",
1294 &is_edge_manifold<Scalar, Index>,
1295 "mesh"_a,
1296 R"(Check if the mesh is edge manifold.
1297
1298:param mesh: The source mesh.
1299
1300:return: Whether the mesh is edge manifold.)");
1301
1302 m.def("is_manifold", &is_manifold<Scalar, Index>, "mesh"_a, R"(Check if the mesh is manifold.
1303
1304A mesh considered as manifold if it is both vertex and edge manifold.
1305
1306:param mesh: The source mesh.
1307
1308:return: Whether the mesh is manifold.)");
1309
1310 m.def(
1311 "is_oriented",
1312 &is_oriented<Scalar, Index>,
1313 "mesh"_a,
1314 R"(Check if the mesh is oriented.
1315
1316:param mesh: The source mesh.
1317
1318:return: Whether the mesh is oriented.)");
1319
1320 m.def(
1321 "transform_mesh",
1322 [](MeshType& mesh,
1323 Eigen::Matrix<Scalar, 4, 4> affine_transform,
1324 bool normalize_normals,
1325 bool normalize_tangents_bitangents,
1326 bool in_place) -> std::optional<MeshType> {
1327 Eigen::Transform<Scalar, 3, Eigen::Affine> M(affine_transform);
1328 TransformOptions options;
1329 options.normalize_normals = normalize_normals;
1330 options.normalize_tangents_bitangents = normalize_tangents_bitangents;
1331
1332 std::optional<MeshType> result;
1333 if (in_place) {
1334 transform_mesh(mesh, M, options);
1335 } else {
1336 result = transformed_mesh(mesh, M, options);
1337 }
1338 return result;
1339 },
1340 "mesh"_a,
1341 "affine_transform"_a,
1342 "normalize_normals"_a = true,
1343 "normalize_tangents_bitangents"_a = true,
1344 "in_place"_a = true,
1345 R"(Apply affine transformation to a mesh.
1346
1347:param mesh: The source mesh.
1348:param affine_transform: The affine transformation matrix.
1349:param normalize_normals: Whether to normalize normals.
1350:param normalize_tangents_bitangents: Whether to normalize tangents and bitangents.
1351:param in_place: Whether to apply the transformation in place.
1352
1353:return: The transformed mesh if in_place is False.)");
1354
1355 nb::enum_<DistortionMetric>(m, "DistortionMetric", "Distortion metric.")
1356 .value("Dirichlet", DistortionMetric::Dirichlet, "Dirichlet energy")
1357 .value("InverseDirichlet", DistortionMetric::InverseDirichlet, "Inverse Dirichlet energy")
1358 .value(
1359 "SymmetricDirichlet",
1360 DistortionMetric::SymmetricDirichlet,
1361 "Symmetric Dirichlet energy")
1362 .value("AreaRatio", DistortionMetric::AreaRatio, "Area ratio")
1363 .value("MIPS", DistortionMetric::MIPS, "Most isotropic parameterization energy");
1364
1365 m.def(
1366 "compute_uv_distortion",
1367 [](MeshType& mesh,
1368 std::string_view uv_attribute_name,
1369 std::string_view output_attribute_name,
1370 DistortionMetric metric) {
1371 UVDistortionOptions opt;
1372 opt.uv_attribute_name = uv_attribute_name;
1373 opt.output_attribute_name = output_attribute_name;
1374 opt.metric = metric;
1375 return compute_uv_distortion(mesh, opt);
1376 },
1377 "mesh"_a,
1378 "uv_attribute_name"_a = "@uv",
1379 "output_attribute_name"_a = "@uv_measure",
1381 R"(Compute UV distortion.
1382
1383:param mesh: The source mesh.
1384:param uv_attribute_name: The input UV attribute name. Default is "@uv".
1385:param output_attribute_name: The output attribute name to store the distortion. Default is "@uv_measure".
1386:param metric: The distortion metric. Default is MIPS.
1387
1388:return: The facet attribute id for distortion.)");
1389
1390 m.def(
1391 "trim_by_isoline",
1392 [](const MeshType& mesh,
1393 std::variant<AttributeId, std::string_view> attribute,
1394 double isovalue,
1395 bool keep_below) {
1396 IsolineOptions opt;
1397 if (std::holds_alternative<AttributeId>(attribute)) {
1398 opt.attribute_id = std::get<AttributeId>(attribute);
1399 } else {
1400 opt.attribute_id = mesh.get_attribute_id(std::get<std::string_view>(attribute));
1401 }
1402 opt.isovalue = isovalue;
1403 opt.keep_below = keep_below;
1404 return trim_by_isoline(mesh, opt);
1405 },
1406 "mesh"_a,
1407 "attribute"_a,
1408 "isovalue"_a = IsolineOptions().isovalue,
1409 "keep_below"_a = IsolineOptions().keep_below,
1410 R"(Trim a mesh by the isoline of an implicit function defined on the mesh vertices/corners.
1411
1412The input mesh must be a triangle mesh.
1413
1414:param mesh: Input triangle mesh to trim.
1415:param attribute: Attribute id or name of the scalar field to use. Can be a vertex or indexed attribute.
1416:param isovalue: Isovalue to trim with.
1417:param keep_below: Whether to keep the part below the isoline.
1418
1419:return: The trimmed mesh.)");
1420
1421 m.def(
1422 "extract_isoline",
1423 [](const MeshType& mesh,
1424 std::variant<AttributeId, std::string_view> attribute,
1425 double isovalue) {
1426 IsolineOptions opt;
1427 if (std::holds_alternative<AttributeId>(attribute)) {
1428 opt.attribute_id = std::get<AttributeId>(attribute);
1429 } else {
1430 opt.attribute_id = mesh.get_attribute_id(std::get<std::string_view>(attribute));
1431 }
1432 opt.isovalue = isovalue;
1433 return extract_isoline(mesh, opt);
1434 },
1435 "mesh"_a,
1436 "attribute"_a,
1437 "isovalue"_a = IsolineOptions().isovalue,
1438 R"(Extract the isoline of an implicit function defined on the mesh vertices/corners.
1439
1440The input mesh must be a triangle mesh.
1441
1442:param mesh: Input triangle mesh to extract the isoline from.
1443:param attribute: Attribute id or name of the scalar field to use. Can be a vertex or indexed attribute.
1444:param isovalue: Isovalue to extract.
1445
1446:return: A mesh whose facets is a collection of size 2 elements representing the extracted isoline.)");
1447
1448 using AttributeNameOrId = AttributeFilter::AttributeNameOrId;
1449 m.def(
1450 "filter_attributes",
1451 [](MeshType& mesh,
1452 std::optional<std::vector<AttributeNameOrId>> included_attributes,
1453 std::optional<std::vector<AttributeNameOrId>> excluded_attributes,
1454 std::optional<std::unordered_set<AttributeUsage>> included_usages,
1455 std::optional<std::unordered_set<AttributeElement>> included_element_types) {
1456 AttributeFilter filter;
1457 if (included_attributes.has_value()) {
1458 filter.included_attributes = included_attributes.value();
1459 }
1460 if (excluded_attributes.has_value()) {
1461 filter.excluded_attributes = excluded_attributes.value();
1462 }
1463 if (included_usages.has_value()) {
1464 filter.included_usages.clear_all();
1465 for (auto usage : included_usages.value()) {
1466 filter.included_usages.set(usage);
1467 }
1468 }
1469 if (included_element_types.has_value()) {
1470 filter.included_element_types.clear_all();
1471 for (auto element_type : included_element_types.value()) {
1472 filter.included_element_types.set(element_type);
1473 }
1474 }
1475 return filter_attributes(mesh, filter);
1476 },
1477 "mesh"_a,
1478 "included_attributes"_a = nb::none(),
1479 "excluded_attributes"_a = nb::none(),
1480 "included_usages"_a = nb::none(),
1481 "included_element_types"_a = nb::none(),
1482 R"(Filters the attributes of mesh according to user specifications.
1483
1484:param mesh: Input mesh.
1485:param included_attributes: List of attribute names or ids to include. By default, all attributes are included.
1486:param excluded_attributes: List of attribute names or ids to exclude. By default, no attribute is excluded.
1487:param included_usages: List of attribute usages to include. By default, all usages are included.
1488:param included_element_types: List of attribute element types to include. By default, all element types are included.)");
1489
1490 m.def(
1491 "cast_attribute",
1492 [](MeshType& mesh,
1493 std::variant<AttributeId, std::string_view> input_attribute,
1494 nb::type_object dtype,
1495 std::optional<std::string_view> output_attribute_name) {
1497 auto cast = [&](AttributeId attr_id) {
1498 auto np = nb::module_::import_("numpy");
1499 if (output_attribute_name.has_value()) {
1500 auto name = output_attribute_name.value();
1501 if (dtype.is(&PyFloat_Type)) {
1502 // Native python float is a C double.
1503 return cast_attribute<double>(mesh, attr_id, name);
1504 } else if (dtype.is(&PyLong_Type)) {
1505 // Native python int maps to int64.
1506 return cast_attribute<int64_t>(mesh, attr_id, name);
1507 } else if (dtype.is(np.attr("float32"))) {
1508 return cast_attribute<float>(mesh, attr_id, name);
1509 } else if (dtype.is(np.attr("float64"))) {
1510 return cast_attribute<double>(mesh, attr_id, name);
1511 } else if (dtype.is(np.attr("int8"))) {
1512 return cast_attribute<int8_t>(mesh, attr_id, name);
1513 } else if (dtype.is(np.attr("int16"))) {
1514 return cast_attribute<int16_t>(mesh, attr_id, name);
1515 } else if (dtype.is(np.attr("int32"))) {
1516 return cast_attribute<int32_t>(mesh, attr_id, name);
1517 } else if (dtype.is(np.attr("int64"))) {
1518 return cast_attribute<int64_t>(mesh, attr_id, name);
1519 } else if (dtype.is(np.attr("uint8"))) {
1520 return cast_attribute<uint8_t>(mesh, attr_id, name);
1521 } else if (dtype.is(np.attr("uint16"))) {
1522 return cast_attribute<uint16_t>(mesh, attr_id, name);
1523 } else if (dtype.is(np.attr("uint32"))) {
1524 return cast_attribute<uint32_t>(mesh, attr_id, name);
1525 } else if (dtype.is(np.attr("uint64"))) {
1526 return cast_attribute<uint64_t>(mesh, attr_id, name);
1527 } else {
1528 throw nb::type_error("Unsupported `dtype`!");
1529 }
1530 } else {
1531 if (dtype.is(&PyFloat_Type)) {
1532 // Native python float is a C double.
1533 return cast_attribute_in_place<double>(mesh, attr_id);
1534 } else if (dtype.is(&PyLong_Type)) {
1535 // Native python int maps to int64.
1536 return cast_attribute_in_place<int64_t>(mesh, attr_id);
1537 } else if (dtype.is(np.attr("float32"))) {
1538 return cast_attribute_in_place<float>(mesh, attr_id);
1539 } else if (dtype.is(np.attr("float64"))) {
1540 return cast_attribute_in_place<double>(mesh, attr_id);
1541 } else if (dtype.is(np.attr("int8"))) {
1542 return cast_attribute_in_place<int8_t>(mesh, attr_id);
1543 } else if (dtype.is(np.attr("int16"))) {
1544 return cast_attribute_in_place<int16_t>(mesh, attr_id);
1545 } else if (dtype.is(np.attr("int32"))) {
1546 return cast_attribute_in_place<int32_t>(mesh, attr_id);
1547 } else if (dtype.is(np.attr("int64"))) {
1548 return cast_attribute_in_place<int64_t>(mesh, attr_id);
1549 } else if (dtype.is(np.attr("uint8"))) {
1550 return cast_attribute_in_place<uint8_t>(mesh, attr_id);
1551 } else if (dtype.is(np.attr("uint16"))) {
1552 return cast_attribute_in_place<uint16_t>(mesh, attr_id);
1553 } else if (dtype.is(np.attr("uint32"))) {
1554 return cast_attribute_in_place<uint32_t>(mesh, attr_id);
1555 } else if (dtype.is(np.attr("uint64"))) {
1556 return cast_attribute_in_place<uint64_t>(mesh, attr_id);
1557 } else {
1558 throw nb::type_error("Unsupported `dtype`!");
1559 }
1560 }
1561 };
1562
1563 if (std::holds_alternative<AttributeId>(input_attribute)) {
1564 return cast(std::get<AttributeId>(input_attribute));
1565 } else {
1566 AttributeId id = mesh.get_attribute_id(std::get<std::string_view>(input_attribute));
1567 return cast(id);
1568 }
1569 },
1570 "mesh"_a,
1571 "input_attribute"_a,
1572 "dtype"_a,
1573 "output_attribute_name"_a = nb::none(),
1574 R"(Cast an attribute to a new dtype.
1575
1576:param mesh: The input mesh.
1577:param input_attribute: The input attribute id or name.
1578:param dtype: The new dtype.
1579:param output_attribute_name: The output attribute name. If none, cast will replace the input attribute.
1580
1581:returns: The id of the new attribute.)");
1582
1583 m.def(
1584 "compute_mesh_covariance",
1586 std::array<Scalar, 3> center,
1587 std::optional<std::string_view> active_facets_attribute_name)
1588 -> std::array<std::array<Scalar, 3>, 3> {
1589 MeshCovarianceOptions options;
1590 options.center = center;
1591 options.active_facets_attribute_name = active_facets_attribute_name;
1592 return compute_mesh_covariance<Scalar, Index>(mesh, options);
1593 },
1594 "mesh"_a,
1595 "center"_a,
1596 "active_facets_attribute_name"_a = nb::none(),
1597 R"(Compute the covariance matrix of a mesh w.r.t. a center (Pythonic API).
1598
1599:param mesh: Input mesh.
1600:param center: The center of the covariance computation.
1601:param active_facets_attribute_name: (optional) Attribute name of whether a facet should be considered in the computation.
1602
1603:returns: The 3 by 3 covariance matrix, which should be symmetric.)");
1604
1605 m.def(
1606 "select_facets_by_normal_similarity",
1608 Index seed_facet_id,
1609 std::optional<double> flood_error_limit,
1610 std::optional<double> flood_second_to_first_order_limit_ratio,
1611 std::optional<std::string_view> facet_normal_attribute_name,
1612 std::optional<std::string_view> is_facet_selectable_attribute_name,
1613 std::optional<std::string_view> output_attribute_name,
1614 std::optional<std::string_view> search_type,
1615 std::optional<int> num_smooth_iterations) {
1616 // Set options in the C++ struct
1617 SelectFacetsByNormalSimilarityOptions options;
1618 if (flood_error_limit.has_value())
1619 options.flood_error_limit = flood_error_limit.value();
1620 if (flood_second_to_first_order_limit_ratio.has_value())
1621 options.flood_second_to_first_order_limit_ratio =
1622 flood_second_to_first_order_limit_ratio.value();
1623 if (facet_normal_attribute_name.has_value())
1624 options.facet_normal_attribute_name = facet_normal_attribute_name.value();
1625 if (is_facet_selectable_attribute_name.has_value()) {
1626 options.is_facet_selectable_attribute_name = is_facet_selectable_attribute_name;
1627 }
1628 if (output_attribute_name.has_value())
1629 options.output_attribute_name = output_attribute_name.value();
1630 if (search_type.has_value()) {
1631 if (search_type.value() == "BFS")
1632 options.search_type = SelectFacetsByNormalSimilarityOptions::SearchType::BFS;
1633 else if (search_type.value() == "DFS")
1634 options.search_type = SelectFacetsByNormalSimilarityOptions::SearchType::DFS;
1635 else
1636 throw std::runtime_error(
1637 fmt::format("Invalid search type: {}", search_type.value()));
1638 }
1639 if (num_smooth_iterations.has_value())
1640 options.num_smooth_iterations = num_smooth_iterations.value();
1641
1642 return select_facets_by_normal_similarity<Scalar, Index>(mesh, seed_facet_id, options);
1643 },
1644 "mesh"_a, /* `_a` is a literal for nanobind to create nb::args, a required argument */
1645 "seed_facet_id"_a,
1646 "flood_error_limit"_a = nb::none(),
1647 "flood_second_to_first_order_limit_ratio"_a = nb::none(),
1648 "facet_normal_attribute_name"_a = nb::none(),
1649 "is_facet_selectable_attribute_name"_a = nb::none(),
1650 "output_attribute_name"_a = nb::none(),
1651 "search_type"_a = nb::none(),
1652 "num_smooth_iterations"_a = nb::none(),
1653 R"(Select facets by normal similarity (Pythonic API).
1654
1655:param mesh: Input mesh.
1656:param seed_facet_id: Index of the seed facet.
1657:param flood_error_limit: Tolerance for normals of the seed and the selected facets. Higher limit leads to larger selected region.
1658: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.
1659: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.
1660:param is_facet_selectable_attribute_name: If provided, this function will look for this attribute to determine if a facet is selectable.
1661:param output_attribute_name: Attribute name of whether a facet is selected.
1662:param search_type: Use 'BFS' for breadth-first search or 'DFS' for depth-first search.
1663:param num_smooth_iterations: Number of iterations to smooth the boundary of the selected region.
1664
1665:returns: Id of the attribute on whether a facet is selected.)",
1666 nb::sig("def select_facets_by_normal_similarity(mesh: SurfaceMesh, "
1667 "seed_facet_id: int, "
1668 "flood_error_limit: float | None = None, "
1669 "flood_second_to_first_order_limit_ratio: float | None = None, "
1670 "facet_normal_attribute_name: str | None = None, "
1671 "is_facet_selectable_attribute_name: str | None = None, "
1672 "output_attribute_name: str | None = None, "
1673 "search_type: typing.Literal['BFS', 'DFS'] | None = None,"
1674 "num_smooth_iterations: int | None = None) -> int"));
1675
1676 m.def(
1677 "select_facets_in_frustum",
1679 std::array<std::array<Scalar, 3>, 4> frustum_plane_points,
1680 std::array<std::array<Scalar, 3>, 4> frustum_plane_normals,
1681 std::optional<bool> greedy,
1682 std::optional<std::string_view> output_attribute_name) {
1683 // Set options in the C++ struct
1684 Frustum<Scalar> frustum;
1685 for (size_t i = 0; i < 4; ++i) {
1686 frustum.planes[i].point = frustum_plane_points[i];
1687 frustum.planes[i].normal = frustum_plane_normals[i];
1688 }
1689 FrustumSelectionOptions options;
1690 if (greedy.has_value()) options.greedy = greedy.value();
1691 if (output_attribute_name.has_value())
1692 options.output_attribute_name = output_attribute_name.value();
1693
1694 return select_facets_in_frustum<Scalar, Index>(mesh, frustum, options);
1695 },
1696 "mesh"_a,
1697 "frustum_plane_points"_a,
1698 "frustum_plane_normals"_a,
1699 "greedy"_a = nb::none(),
1700 "output_attribute_name"_a = nb::none(),
1701 R"(Select facets in a frustum (Pythonic API).
1702
1703:param mesh: Input mesh.
1704:param frustum_plane_points: Four points on each of the frustum planes.
1705:param frustum_plane_normals: Four normals of each of the frustum planes.
1706:param greedy: If true, the function returns as soon as the first facet is found.
1707:param output_attribute_name: Attribute name of whether a facet is selected.
1708
1709:returns: Whether any facets got selected.)");
1710
1711 m.def(
1712 "thicken_and_close_mesh",
1713 [](MeshType& mesh,
1714 std::optional<Scalar> offset_amount,
1715 std::variant<std::monostate, std::array<double, 3>, std::string_view> direction,
1716 std::optional<double> mirror_ratio,
1717 std::optional<size_t> num_segments,
1718 std::optional<std::vector<std::string>> indexed_attributes) {
1719 ThickenAndCloseOptions options;
1720
1721 if (auto array_val = std::get_if<std::array<double, 3>>(&direction)) {
1722 options.direction = *array_val;
1723 } else if (auto string_val = std::get_if<std::string_view>(&direction)) {
1724 options.direction = *string_val;
1725 }
1726 options.offset_amount = offset_amount.value_or(options.offset_amount);
1727 options.mirror_ratio = std::move(mirror_ratio);
1728 options.num_segments = num_segments.value_or(options.num_segments);
1729 options.indexed_attributes = indexed_attributes.value_or(options.indexed_attributes);
1730
1731 return thicken_and_close_mesh<Scalar, Index>(mesh, options);
1732 },
1733 "mesh"_a,
1734 "offset_amount"_a = nb::none(),
1735 "direction"_a = nb::none(),
1736 "mirror_ratio"_a = nb::none(),
1737 "num_segments"_a = nb::none(),
1738 "indexed_attributes"_a = nb::none(),
1739 R"(Thicken a mesh by offsetting it, and close the shape into a thick 3D solid.
1740
1741:param mesh: Input mesh.
1742:param direction: Direction of the offset. Can be an attribute name or a fixed 3D vector.
1743:param offset_amount: Amount of offset.
1744:param mirror_ratio: Ratio of the offset amount to mirror the mesh.
1745:param num_segments: Number of segments to use for the thickening.
1746:param indexed_attributes: List of indexed attributes to copy to the new mesh.
1747
1748:returns: The thickened and closed mesh.)");
1749
1750 m.def(
1751 "extract_boundary_loops",
1752 &extract_boundary_loops<Scalar, Index>,
1753 "mesh"_a,
1754 R"(Extract boundary loops from a mesh.
1755
1756:param mesh: Input mesh.
1757
1758:returns: A list of boundary loops, each represented as a list of vertex indices.)");
1759
1760 m.def(
1761 "extract_boundary_edges",
1762 [](MeshType& mesh) {
1763 mesh.initialize_edges();
1764 Index num_edges = mesh.get_num_edges();
1765 std::vector<Index> bd_edges;
1766 bd_edges.reserve(num_edges);
1767 for (Index ei = 0; ei < num_edges; ++ei) {
1768 if (mesh.is_boundary_edge(ei)) {
1769 bd_edges.push_back(ei);
1770 }
1771 }
1772 return bd_edges;
1773 },
1774 "mesh"_a,
1775 R"(Extract boundary edges from a mesh.
1776
1777:param mesh: Input mesh.
1778
1779:returns: A list of boundary edge indices.)");
1780
1781 m.def(
1782 "compute_uv_charts",
1783 [](MeshType& mesh,
1784 std::string_view uv_attribute_name,
1785 std::string_view output_attribute_name,
1786 std::string_view connectivity_type) {
1787 UVChartOptions options;
1788 options.uv_attribute_name = uv_attribute_name;
1789 options.output_attribute_name = output_attribute_name;
1790 if (connectivity_type == "Vertex") {
1791 options.connectivity_type = UVChartOptions::ConnectivityType::Vertex;
1792 } else if (connectivity_type == "Edge") {
1793 options.connectivity_type = UVChartOptions::ConnectivityType::Edge;
1794 } else {
1795 throw std::runtime_error(
1796 fmt::format("Invalid connectivity type: {}", connectivity_type));
1797 }
1798 return compute_uv_charts(mesh, options);
1799 },
1800 "mesh"_a,
1801 "uv_attribute_name"_a = UVChartOptions().uv_attribute_name,
1802 "output_attribute_name"_a = UVChartOptions().output_attribute_name,
1803 "connectivity_type"_a = "Edge",
1804 R"(Compute UV charts.
1805
1806@param mesh: Input mesh.
1807@param uv_attribute_name: Name of the UV attribute.
1808@param output_attribute_name: Name of the output attribute to store the chart ids.
1809@param connectivity_type: Type of connectivity to use for chart computation. Can be "Vertex" or "Edge".
1810
1811@returns: A list of chart ids for each vertex.)");
1812
1813 m.def(
1814 "uv_mesh_view",
1815 [](const MeshType& mesh, std::string_view uv_attribute_name) {
1816 UVMeshOptions options;
1817 options.uv_attribute_name = uv_attribute_name;
1818 return uv_mesh_view(mesh, options);
1819 },
1820 "mesh"_a,
1821 "uv_attribute_name"_a = UVMeshOptions().uv_attribute_name,
1822 R"(Extract a UV mesh view from a 3D mesh.
1823
1824:param mesh: Input mesh.
1825:param uv_attribute_name: Name of the (indexed or vertex) UV attribute.
1826
1827:return: A new mesh representing the UV mesh.)");
1828 m.def(
1829 "uv_mesh_ref",
1830 [](MeshType& mesh, std::string_view uv_attribute_name) {
1831 UVMeshOptions options;
1832 options.uv_attribute_name = uv_attribute_name;
1833 return uv_mesh_ref(mesh, options);
1834 },
1835 "mesh"_a,
1836 "uv_attribute_name"_a = UVMeshOptions().uv_attribute_name,
1837 R"(Extract a UV mesh reference from a 3D mesh.
1838
1839:param mesh: Input mesh.
1840:param uv_attribute_name: Name of the (indexed or vertex) UV attribute.
1841
1842:return: A new mesh representing the UV mesh.)");
1843}
1844
1845} // namespace lagrange::python
Definition: Mesh.h:48
A general purpose polygonal mesh class.
Definition: SurfaceMesh.h:66
bool is_boundary_edge(Index e) const
Determines whether the specified edge e is a boundary edge.
Definition: SurfaceMesh.cpp:2615
Index get_dimension() const
Retrieves the dimension of the mesh vertices.
Definition: SurfaceMesh.h:656
AttributeId get_attribute_id(std::string_view name) const
Retrieve an attribute id given its name.
Definition: SurfaceMesh.cpp:341
Index get_num_edges() const
Retrieves the number of edges.
Definition: SurfaceMesh.h:692
void initialize_edges(span< const Index > edges={})
Initializes attributes associated to mesh edges and connectivity.
Definition: SurfaceMesh.cpp:2198
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:140
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
@ Edge
Per-edge mesh attributes.
Definition: AttributeFwd.h:34
@ Facet
Per-facet mesh attributes.
Definition: AttributeFwd.h:31
@ Vertex
Per-vertex mesh attributes.
Definition: AttributeFwd.h:28
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:319
AttributeId compute_edge_lengths(SurfaceMesh< Scalar, Index > &mesh, const EdgeLengthOptions &options={})
Computes edge lengths attribute.
Definition: compute_edge_lengths.cpp:28
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:322
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
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:32
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.
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:206
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 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:197
void normalize_meshes(span< SurfaceMesh< Scalar, Index > * > meshes)
Normalize a list of meshes to fit in a unit box centered at the origin.
Definition: normalize_meshes.cpp:39
AttributeId compute_uv_distortion(SurfaceMesh< Scalar, Index > &mesh, const UVDistortionOptions &options={})
Compute uv distortion using the selected distortion measure.
Definition: compute_uv_distortion.cpp:30
DistortionMetric
UV distortion metric type.
Definition: DistortionMetric.h:26
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:327
@ Angle
Incident face normals are averaged weighted by incident angle of vertex.
@ CornerTriangleArea
Incident face normals are averaged weighted by area of the corner triangle.
@ Uniform
Incident face normals have uniform influence on vertex normal.
@ MIPS
UV triangle area / 3D triangle area.
#define la_runtime_assert(...)
Runtime assertion check.
Definition: assert.h:169
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:26
SurfaceMesh< Scalar, 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:51
size_t compute_uv_charts(SurfaceMesh< Scalar, Index > &mesh, const UVChartOptions &options={})
Compute UV charts of an input mesh.
Definition: compute_uv_charts.cpp:23
@ Edge
Two facets are considered connected if they share an edge.
ReorderingMethod
Mesh reordering method to apply before decimation.
Definition: reorder_mesh.h:21
SurfaceMesh< Scalar, Index > uv_mesh_ref(SurfaceMesh< Scalar, Index > &mesh, const UVMeshOptions &options={})
Extract a UV mesh reference from an input mesh.
Definition: uv_mesh.cpp:21
void reorder_mesh(SurfaceMesh< Scalar, Index > &mesh, ReorderingMethod method)
Mesh reordering to improve cache locality.
Definition: reorder_mesh.cpp:178
Options to control connected components computation.
Definition: compute_components.h:31
std::string_view output_attribute_name
Output normal attribute name.
Definition: compute_facet_normal.h:35
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
NormalWeightingType weight_type
Per-vertex normal averaging weighting type.
Definition: compute_normal.h:44
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
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