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>
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>
80namespace lagrange::python {
82template <
typename Scalar,
typename Index>
83void bind_utilities(nanobind::module_& m)
85 namespace nb = nanobind;
86 using namespace nb::literals;
89 nb::enum_<NormalWeightingType>(m,
"NormalWeightingType",
"Normal weighting type.")
94 "Weight by corner triangle area")
97 nb::class_<VertexNormalOptions>(
99 "VertexNormalOptions",
100 "Options for computing vertex normals")
103 "output_attribute_name",
105 "Output attribute name. Default is `@vertex_normal`.")
109 "Weighting type for normal computation. Default is Angle.")
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.")
118 "recompute_weighted_corner_normals",
120 "Whether to recompute weighted corner normals. Default is false")
122 "keep_weighted_corner_normals",
124 "Whether to keep the weighted corner normal attribute. Default is false.");
127 "compute_vertex_normal",
128 &compute_vertex_normal<Scalar, Index>,
130 "options"_a = VertexNormalOptions(),
131 R
"(Computer vertex normal.
133:param mesh: Input mesh.
134:param options: Options for computing vertex normals.
136:returns: Vertex normal attribute id.)");
139 "compute_vertex_normal",
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;
157 return compute_vertex_normal<Scalar, Index>(mesh, options);
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).
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.
174:returns: Vertex normal attribute id.)");
176 nb::class_<FacetNormalOptions>(m, "FacetNormalOptions",
"Facet normal computation options.")
179 "output_attribute_name",
181 "Output attribute name. Default: `@facet_normal`");
184 "compute_facet_normal",
185 &compute_facet_normal<Scalar, Index>,
187 "options"_a = FacetNormalOptions(),
188 R
"(Compute facet normal.
190:param mesh: Input mesh.
191:param options: Options for computing facet normals.
193:returns: Facet normal attribute id.)");
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);
203 "output_attribute_name"_a = nb::none(),
204 R
"(Compute facet normal (Pythonic API).
206:param mesh: Input mesh.
207:param output_attribute_name: Output attribute name.
209:returns: Facet normal attribute id.)");
211 nb::class_<NormalOptions>(m, "NormalOptions",
"Normal computation options.")
214 "output_attribute_name",
216 "Output attribute name. Default: `@normal`")
220 "Weighting type for normal computation. Default is Angle.")
222 "facet_normal_attribute_name",
224 "Facet normal attribute name to use. Default is `@facet_normal`.")
226 "recompute_facet_normals",
228 "Whether to recompute facet normals. Default is false.")
230 "keep_facet_normals",
232 "Whether to keep the computed facet normal attribute. Default is false.");
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());
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);
255 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
257 throw std::runtime_error(
"Invalid cone_vertices type");
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.
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.
269:param mesh: input mesh
270:param feature_angle_threshold: feature angle threshold
271:param cone_vertices: cone vertices
272:param options: normal options
274:returns: the id of the indexed normal attribute.
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;
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);
305 return compute_normal<Scalar, Index>(mesh, feature_angle_threshold, data, options);
307 throw std::runtime_error(
"Invalid cone_vertices type");
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).
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
329:returns: the id of the indexed normal attribute.)");
331 using ConstArray3d = nb::ndarray<
const double, nb::shape<-1, 3>, nb::c_contig, nb::device::cpu>;
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);
343 "shift_centroid"_a = ComputePointcloudPCAOptions().shift_centroid,
344 "normalize"_a = ComputePointcloudPCAOptions().normalize,
345 R
"(Compute principal components of a point cloud.
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?
351:returns: tuple of (center, eigenvectors, eigenvalues).)");
354 "compute_greedy_coloring",
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);
367 "num_color_used"_a = 8,
368 "output_attribute_name"_a = nb::none(),
369 R
"(Compute greedy coloring of mesh elements.
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.
376:returns: Color attribute id.)");
380 &normalize_mesh<Scalar, Index>,
382 R
"(Normalize a mesh to fit into a unit box centered at the origin.
384:param mesh: input mesh)");
389 span<SurfaceMesh<Scalar, Index>*> meshes_span(meshes.data(), meshes.size());
393 R
"(Normalize a list of meshes to fit into a unit box centered at the origin.
395:param meshes: input meshes)");
400 return combine_meshes<Scalar, Index>(
406 "preserve_attributes"_a =
true,
407 R
"(Combine a list of meshes into a single mesh.
409:param meshes: input meshes
410:param preserve_attributes: whether to preserve attributes
412:returns: the combined mesh)");
415 "compute_seam_edges",
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);
424 "indexed_attribute_id"_a,
425 "output_attribute_name"_a = nb::none(),
426 R
"(Computer seam edges for a given indexed attribute.
428:param mesh: Input mesh.
429:param indexed_attribute_id: Input indexed attribute id.
430:param output_attribute_name: Output attribute name.
432:returns: Attribute id for the output per-edge seam attribute (1 is a seam, 0 is not).)");
437 OrientOptions options;
438 options.positive = positive;
439 orient_outward<Scalar, Index>(mesh, options);
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.
445:param mesh: Input mesh.
446:param positive: Whether to orient each volume positively or negatively.)");
449 "unify_index_buffer",
452 R
"(Unify the index buffer of the mesh. All indexed attributes will be unified.
454:param mesh: The mesh to unify.
456:returns: The unified mesh.)");
459 "unify_index_buffer",
460 &lagrange::unify_index_buffer<Scalar, Index>,
463 R
"(Unify the index buffer of the mesh for selected attributes.
465:param mesh: The mesh to unify.
466:param attribute_ids: The selected attribute ids to unify.
468:returns: The unified mesh.)");
471 "unify_index_buffer",
472 &lagrange::unify_named_index_buffer<Scalar, Index>,
475 R
"(Unify the index buffer of the mesh for selected attributes.
477:param mesh: The mesh to unify.
478:param attribute_names: The selected attribute names to unify.
480:returns: The unified mesh.)");
483 "triangulate_polygonal_facets",
484 &lagrange::triangulate_polygonal_facets<Scalar, Index>,
486 R
"(Triangulate polygonal facets of the mesh.
488:param mesh: The input mesh.)");
490 nb::enum_<ComponentOptions::ConnectivityType>(m, "ConnectivityType",
"Mesh connectivity type")
494 "Two facets are connected if they share a vertex")
498 "Two facets are connected if they share an edge");
501 "compute_components",
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();
510 if (connectivity_type.has_value()) {
511 opt.connectivity_type = connectivity_type.value();
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));
519 return lagrange::compute_components<Scalar, Index>(mesh, blocker_elements_vec, opt);
522 "output_attribute_name"_a = nb::none(),
523 "connectivity_type"_a = nb::none(),
524 "blocker_elements"_a = nb::none(),
525 R
"(Compute connected components.
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.
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.
535:returns: The total number of components.)");
537 nb::class_<VertexValenceOptions>(m, "VertexValenceOptions",
"Vertex valence options")
540 "output_attribute_name",
541 &VertexValenceOptions::output_attribute_name,
542 "The name of the output attribute")
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");
550 "compute_vertex_valence",
551 &lagrange::compute_vertex_valence<Scalar, Index>,
553 "options"_a = VertexValenceOptions(),
554 R
"(Compute vertex valence
556:param mesh: The input mesh.
557:param options: The vertex valence options.
559:returns: The vertex valence attribute id.)");
562 "compute_vertex_valence",
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();
570 if (induced_by_attribute.has_value()) {
571 opt.induced_by_attribute = induced_by_attribute.value();
573 return lagrange::compute_vertex_valence<Scalar, Index>(mesh, opt);
576 "output_attribute_name"_a = nb::none(),
577 "induced_by_attribute"_a = nb::none(),
578 R
"(Compute vertex valence);
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.
584:returns: The vertex valence attribute id)");
586 nb::class_<TangentBitangentOptions>(m, "TangentBitangentOptions",
"Tangent bitangent options")
589 "tangent_attribute_name",
590 &TangentBitangentOptions::tangent_attribute_name,
591 "The name of the output tangent attribute, default is `@tangent`")
593 "bitangent_attribute_name",
594 &TangentBitangentOptions::bitangent_attribute_name,
595 "The name of the output bitangent attribute, default is `@bitangent`")
598 &TangentBitangentOptions::uv_attribute_name,
599 "The name of the uv attribute")
601 "normal_attribute_name",
602 &TangentBitangentOptions::normal_attribute_name,
603 "The name of the normal attribute")
605 "output_element_type",
606 &TangentBitangentOptions::output_element_type,
607 "The output element type")
610 &TangentBitangentOptions::pad_with_sign,
611 "Whether to pad the output tangent/bitangent with sign");
613 nb::class_<TangentBitangentResult>(m,
"TangentBitangentResult",
"Tangent bitangent result")
617 &TangentBitangentResult::tangent_id,
618 "The output tangent attribute id")
621 &TangentBitangentResult::bitangent_id,
622 "The output bitangent attribute id");
625 "compute_tangent_bitangent",
626 &lagrange::compute_tangent_bitangent<Scalar, Index>,
628 "options"_a = TangentBitangentOptions(),
629 R
"(Compute tangent and bitangent vector attributes.
631:param mesh: The input mesh.
632:param options: The tangent bitangent options.
634:returns: The tangent and bitangent attribute ids)");
637 "compute_tangent_bitangent",
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();
649 if (bitangent_attribute_name.has_value()) {
650 opt.bitangent_attribute_name = bitangent_attribute_name.value();
652 if (uv_attribute_name.has_value()) {
653 opt.uv_attribute_name = uv_attribute_name.value();
655 if (normal_attribute_name.has_value()) {
656 opt.normal_attribute_name = normal_attribute_name.value();
658 if (output_attribute_type.has_value()) {
659 opt.output_element_type = output_attribute_type.value();
661 if (pad_with_sign.has_value()) {
662 opt.pad_with_sign = pad_with_sign.value();
665 auto r = compute_tangent_bitangent<Scalar, Index>(mesh, opt);
666 return std::make_tuple(r.tangent_id, r.bitangent_id);
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).
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.
685:returns: The tangent and bitangent attribute ids)");
695 "old_attribute_id"_a,
696 "new_attribute_name"_a,
698 R
"(Map an attribute to a new element type.
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.
705:returns: The id of the new attribute.)");
715 "old_attribute_name"_a,
716 "new_attribute_name"_a,
718 R
"(Map an attribute to a new element type.
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.
725:returns: The id of the new attribute.)");
728 "map_attribute_in_place",
730 &map_attribute_in_place<Scalar, Index>),
734 R
"(Map an attribute to a new element type in place.
736:param mesh: The input mesh.
737:param id: The id of the input attribute.
738:param new_element: The new element type.
740:returns: The id of the new attribute.)");
743 "map_attribute_in_place",
746 &map_attribute_in_place<Scalar, Index>),
750 R
"(Map an attribute to a new element type in place.
752:param mesh: The input mesh.
753:param name: The name of the input attribute.
754:param new_element: The new element type.
756:returns: The id of the new attribute.)");
758 nb::class_<FacetAreaOptions>(m, "FacetAreaOptions",
"Options for computing facet area.")
761 "output_attribute_name",
762 &FacetAreaOptions::output_attribute_name,
763 "The name of the output attribute.");
766 "compute_facet_area",
767 &lagrange::compute_facet_area<Scalar, Index>,
769 "options"_a = FacetAreaOptions(),
770 R
"(Compute facet area.
772:param mesh: The input mesh.
773:param options: The options for computing facet area.
775:returns: The id of the new attribute.)");
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();
784 return lagrange::compute_facet_area<Scalar, Index>(mesh, opt);
787 "output_attribute_name"_a = nb::none(),
788 R
"(Compute facet area (Pythonic API).
790:param mesh: The input mesh.
791:param output_attribute_name: The name of the output attribute.
793:returns: The id of the new attribute.)");
795 nb::class_<MeshAreaOptions>(m, "MeshAreaOptions",
"Options for computing mesh area.")
798 "input_attribute_name",
799 &MeshAreaOptions::input_attribute_name,
800 "The name of the pre-computed facet area attribute, default is `@facet_area`.")
803 &MeshAreaOptions::use_signed_area,
804 "Whether to use signed area.");
808 &lagrange::compute_mesh_area<Scalar, Index>,
810 "options"_a = MeshAreaOptions(),
811 R
"(Compute mesh area.
813:param mesh: The input mesh.
814:param options: The options for computing mesh area.
816:returns: The mesh area.)");
821 std::optional<std::string_view> input_attribute_name,
822 std::optional<bool> use_signed_area) {
824 if (input_attribute_name.has_value()) {
825 opt.input_attribute_name = input_attribute_name.value();
827 if (use_signed_area.has_value()) {
828 opt.use_signed_area = use_signed_area.value();
833 "input_attribute_name"_a = nb::none(),
834 "use_signed_area"_a = nb::none(),
835 R
"(Compute mesh area (Pythonic API).
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.
841:returns: The mesh area.)");
843 nb::class_<FacetCentroidOptions>(m, "FacetCentroidOptions",
"Facet centroid options.")
846 "output_attribute_name",
847 &FacetCentroidOptions::output_attribute_name,
848 "The name of the output attribute.");
850 "compute_facet_centroid",
851 &lagrange::compute_facet_centroid<Scalar, Index>,
853 "options"_a = FacetCentroidOptions(),
854 R
"(Compute facet centroid.
856:param mesh: The input mesh.
857:param options: The options for computing facet centroid.
859:returns: The id of the new attribute.)");
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();
868 return lagrange::compute_facet_centroid<Scalar, Index>(mesh, opt);
871 "output_attribute_name"_a = nb::none(),
872 R
"(Compute facet centroid (Pythonic API).
874:param mesh: The input mesh.
875:param output_attribute_name: The name of the output attribute.
877:returns: The id of the new attribute.)");
879 nb::enum_<MeshCentroidOptions::WeightingType>(
881 "CentroidWeightingType",
882 "Centroid weighting type.")
883 .value(
"Uniform", MeshCentroidOptions::Uniform,
"Uniform weighting.")
884 .value(
"Area", MeshCentroidOptions::Area,
"Area weighting.");
886 nb::class_<MeshCentroidOptions>(m,
"MeshCentroidOptions",
"Mesh centroid options.")
888 .def_rw(
"weighting_type", &MeshCentroidOptions::weighting_type,
"The weighting type.")
890 "facet_centroid_attribute_name",
891 &MeshCentroidOptions::facet_centroid_attribute_name,
892 "The name of the pre-computed facet centroid attribute if available.")
894 "facet_area_attribute_name",
895 &MeshCentroidOptions::facet_area_attribute_name,
896 "The name of the pre-computed facet area attribute if available.");
899 "compute_mesh_centroid",
902 std::vector<Scalar> centroid(dim, invalid<Scalar>());
903 compute_mesh_centroid<Scalar, Index>(mesh, centroid, opt);
907 "options"_a = MeshCentroidOptions(),
908 R
"(Compute mesh centroid.
910:param mesh: The input mesh.
911:param options: The options for computing mesh centroid.
913:returns: The mesh centroid.)");
916 "compute_mesh_centroid",
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();
925 if (facet_centroid_attribute_name.has_value()) {
926 opt.facet_centroid_attribute_name = facet_centroid_attribute_name.value();
928 if (facet_area_attribute_name.has_value()) {
929 opt.facet_area_attribute_name = facet_area_attribute_name.value();
932 std::vector<Scalar> centroid(dim, invalid<Scalar>());
933 compute_mesh_centroid<Scalar, Index>(mesh, centroid, opt);
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).
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`.
947:returns: The mesh centroid.)");
951 [](
MeshType& mesh, Tensor<Index> new_to_old) {
952 auto [data, shape, stride] = tensor_to_span(new_to_old);
954 permute_vertices<Scalar, Index>(mesh, data);
958 R
"(Reorder vertices of a mesh in place based on a permutation.
960:param mesh: input mesh
961:param new_to_old: permutation vector for vertices)");
965 [](
MeshType& mesh, Tensor<Index> new_to_old) {
966 auto [data, shape, stride] = tensor_to_span(new_to_old);
968 permute_facets<Scalar, Index>(mesh, data);
972 R
"(Reorder facets of a mesh in place based on a permutation.
974:param mesh: input mesh
975:param new_to_old: permutation vector for facets)");
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.");
982 nb::class_<RemapVerticesOptions>(m,
"RemapVerticesOptions",
"Options for remapping vertices.")
985 "collision_policy_float",
986 &RemapVerticesOptions::collision_policy_float,
987 "The collision policy for float attributes.")
989 "collision_policy_integral",
990 &RemapVerticesOptions::collision_policy_integral,
991 "The collision policy for integral attributes.");
995 [](
MeshType& mesh, Tensor<Index> old_to_new, RemapVerticesOptions opt) {
996 auto [data, shape, stride] = tensor_to_span(old_to_new);
998 remap_vertices<Scalar, Index>(mesh, data, opt);
1002 "options"_a = RemapVerticesOptions(),
1003 R
"(Remap vertices of a mesh in place based on a permutation.
1005:param mesh: input mesh
1006:param old_to_new: permutation vector for vertices
1007:param options: options for remapping vertices)");
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();
1019 if (collision_policy_integral.has_value()) {
1020 opt.collision_policy_integral = collision_policy_integral.value();
1022 auto [data, shape, stride] = tensor_to_span(old_to_new);
1024 remap_vertices<Scalar, Index>(mesh, data, opt);
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).
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.)");
1039 [](
MeshType& mesh, std::string_view 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;
1050 throw std::runtime_error(fmt::format(
"Invalid reordering method: {}", method));
1056 "method"_a =
"Morton",
1057 R
"(Reorder a mesh in place.
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"));
1065 "separate_by_facet_groups",
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;
1075 auto [data, shape, stride] = tensor_to_span(facet_group_indices);
1077 return separate_by_facet_groups<Scalar, Index>(mesh, data, options);
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.
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.
1091:returns: A list of meshes, one for each facet group.
1095 "separate_by_components",
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;
1105 options.connectivity_type = connectivity_type;
1109 "source_vertex_attr_name"_a =
"",
1110 "source_facet_attr_name"_a =
"",
1111 "map_attributes"_a =
false,
1113 R
"(Extract a set of submeshes based on connected components.
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.
1121:returns: A list of meshes, one for each connected component.
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;
1135 auto [data, shape, stride] = tensor_to_span(selected_facets);
1137 return extract_submesh<Scalar, Index>(mesh, data, options);
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.
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.
1152:returns: A mesh that contains only the selected facets.
1156 "compute_dihedral_angles",
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();
1166 if (facet_normal_attribute_name.has_value()) {
1167 options.facet_normal_attribute_name = facet_normal_attribute_name.value();
1169 if (recompute_facet_normals.has_value()) {
1170 options.recompute_facet_normals = recompute_facet_normals.value();
1172 if (keep_facet_normals.has_value()) {
1173 options.keep_facet_normals = keep_facet_normals.value();
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.
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.
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.
1195:return: The edge attribute id of dihedral angles.)");
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();
1206 "output_attribute_name"_a = nb::none(),
1207 R
"(Compute edge lengths.
1209:param mesh: The source mesh.
1210:param output_attribute_name: The optional edge attribute name to store the edge lengths.
1212:return: The edge attribute id of edge lengths.)");
1215 "compute_dijkstra_distance",
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));
1227 if (radius.has_value()) {
1228 options.radius = radius.value();
1230 options.output_attribute_name = output_attribute_name;
1231 options.output_involved_vertices = output_involved_vertices;
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.
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.)");
1251 "weld_indexed_attribute",
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;
1263 "epsilon_rel"_a = nb::none(),
1264 "epsilon_abs"_a = nb::none(),
1265 R
"(Weld indexed attribute.
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.)");
1274 &compute_euler<Scalar, Index>,
1276 R
"(Compute the Euler characteristic.
1278:param mesh: The source mesh.
1280:return: The Euler characteristic.)");
1283 "is_vertex_manifold",
1284 &is_vertex_manifold<Scalar, Index>,
1286 R
"(Check if the mesh is vertex manifold.
1288:param mesh: The source mesh.
1290:return: Whether the mesh is vertex manifold.)");
1294 &is_edge_manifold<Scalar, Index>,
1296 R
"(Check if the mesh is edge manifold.
1298:param mesh: The source mesh.
1300:return: Whether the mesh is edge manifold.)");
1302 m.def("is_manifold", &is_manifold<Scalar, Index>,
"mesh"_a, R
"(Check if the mesh is manifold.
1304A mesh considered as manifold if it is both vertex and edge manifold.
1306:param mesh: The source mesh.
1308:return: Whether the mesh is manifold.)");
1312 &is_oriented<Scalar, Index>,
1314 R
"(Check if the mesh is oriented.
1316:param mesh: The source mesh.
1318:return: Whether the mesh is oriented.)");
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;
1332 std::optional<MeshType> result;
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.
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.
1353:return: The transformed mesh if in_place is False.)");
1355 nb::enum_<DistortionMetric>(m, "DistortionMetric",
"Distortion metric.")
1356 .value(
"Dirichlet", DistortionMetric::Dirichlet,
"Dirichlet energy")
1357 .value(
"InverseDirichlet", DistortionMetric::InverseDirichlet,
"Inverse Dirichlet energy")
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");
1366 "compute_uv_distortion",
1368 std::string_view uv_attribute_name,
1369 std::string_view output_attribute_name,
1371 UVDistortionOptions opt;
1372 opt.uv_attribute_name = uv_attribute_name;
1373 opt.output_attribute_name = output_attribute_name;
1374 opt.metric = metric;
1378 "uv_attribute_name"_a =
"@uv",
1379 "output_attribute_name"_a =
"@uv_measure",
1381 R
"(Compute UV distortion.
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.
1388:return: The facet attribute id for distortion.)");
1393 std::variant<AttributeId, std::string_view> attribute,
1397 if (std::holds_alternative<AttributeId>(attribute)) {
1398 opt.attribute_id = std::get<AttributeId>(attribute);
1400 opt.attribute_id = mesh.
get_attribute_id(std::get<std::string_view>(attribute));
1402 opt.isovalue = isovalue;
1403 opt.keep_below = keep_below;
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.
1412The input mesh must be a triangle mesh.
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.
1419:return: The trimmed mesh.)");
1424 std::variant<AttributeId, std::string_view> attribute,
1427 if (std::holds_alternative<AttributeId>(attribute)) {
1428 opt.attribute_id = std::get<AttributeId>(attribute);
1430 opt.attribute_id = mesh.
get_attribute_id(std::get<std::string_view>(attribute));
1432 opt.isovalue = isovalue;
1437 "isovalue"_a = IsolineOptions().isovalue,
1438 R
"(Extract the isoline of an implicit function defined on the mesh vertices/corners.
1440The input mesh must be a triangle mesh.
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.
1446:return: A mesh whose facets is a collection of size 2 elements representing the extracted isoline.)");
1448 using AttributeNameOrId = AttributeFilter::AttributeNameOrId;
1450 "filter_attributes",
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();
1460 if (excluded_attributes.has_value()) {
1461 filter.excluded_attributes = excluded_attributes.value();
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);
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);
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.
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.)");
1493 std::variant<AttributeId, std::string_view> input_attribute,
1494 nb::type_object dtype,
1495 std::optional<std::string_view> output_attribute_name) {
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)) {
1503 return cast_attribute<double>(mesh, attr_id, name);
1504 }
else if (dtype.is(&PyLong_Type)) {
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);
1528 throw nb::type_error(
"Unsupported `dtype`!");
1531 if (dtype.is(&PyFloat_Type)) {
1533 return cast_attribute_in_place<double>(mesh, attr_id);
1534 }
else if (dtype.is(&PyLong_Type)) {
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);
1558 throw nb::type_error(
"Unsupported `dtype`!");
1563 if (std::holds_alternative<AttributeId>(input_attribute)) {
1564 return cast(std::get<AttributeId>(input_attribute));
1571 "input_attribute"_a,
1573 "output_attribute_name"_a = nb::none(),
1574 R
"(Cast an attribute to a new dtype.
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.
1581:returns: The id of the new attribute.)");
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);
1596 "active_facets_attribute_name"_a = nb::none(),
1597 R
"(Compute the covariance matrix of a mesh w.r.t. a center (Pythonic API).
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.
1603:returns: The 3 by 3 covariance matrix, which should be symmetric.)");
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) {
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;
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;
1636 throw std::runtime_error(
1637 fmt::format(
"Invalid search type: {}", search_type.value()));
1639 if (num_smooth_iterations.has_value())
1640 options.num_smooth_iterations = num_smooth_iterations.value();
1642 return select_facets_by_normal_similarity<Scalar, Index>(mesh, seed_facet_id, options);
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).
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.
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"));
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) {
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];
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();
1694 return select_facets_in_frustum<Scalar, Index>(mesh, frustum, options);
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).
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.
1709:returns: Whether any facets got selected.)");
1712 "thicken_and_close_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;
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;
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);
1731 return thicken_and_close_mesh<Scalar, Index>(mesh, options);
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.
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.
1748:returns: The thickened and closed mesh.)");
1751 "extract_boundary_loops",
1752 &extract_boundary_loops<Scalar, Index>,
1754 R
"(Extract boundary loops from a mesh.
1756:param mesh: Input mesh.
1758:returns: A list of boundary loops, each represented as a list of vertex indices.)");
1761 "extract_boundary_edges",
1765 std::vector<Index> bd_edges;
1766 bd_edges.reserve(num_edges);
1767 for (Index ei = 0; ei < num_edges; ++ei) {
1769 bd_edges.push_back(ei);
1775 R
"(Extract boundary edges from a mesh.
1777:param mesh: Input mesh.
1779:returns: A list of boundary edge indices.)");
1782 "compute_uv_charts",
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") {
1792 }
else if (connectivity_type ==
"Edge") {
1795 throw std::runtime_error(
1796 fmt::format(
"Invalid connectivity type: {}", connectivity_type));
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.
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".
1811@returns: A list of chart ids for each vertex.)");
1815 [](
const MeshType& mesh, std::string_view uv_attribute_name) {
1816 UVMeshOptions options;
1817 options.uv_attribute_name = uv_attribute_name;
1821 "uv_attribute_name"_a = UVMeshOptions().uv_attribute_name,
1822 R
"(Extract a UV mesh view from a 3D mesh.
1824:param mesh: Input mesh.
1825:param uv_attribute_name: Name of the (indexed or vertex) UV attribute.
1827:return: A new mesh representing the UV mesh.)");
1830 [](
MeshType& mesh, std::string_view uv_attribute_name) {
1831 UVMeshOptions options;
1832 options.uv_attribute_name = uv_attribute_name;
1836 "uv_attribute_name"_a = UVMeshOptions().uv_attribute_name,
1837 R
"(Extract a UV mesh reference from a 3D mesh.
1839:param mesh: Input mesh.
1840:param uv_attribute_name: Name of the (indexed or vertex) UV attribute.
1842:return: A new mesh representing the UV mesh.)");
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