14#include "PyAttribute.h"
16#include <lagrange/Attribute.h>
17#include <lagrange/AttributeFwd.h>
18#include <lagrange/AttributeTypes.h>
19#include <lagrange/IndexedAttribute.h>
20#include <lagrange/Logger.h>
21#include <lagrange/SurfaceMesh.h>
22#include <lagrange/find_matching_attributes.h>
23#include <lagrange/foreach_attribute.h>
24#include <lagrange/python/binding.h>
25#include <lagrange/python/tensor_utils.h>
26#include <lagrange/utils/BitField.h>
27#include <lagrange/utils/Error.h>
28#include <lagrange/utils/assert.h>
29#include <lagrange/utils/invalid.h>
32#include <lagrange/utils/warnoff.h>
33#include <tbb/parallel_for.h>
34#include <lagrange/utils/warnon.h>
35#include <lagrange/utils/fmt/format.h>
38namespace lagrange::python {
40template <
typename Scalar,
typename Index>
41void bind_surface_mesh(nanobind::module_& m)
43 namespace nb = nanobind;
44 using namespace nb::literals;
45 using namespace lagrange;
46 using namespace lagrange::python;
48 using MeshType = SurfaceMesh<Scalar, Index>;
50 auto surface_mesh_class = nb::class_<MeshType>(m,
"SurfaceMesh",
"Surface mesh data structure");
51 surface_mesh_class.def(nb::init<Index>(), nb::arg(
"dimension") = 3);
52 surface_mesh_class.def(
54 [](MeshType& self, Tensor<Scalar> b) {
55 auto [data, shape, stride] = tensor_to_span(b);
58 self.add_vertex(data);
61 R
"(Add a vertex to the mesh.
63:param vertex: vertex coordinates)");
66 surface_mesh_class.def(
68 [](MeshType& self, nb::list b) {
70 if (self.get_dimension() == 3) {
72 {nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1]), nb::cast<Scalar>(b[2])});
73 }
else if (self.get_dimension() == 2) {
74 self.add_vertex({nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1])});
76 throw std::runtime_error(
"Dimension mismatch in vertex tensor");
80 R
"(Add a vertex to the mesh.
82:param vertex: vertex coordinates as a list)");
84 surface_mesh_class.def(
86 [](MeshType& self, Tensor<Scalar> b) {
87 auto [data, shape, stride] = tensor_to_span(b);
90 self.add_vertices(
static_cast<Index
>(shape[0]), data);
93 R
"(Add multiple vertices to the mesh.
95:param vertices: N x D tensor of vertex coordinates, where N is the number of vertices and D is the dimension)");
98 surface_mesh_class.def(
100 &MeshType::add_triangle,
104 R
"(Add a triangle to the mesh.
106:param v0: first vertex index
107:param v1: second vertex index
108:param v2: third vertex index
110:returns: facet index of the added triangle)");
111 surface_mesh_class.def(
113 [](MeshType& self, Tensor<Index> b) {
114 auto [data, shape, stride] = tensor_to_span(b);
117 self.add_triangles(
static_cast<Index
>(shape[0]), data);
120 R
"(Add multiple triangles to the mesh.
122:param triangles: N x 3 tensor of vertex indices, where N is the number of triangles)");
123 surface_mesh_class.def(
130 R
"(Add a quad to the mesh.
132:param v0: first vertex index
133:param v1: second vertex index
134:param v2: third vertex index
135:param v3: fourth vertex index
137:returns: facet index of the added quad)");
138 surface_mesh_class.def(
140 [](MeshType& self, Tensor<Index> b) {
141 auto [data, shape, stride] = tensor_to_span(b);
144 self.add_quads(
static_cast<Index
>(shape[0]), data);
147 R
"(Add multiple quads to the mesh.
149:param quads: N x 4 tensor of vertex indices, where N is the number of quads)");
150 surface_mesh_class.def(
152 [](MeshType& self, Tensor<Index> b) {
153 auto [data, shape, stride] = tensor_to_span(b);
156 self.add_polygon(data);
159 R
"(Add a polygon to the mesh.
161:param vertices: 1D tensor of vertex indices defining the polygon
163:returns: facet index of the added polygon)");
164 surface_mesh_class.def(
166 [](MeshType& self, Tensor<Index> b) {
167 auto [data, shape, stride] = tensor_to_span(b);
169 self.add_polygons(
static_cast<Index
>(shape[0]),
static_cast<Index
>(shape[1]), data);
172 R
"(Add multiple regular polygons to the mesh.
174:param polygons: N x K tensor of vertex indices, where N is the number of polygons and K is the number of vertices per polygon)");
175 surface_mesh_class.def(
177 [](MeshType& self, Tensor<Index> sizes, Tensor<Index> indices) {
178 auto [size_data, size_shape, size_stride] = tensor_to_span(sizes);
182 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
186 self.add_hybrid(size_data, index_data);
190 R
"(Add hybrid facets (polygons with varying number of vertices) to the mesh.
192:param sizes: 1D tensor specifying the number of vertices for each facet
193:param indices: 1D tensor of vertex indices for all facets concatenated together)");
194 surface_mesh_class.def(
196 [](MeshType& self, Tensor<Index> b) {
197 auto [data, shape, stride] = tensor_to_span(b);
200 self.remove_vertices(data);
203 R
"(Remove selected vertices from the mesh.
205:param vertices: 1D tensor of vertex indices to remove)");
206 surface_mesh_class.def(
208 [](MeshType& self, nb::list b) {
209 std::vector<Index> indices;
211 indices.push_back(nb::cast<Index>(i));
213 self.remove_vertices(indices);
216 R
"(Remove selected vertices from the mesh.
218:param vertices: list of vertex indices to remove)");
219 surface_mesh_class.def(
221 [](MeshType& self, Tensor<Index> b) {
222 auto [data, shape, stride] = tensor_to_span(b);
225 self.remove_facets(data);
228 R
"(Remove selected facets from the mesh.
230:param facets: 1D tensor of facet indices to remove)");
231 surface_mesh_class.def(
233 [](MeshType& self, nb::list b) {
234 std::vector<Index> indices;
236 indices.push_back(nb::cast<Index>(i));
238 self.remove_facets(indices);
241 R
"(Remove selected facets from the mesh.
243:param facets: list of facet indices to remove)");
244 surface_mesh_class.def(
247 auto [data, shape, stride] = tensor_to_span(b);
250 self.flip_facets(data, policy);
254 R
"(Flip the orientation of selected facets.
256:param facets: 1D tensor of facet indices to flip
257:param policy: Whether to reorient associated attributes like normals and bitangents.)");
258 surface_mesh_class.def(
261 std::vector<Index> indices;
263 indices.push_back(nb::cast<Index>(i));
265 self.flip_facets(indices, policy);
269 R
"(Flip the orientation of selected facets.
271:param facets: list of facet indices to flip
272:param policy: Whether to reorient associated attributes like normals and bitangents.)");
273 surface_mesh_class.def(
275 &MeshType::clear_vertices,
276 R
"(Remove all vertices from the mesh.)");
277 surface_mesh_class.def(
279 &MeshType::clear_facets,
280 R
"(Remove all facets from the mesh.)");
281 surface_mesh_class.def(
283 &MeshType::shrink_to_fit,
284 R
"(Shrink the internal storage to fit the current mesh size.)");
285 surface_mesh_class.def(
286 "compress_if_regular",
287 &MeshType::compress_if_regular,
288 R
"(Compress the mesh representation if it is regular (all facets have the same number of vertices).
290:returns: True if the mesh was compressed, False otherwise)");
291 surface_mesh_class.def_prop_ro("is_triangle_mesh", &MeshType::is_triangle_mesh);
292 surface_mesh_class.def_prop_ro(
"is_quad_mesh", &MeshType::is_quad_mesh);
293 surface_mesh_class.def_prop_ro(
"is_regular", &MeshType::is_regular);
294 surface_mesh_class.def_prop_ro(
"is_hybrid", &MeshType::is_hybrid);
295 surface_mesh_class.def_prop_ro(
"dimension", &MeshType::get_dimension);
296 surface_mesh_class.def_prop_ro(
"vertex_per_facet", &MeshType::get_vertex_per_facet);
297 surface_mesh_class.def_prop_ro(
"num_vertices", &MeshType::get_num_vertices);
298 surface_mesh_class.def_prop_ro(
"num_facets", &MeshType::get_num_facets);
299 surface_mesh_class.def_prop_ro(
"num_corners", &MeshType::get_num_corners);
301 surface_mesh_class.def(
303 [](MeshType& self, Index i) {
304 return span_to_tensor(self.get_position(i), nb::find(&self));
307 R
"(Get the position of a vertex.
309:param vertex_id: vertex index
311:returns: position coordinates as a tensor)");
312 surface_mesh_class.def(
314 [](MeshType& self, Index i) {
315 return span_to_tensor(self.ref_position(i), nb::find(&self));
318 R
"(Get a mutable reference to the position of a vertex.
320:param vertex_id: vertex index
322:returns: mutable position coordinates as a tensor)");
323 surface_mesh_class.def(
325 &MeshType::get_facet_size,
327 R
"(Get the number of vertices in a facet.
329:param facet_id: facet index
331:returns: number of vertices in the facet)");
332 surface_mesh_class.def(
334 &MeshType::get_facet_vertex,
337 R
"(Get a vertex index from a facet.
339:param facet_id: facet index
340:param local_vertex_id: local vertex index within the facet (0 to facet_size-1)
342:returns: global vertex index)");
343 surface_mesh_class.def(
344 "get_facet_corner_begin",
345 &MeshType::get_facet_corner_begin,
347 R
"(Get the first corner index of a facet.
349:param facet_id: facet index
351:returns: first corner index of the facet)");
352 surface_mesh_class.def(
353 "get_facet_corner_end",
354 &MeshType::get_facet_corner_end,
356 R
"(Get the end corner index of a facet (one past the last corner).
358:param facet_id: facet index
360:returns: end corner index of the facet)");
361 surface_mesh_class.def(
363 &MeshType::get_corner_vertex,
365 R
"(Get the vertex index associated with a corner.
367:param corner_id: corner index
369:returns: vertex index)");
370 surface_mesh_class.def(
372 &MeshType::get_corner_facet,
374 R
"(Get the facet index associated with a corner.
376:param corner_id: corner index
378:returns: facet index)");
379 surface_mesh_class.def(
380 "get_facet_vertices",
381 [](MeshType& self, Index f) {
382 return span_to_tensor(self.get_facet_vertices(f), nb::find(&self));
385 R
"(Get all vertex indices of a facet.
387:param facet_id: facet index
389:returns: vertex indices as a tensor)");
390 surface_mesh_class.def(
391 "ref_facet_vertices",
392 [](MeshType& self, Index f) {
393 return span_to_tensor(self.ref_facet_vertices(f), nb::find(&self));
396 R
"(Get a mutable reference to all vertex indices of a facet.
398:param facet_id: facet index
400:returns: mutable vertex indices as a tensor)");
401 surface_mesh_class.def(
403 &MeshType::get_attribute_id,
405 R
"(Get the attribute ID by name.
407:param name: attribute name
409:returns: attribute ID)");
410 surface_mesh_class.def(
411 "get_attribute_name",
412 &MeshType::get_attribute_name,
414 R
"(Get the attribute name by ID.
416:param id: attribute ID
418:returns: attribute name)");
419 surface_mesh_class.def(
422 std::string_view name,
423 std::variant<std::monostate, AttributeElement, std::string_view> element,
424 std::variant<std::monostate, AttributeUsage, std::string_view> usage,
425 std::variant<std::monostate, GenericTensor, nb::list> initial_values,
426 std::variant<std::monostate, Tensor<Index>, GenericTensor, nb::list> initial_indices,
427 std::optional<Index> num_channels,
428 std::optional<nb::type_object> dtype) {
429 const bool with_initial_values = initial_values.index() != 0;
430 const bool with_initial_indices = initial_indices.index() != 0;
434 if (num_channels.has_value()) {
435 n = num_channels.value();
436 }
else if (with_initial_values) {
437 if (initial_values.index() == 1) {
438 const auto& values = std::get<GenericTensor>(initial_values);
440 values.ndim() == 1 || values.ndim() == 2,
441 "Only vector or matrix are accepted as initial values.");
442 n = values.ndim() == 1 ? 1 :
static_cast<Index
>(values.shape(1));
443 }
else if (initial_values.index() == 2) {
447 throw nb::type_error(
"Either number of channels or initial values are required!");
454 using T = std::decay_t<
decltype(value)>;
455 if constexpr (std::is_same_v<T, AttributeElement>) {
457 }
else if (with_initial_indices) {
459 }
else if constexpr (std::is_same_v<T, std::string_view>) {
460 if (value ==
"Vertex") {
462 }
else if (value ==
"Facet") {
464 }
else if (value ==
"Edge") {
466 }
else if (value ==
"Corner") {
468 }
else if (value ==
"Value") {
470 }
else if (value ==
"Indexed") {
473 throw nb::type_error(
"Invalid element type!");
475 }
else if (with_initial_values) {
477 const Index num_vertices = self.get_num_vertices();
478 const Index num_facets = self.get_num_facets();
479 const Index num_corners = self.get_num_corners();
480 const Index num_edges =
484 if (initial_values.index() == 1) {
485 const auto& values = std::get<GenericTensor>(initial_values);
486 num_rows =
static_cast<Index
>(values.shape(0));
487 }
else if (initial_values.index() == 2) {
488 const auto& values = std::get<nb::list>(initial_values);
489 num_rows =
static_cast<Index
>(nb::len(values));
493 if (num_rows == num_vertices) {
495 num_rows != num_facets,
496 "Cannot infer attribute element due to ambiguity: vertices vs "
499 num_rows != num_edges,
500 "Cannot infer attribute element due to ambiguity: vertices vs "
503 num_rows != num_corners,
504 "Cannot infer attribute element due to ambiguity: vertices vs "
507 }
else if (num_rows == num_facets) {
509 num_rows != num_edges,
510 "Cannot infer attribute element due to ambiguity: facets vs "
513 num_rows != num_corners,
514 "Cannot infer attribute element due to ambiguity: facets vs "
517 }
else if (num_rows == num_corners) {
519 num_rows != num_edges,
520 "Cannot infer attribute element due to ambiguity: corners vs "
523 }
else if (num_rows == num_edges) {
526 throw nb::type_error(
527 "Cannot infer attribute element type from initial_values!");
530 throw nb::type_error(
"Invalid element type!");
539 using T = std::decay_t<
decltype(value)>;
540 if constexpr (std::is_same_v<T, AttributeUsage>) {
542 }
else if constexpr (std::is_same_v<T, std::string_view>) {
543 if (value ==
"Vector") {
545 }
else if (value ==
"Scalar") {
547 }
else if (value ==
"Position") {
549 }
else if (value ==
"Normal") {
551 }
else if (value ==
"Tangent") {
553 }
else if (value ==
"Bitangent") {
555 }
else if (value ==
"Color") {
557 }
else if (value ==
"UV") {
559 }
else if (value ==
"VertexIndex") {
561 }
else if (value ==
"FacetIndex") {
563 }
else if (value ==
"CornerIndex") {
565 }
else if (value ==
"EdgeIndex") {
568 throw nb::type_error(
"Invalid usage type!");
580 auto create_attribute = [&](
auto values) {
581 using ValueType =
typename std::decay_t<
decltype(values)>::element_type;
585 std::vector<Index> index_storage;
588 if (with_initial_values) {
589 init_values = values;
593 num_channels.has_value(),
594 "Number of channels is required when initial values are not provided!");
597 "dtype is required when initial values are not provided!");
601 if (
const Tensor<Index>* tensor_ptr =
602 std::get_if<Tensor<Index>>(&initial_indices)) {
604 const auto& indices = *tensor_ptr;
605 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
607 init_indices = index_data;
609 GenericTensor* generic_tensor_ptr =
610 std::get_if<GenericTensor>(&initial_indices)) {
612 auto& indices = *generic_tensor_ptr;
613 index_storage.resize(indices.size());
615#define LA_X_create_attribute_index(_, IndexType) \
616 if (indices.dtype() == nb::dtype<IndexType>()) { \
617 auto view = indices.template view<IndexType, nb::ndim<1>>(); \
618 std::copy(view.data(), view.data() + indices.size(), index_storage.begin()); \
621#undef LA_X_create_attribute_index
622 init_indices =
span<Index>(index_storage.data(), index_storage.size());
623 }
else if (
const nb::list* list_ptr = std::get_if<nb::list>(&initial_indices)) {
625 const nb::list& py_list = *list_ptr;
626 index_storage = nb::cast<std::vector<Index>>(py_list);
627 init_indices =
span<Index>(index_storage.begin(), index_storage.size());
630 return self.template create_attribute<ValueType>(
640 if (
const GenericTensor* tensor_ptr = std::get_if<GenericTensor>(&initial_values)) {
641 const auto& values = *tensor_ptr;
642#define LA_X_create_attribute(_, ValueType) \
643 if (values.dtype() == nb::dtype<ValueType>()) { \
644 Tensor<ValueType> local_values(values.handle()); \
645 auto [value_data, value_shape, value_stride] = tensor_to_span(local_values); \
646 la_runtime_assert(is_dense(value_shape, value_stride)); \
647 if (num_channels.has_value()) { \
648 Index nn = value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]); \
649 la_runtime_assert(nn == n, "Number of channels does not match initial_values"); \
651 return create_attribute(value_data); \
654#undef LA_X_create_attribute
655 }
else if (
const nb::list* list_ptr = std::get_if<nb::list>(&initial_values)) {
656 auto values = nb::cast<std::vector<double>>(*list_ptr);
657 return create_attribute(
span<double>(values.data(), values.size()));
658 }
else if (dtype.has_value()) {
659 const auto& t = dtype.value();
660 auto np = nb::module_::import_(
"numpy");
661 if (t.is(&PyFloat_Type)) {
664 return create_attribute(local_values);
665 }
else if (t.is(np.attr(
"float32"))) {
667 return create_attribute(local_values);
668 }
else if (t.is(np.attr(
"float64"))) {
670 return create_attribute(local_values);
671 }
else if (t.is(np.attr(
"int8"))) {
673 return create_attribute(local_values);
674 }
else if (t.is(np.attr(
"int16"))) {
676 return create_attribute(local_values);
677 }
else if (t.is(np.attr(
"int32"))) {
679 return create_attribute(local_values);
680 }
else if (t.is(np.attr(
"int64"))) {
682 return create_attribute(local_values);
683 }
else if (t.is(np.attr(
"uint8"))) {
685 return create_attribute(local_values);
686 }
else if (t.is(np.attr(
"uint16"))) {
688 return create_attribute(local_values);
689 }
else if (t.is(np.attr(
"uint32"))) {
691 return create_attribute(local_values);
692 }
else if (t.is(np.attr(
"uint64"))) {
694 return create_attribute(local_values);
697 throw nb::type_error(
"`initial_values` and `dtype` cannot both be None!");
700 "element"_a = nb::none(),
701 "usage"_a = nb::none(),
702 "initial_values"_a = nb::none(),
703 "initial_indices"_a = nb::none(),
704 "num_channels"_a = nb::none(),
705 "dtype"_a = nb::none(),
707 "def create_attribute(self, "
709 "element: typing.Union[AttributeElement, "
711 "'Vertex', 'Facet', 'Edge', 'Corner', 'Value', 'Indexed'"
713 "usage: typing.Union[AttributeUsage, "
715 "'Vector', 'Scalar', 'Position', 'Normal', 'Tangent', 'Bitangent', 'Color', 'UV', "
716 "'VertexIndex', 'FacetIndex', 'CornerIndex', 'EdgeIndex'"
718 "initial_values: typing.Union[numpy.typing.NDArray, typing.List[float], None] = None, "
719 "initial_indices: typing.Union[numpy.typing.NDArray, typing.List[int], None] = None, "
720 "num_channels: typing.Optional[int] = None, "
721 "dtype: typing.Optional[numpy.typing.DTypeLike] = None) -> int"),
722 R
"(Create an attribute.
724:param name: Name of the attribute.
725:param element: Element type of the attribute. If None, derive from the shape of initial values.
726:param usage: Usage type of the attribute. If None, derive from the shape of initial values or the number of channels.
727:param initial_values: Initial values of the attribute.
728:param initial_indices: Initial indices of the attribute (Indexed attribute only).
729:param num_channels: Number of channels of the attribute.
730:param dtype: Data type of the attribute.
732:returns: The id of the created attribute.
735 If `element` is None, it will be derived based on the cardinality of the mesh elements.
736 If there is an ambiguity, an exception will be raised.
737 In addition, explicit `element` specification is required for value attributes.
740 If `usage` is None, it will be derived based on the shape of `initial_values` or `num_channels` if specified.)");
742 surface_mesh_class.def(
743 "create_attribute_from",
744 &MeshType::template create_attribute_from<Scalar, Index>,
747 "source_name"_a =
"",
748 R
"(Shallow copy an attribute from another mesh.
750:param name: Name of the attribute.
751:param source_mesh: Source mesh.
752:param source_name: Name of the attribute in the source mesh. If empty, use the same name as `name`.
754:returns: The id of the created attribute.)");
756 surface_mesh_class.def(
759 std::string_view name,
762 GenericTensor values) {
763 auto wrap_as_attribute = [&](
auto tensor) {
764 using ValueType =
typename std::decay_t<
decltype(tensor)>
::Scalar;
765 auto [data, shape, stride] = tensor_to_span(tensor);
767 Index num_channels = shape.size() == 1 ? 1 :
static_cast<Index
>(shape[1]);
769 auto owner = std::make_shared<nb::object>(nb::find(values));
770 if constexpr (std::is_const_v<ValueType>) {
771 id = self.wrap_as_const_attribute(
778 id = self.wrap_as_attribute(
785 auto& attr = self.template ref_attribute<ValueType>(
id);
790#define LA_X_wrap_as_attribute(_, ValueType) \
791 if (values.dtype() == nb::dtype<ValueType>()) { \
792 Tensor<ValueType> local_values(values.handle()); \
793 return wrap_as_attribute(local_values); \
796#undef LA_X_wrap_as_attribute
797 throw nb::type_error(
"Unsupported value type!");
803 R
"(Wrap an existing numpy array as an attribute.
805:param name: Name of the attribute.
806:param element: Element type of the attribute.
807:param usage: Usage type of the attribute.
808:param values: Values of the attribute.
810:returns: The id of the created attribute.)");
811 surface_mesh_class.def(
812 "wrap_as_indexed_attribute",
814 std::string_view name,
816 GenericTensor values,
817 Tensor<Index> indices) {
818 auto wrap_as_indexed_attribute = [&](
auto value_tensor,
auto index_tensor) {
819 using ValueType =
typename std::decay_t<
decltype(value_tensor)>
::Scalar;
820 auto [value_data, value_shape, value_stride] = tensor_to_span(value_tensor);
821 auto [index_data, index_shape, index_stride] = tensor_to_span(index_tensor);
824 Index num_values =
static_cast<Index
>(value_shape[0]);
826 value_shape.size() == 1 ? 1 :
static_cast<Index
>(value_shape[1]);
829 auto value_owner = std::make_shared<nb::object>(nb::find(values));
830 auto index_owner = std::make_shared<nb::object>(nb::find(indices));
832 if constexpr (std::is_const_v<ValueType>) {
833 id = self.wrap_as_const_indexed_attribute(
841 id = self.wrap_as_indexed_attribute(
849 auto& attr = self.template ref_indexed_attribute<ValueType>(
id);
855#define LA_X_wrap_as_indexed_attribute(_, ValueType) \
856 if (values.dtype() == nb::dtype<ValueType>()) { \
857 Tensor<ValueType> local_values(values.handle()); \
858 return wrap_as_indexed_attribute(local_values, indices); \
861#undef LA_X_wrap_as_indexed_attribute
862 throw nb::type_error(
"Unsupported value type!");
868 R
"(Wrap an existing numpy array as an indexed attribute.
870:param name: Name of the attribute.
871:param usage: Usage type of the attribute.
872:param values: Values of the attribute.
873:param indices: Indices of the attribute.
875:returns: The id of the created attribute.)");
876 surface_mesh_class.def(
877 "duplicate_attribute",
878 &MeshType::duplicate_attribute,
881 R
"(Duplicate an attribute with a new name.
883:param old_name: name of the attribute to duplicate
884:param new_name: name for the new attribute
886:returns: attribute ID of the duplicated attribute)");
887 surface_mesh_class.def(
889 &MeshType::rename_attribute,
892 R
"(Rename an attribute.
894:param old_name: current name of the attribute
895:param new_name: new name for the attribute)");
897 surface_mesh_class.def(
900 self.delete_attribute(name, policy);
904 R
"(Delete an attribute by name.
906:param name: Name of the attribute.
907:param policy: Deletion policy for reserved attributes.)");
908 surface_mesh_class.def(
911 self.delete_attribute(
id, policy);
915 R
"(Delete an attribute by id.
917:param id: Id of the attribute.
918:param policy: Deletion policy for reserved attributes.)");
919 surface_mesh_class.def(
921 &MeshType::has_attribute,
923 R
"(Check if an attribute exists.
925:param name: attribute name
927:returns: True if the attribute exists, False otherwise)");
928 surface_mesh_class.def(
929 "is_attribute_indexed",
930 static_cast<bool (MeshType::*)(
AttributeId) const
>(&MeshType::is_attribute_indexed),
932 R
"(Check if an attribute is indexed.
934:param id: attribute ID
936:returns: True if the attribute is indexed, False otherwise)");
937 surface_mesh_class.def(
938 "is_attribute_indexed",
939 static_cast<bool (MeshType::*)(std::string_view) const
>(&MeshType::is_attribute_indexed),
941 R
"(Check if an attribute is indexed.
943:param name: attribute name
945:returns: True if the attribute is indexed, False otherwise)");
948 auto ensure_attribute_is_not_shared = [](MeshType& mesh,
AttributeId id) {
949 auto& attr_base = mesh.get_attribute_base(
id);
950#define LA_X_trigger_copy_on_write(_, ValueType) \
951 if (mesh.is_attribute_indexed(id)) { \
952 if (dynamic_cast<const IndexedAttribute<ValueType, Index>*>(&attr_base)) { \
953 [[maybe_unused]] auto& attr = mesh.template ref_indexed_attribute<ValueType>(id); \
956 if (dynamic_cast<const Attribute<ValueType>*>(&attr_base)) { \
957 [[maybe_unused]] auto& attr = mesh.template ref_attribute<ValueType>(id); \
961#undef LA_X_trigger_copy_on_write
964 surface_mesh_class.def(
966 [&](MeshType& self,
AttributeId id,
bool sharing) {
968 !self.is_attribute_indexed(
id),
970 "Attribute {} is indexed! Please use `indexed_attribute` property "
973 if (!sharing) ensure_attribute_is_not_shared(self,
id);
978 R
"(Get an attribute by id.
980:param id: Id of the attribute.
981:param sharing: Whether to allow sharing the attribute with other meshes.
983:returns: The attribute.)");
984 surface_mesh_class.def(
986 [&](MeshType& self, std::string_view name,
bool sharing) {
988 !self.is_attribute_indexed(name),
990 "Attribute \"{}\" is indexed! Please use `indexed_attribute` property "
993 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
998 R
"(Get an attribute by name.
1000:param name: Name of the attribute.
1001:param sharing: Whether to allow sharing the attribute with other meshes.
1003:return: The attribute.)");
1004 surface_mesh_class.def(
1005 "indexed_attribute",
1006 [&](MeshType& self,
AttributeId id,
bool sharing) {
1008 self.is_attribute_indexed(
id),
1010 "Attribute {} is not indexed! Please use `attribute` property instead.",
1012 if (!sharing) ensure_attribute_is_not_shared(self,
id);
1017 R
"(Get an indexed attribute by id.
1019:param id: Id of the attribute.
1020:param sharing: Whether to allow sharing the attribute with other meshes.
1022:returns: The indexed attribute.)");
1023 surface_mesh_class.def(
1024 "indexed_attribute",
1025 [&](MeshType& self, std::string_view name,
bool sharing) {
1027 self.is_attribute_indexed(name),
1029 "Attribute \"{}\" is not indexed! Please use `attribute` property instead.",
1031 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
1036 R
"(Get an indexed attribute by name.
1038:param name: Name of the attribute.
1039:param sharing: Whether to allow sharing the attribute with other meshes.
1041:returns: The indexed attribute.)");
1042 surface_mesh_class.def(
1043 "__attribute_ref_count__",
1045 auto ptr = self._get_attribute_ptr(
id);
1046 return ptr.use_count();
1049 R
"(Get the reference count of an attribute (for debugging purposes).
1051:param id: attribute ID
1053:returns: reference count of the attribute)");
1054 surface_mesh_class.def_prop_rw(
1056 [](
const MeshType& self) {
1057 const auto& attr = self.get_vertex_to_position();
1058 return attribute_to_tensor(attr, nb::find(&self));
1060 [](MeshType& self, Tensor<Scalar> tensor) {
1061 auto [values, shape, stride] = tensor_to_span(tensor);
1065 size_t num_vertices = shape.size() == 1 ? 1 : shape[0];
1066 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1067 auto id = self.wrap_as_vertices(
1069 static_cast<Index
>(num_vertices));
1070 auto& attr = self.template ref_attribute<Scalar>(
id);
1073 "Vertices of the mesh.");
1074 surface_mesh_class.def_prop_rw(
1076 [](
const MeshType& self) {
1077 if (self.is_regular()) {
1078 const auto& attr = self.get_corner_to_vertex();
1079 const size_t shape[2] = {
1080 static_cast<size_t>(self.get_num_facets()),
1081 static_cast<size_t>(self.get_vertex_per_facet())};
1082 return attribute_to_tensor(attr, shape, nb::find(&self));
1084 logger().warn(
"Mesh is not regular, returning the flattened facets.");
1085 const auto& attr = self.get_corner_to_vertex();
1086 return attribute_to_tensor(attr, nb::find(&self));
1089 [](MeshType& self, Tensor<Index> tensor) {
1090 auto [values, shape, stride] = tensor_to_span(tensor);
1093 const size_t num_facets = shape.size() == 1 ? 1 : shape[0];
1094 const size_t vertex_per_facet = shape.size() == 1 ? shape[0] : shape[1];
1095 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1096 auto id = self.wrap_as_facets(
1098 static_cast<Index
>(num_facets),
1099 static_cast<Index
>(vertex_per_facet));
1100 auto& attr = self.template ref_attribute<Index>(
id);
1103 "Facets of the mesh.");
1104 surface_mesh_class.def_prop_ro(
1106 [](MeshType& self) {
1107 self.initialize_edges();
1109 std::vector<Index> data(num_edges * 2);
1110 tbb::parallel_for(Index{0}, num_edges, [&](Index i) {
1113 data[i * 2 + 1] = v1;
1115 nb::ndarray<Index, nb::shape<-1, 2>, nb::numpy, nb::c_contig, nb::device::cpu> edges(
1117 {static_cast<size_t>(num_edges), 2});
1118 return edges.cast();
1120 "Edges of the mesh.");
1121 surface_mesh_class.def(
1123 [](MeshType& self, Tensor<Scalar> tensor, Index num_vertices) {
1124 auto [values, shape, stride] = tensor_to_span(tensor);
1128 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1129 auto id = self.wrap_as_vertices(
1132 auto& attr = self.template ref_attribute<Scalar>(
id);
1138 R
"(Wrap a tensor as vertices.
1140:param tensor: The tensor to wrap.
1141:param num_vertices: Number of vertices.
1143:return: The id of the wrapped vertices attribute.)");
1144 surface_mesh_class.def(
1146 [](MeshType& self, Tensor<Index> tensor, Index num_facets, Index vertex_per_facet) {
1147 auto [values, shape, stride] = tensor_to_span(tensor);
1150 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1151 auto id = self.wrap_as_facets(
1155 auto& attr = self.template ref_attribute<Index>(
id);
1161 "vertex_per_facet"_a,
1162 R
"(Wrap a tensor as a list of regular facets.
1164:param tensor: The tensor to wrap.
1165:param num_facets: Number of facets.
1166:param vertex_per_facet: Number of vertices per facet.
1168:return: The id of the wrapped facet attribute.)");
1169 surface_mesh_class.def(
1172 Tensor<Index> offsets,
1174 Tensor<Index> facets,
1175 Index num_corners) {
1176 auto [offsets_data, offsets_shape, offsets_stride] = tensor_to_span(offsets);
1177 auto [facets_data, facets_shape, facets_stride] = tensor_to_span(facets);
1181 auto offsets_owner = std::make_shared<nb::object>(nb::find(offsets));
1182 auto facets_owner = std::make_shared<nb::object>(nb::find(facets));
1184 auto id = self.wrap_as_facets(
1189 auto& attr = self.template ref_attribute<Index>(
id);
1197 R
"(Wrap a tensor as a list of hybrid facets.
1199:param offsets: The offset indices into the facets array.
1200:param num_facets: Number of facets.
1201:param facets: The indices of the vertices of the facets.
1202:param num_corners: Number of corners.
1204:return: The id of the wrapped facet attribute.)");
1205 surface_mesh_class.def_static(
1206 "attr_name_is_reserved",
1207 &MeshType::attr_name_is_reserved,
1209 R
"(Check if an attribute name is reserved.
1211:param name: attribute name to check
1213:returns: True if the name is reserved, False otherwise)");
1214 surface_mesh_class.def_prop_ro_static(
1215 "attr_name_vertex_to_position",
1216 [](nb::handle) {
return MeshType::attr_name_vertex_to_position(); },
1217 "The name of the attribute that stores the vertex positions.");
1218 surface_mesh_class.def_prop_ro_static(
1219 "attr_name_corner_to_vertex",
1220 [](nb::handle) {
return MeshType::attr_name_corner_to_vertex(); },
1221 "The name of the attribute that stores the corner to vertex mapping.");
1222 surface_mesh_class.def_prop_ro_static(
1223 "attr_name_facet_to_first_corner",
1224 [](nb::handle) {
return MeshType::attr_name_facet_to_first_corner(); },
1225 "The name of the attribute that stores the facet to first corner mapping.");
1226 surface_mesh_class.def_prop_ro_static(
1227 "attr_name_corner_to_facet",
1228 [](nb::handle) {
return MeshType::attr_name_corner_to_facet(); },
1229 "The name of the attribute that stores the corner to facet mapping.");
1230 surface_mesh_class.def_prop_ro_static(
1231 "attr_name_corner_to_edge",
1232 [](nb::handle) {
return MeshType::attr_name_corner_to_edge(); },
1233 "The name of the attribute that stores the corner to edge mapping.");
1234 surface_mesh_class.def_prop_ro_static(
1235 "attr_name_edge_to_first_corner",
1236 [](nb::handle) {
return MeshType::attr_name_edge_to_first_corner(); },
1237 "The name of the attribute that stores the edge to first corner mapping.");
1238 surface_mesh_class.def_prop_ro_static(
1239 "attr_name_next_corner_around_edge",
1240 [](nb::handle) {
return MeshType::attr_name_next_corner_around_edge(); },
1241 "The name of the attribute that stores the next corner around edge mapping.");
1242 surface_mesh_class.def_prop_ro_static(
1243 "attr_name_vertex_to_first_corner",
1244 [](nb::handle) {
return MeshType::attr_name_vertex_to_first_corner(); },
1245 "The name of the attribute that stores the vertex to first corner mapping.");
1246 surface_mesh_class.def_prop_ro_static(
1247 "attr_name_next_corner_around_vertex",
1248 [](nb::handle) {
return MeshType::attr_name_next_corner_around_vertex(); },
1249 "The name of the attribute that stores the next corner around vertex mapping.");
1250 surface_mesh_class.def_prop_ro(
1251 "attr_id_vertex_to_position",
1252 &MeshType::attr_id_vertex_to_position);
1253 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_vertex", &MeshType::attr_id_corner_to_vertex);
1254 surface_mesh_class.def_prop_ro(
1255 "attr_id_facet_to_first_corner",
1256 &MeshType::attr_id_facet_to_first_corner);
1257 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_facet", &MeshType::attr_id_corner_to_facet);
1258 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_edge", &MeshType::attr_id_corner_to_edge);
1259 surface_mesh_class.def_prop_ro(
1260 "attr_id_edge_to_first_corner",
1261 &MeshType::attr_id_edge_to_first_corner);
1262 surface_mesh_class.def_prop_ro(
1263 "attr_id_next_corner_around_edge",
1264 &MeshType::attr_id_next_corner_around_edge);
1265 surface_mesh_class.def_prop_ro(
1266 "attr_id_vertex_to_first_corner",
1267 &MeshType::attr_id_vertex_to_first_corner);
1268 surface_mesh_class.def_prop_ro(
1269 "attr_id_next_corner_around_vertex",
1270 &MeshType::attr_id_next_corner_around_vertex);
1271 surface_mesh_class.def(
1273 [](MeshType& self, std::optional<Tensor<Index>> tensor) {
1274 if (tensor.has_value()) {
1275 auto [edge_data, edge_shape, edge_stride] = tensor_to_span(tensor.value());
1279 "Edge tensor must be of the shape num_edges x 2");
1280 self.initialize_edges(edge_data);
1282 self.initialize_edges();
1285 "edges"_a = nb::none(),
1286 R
"(Initialize the edges.
1288The `edges` tensor provides a predefined ordering of the edges.
1289If not provided, the edges are initialized in an arbitrary order.
1291:param edges: M x 2 tensor of predefined edge vertex indices, where M is the number of edges.)");
1292 surface_mesh_class.def(
1294 &MeshType::clear_edges,
1295 R
"(Clear all edge connectivity information.)");
1296 surface_mesh_class.def_prop_ro("has_edges", &MeshType::has_edges);
1297 surface_mesh_class.def(
1302 R
"(Get the edge index associated with a local vertex of a facet.
1304:param facet_id: facet index
1305:param lv: local vertex index of the facet
1307:returns: edge index)");
1308 surface_mesh_class.def(
1310 &MeshType::get_corner_edge,
1312 R
"(Get the edge index associated with a corner.
1314:param corner_id: corner index
1316:returns: edge index)");
1317 surface_mesh_class.def(
1318 "get_edge_vertices",
1321 R
"(Get the two vertex indices of an edge.
1323:param edge_id: edge index
1325:returns: tuple of (vertex1_id, vertex2_id))");
1326 surface_mesh_class.def(
1327 "find_edge_from_vertices",
1331 R
"(Find the edge connecting two vertices.
1333:param vertex1_id: first vertex index
1334:param vertex2_id: second vertex index
1336:returns: edge index, or invalid_index if no such edge exists)");
1337 surface_mesh_class.def(
1338 "get_first_corner_around_edge",
1339 &MeshType::get_first_corner_around_edge,
1341 R
"(Get the first corner around an edge.
1343:param edge_id: edge index
1345:returns: first corner index around the edge)");
1346 surface_mesh_class.def(
1347 "get_next_corner_around_edge",
1348 &MeshType::get_next_corner_around_edge,
1350 R
"(Get the next corner around the same edge.
1352:param corner_id: current corner index
1354:returns: next corner index around the same edge)");
1355 surface_mesh_class.def(
1356 "get_first_corner_around_vertex",
1357 &MeshType::get_first_corner_around_vertex,
1359 R
"(Get the first corner around a vertex.
1361:param vertex_id: vertex index
1363:returns: first corner index around the vertex)");
1364 surface_mesh_class.def(
1365 "get_next_corner_around_vertex",
1366 &MeshType::get_next_corner_around_vertex,
1368 R
"(Get the next corner around the same vertex.
1370:param corner_id: current corner index
1372:returns: next corner index around the same vertex)");
1373 surface_mesh_class.def(
1374 "count_num_corners_around_edge",
1375 &MeshType::count_num_corners_around_edge,
1377 R
"(Count the number of corners around an edge.
1379:param edge_id: edge index
1381:returns: number of corners around the edge)");
1382 surface_mesh_class.def(
1383 "count_num_corners_around_vertex",
1384 &MeshType::count_num_corners_around_vertex,
1386 R
"(Count the number of corners around a vertex.
1388:param vertex_id: vertex index
1390:returns: number of corners around the vertex)");
1391 surface_mesh_class.def(
1392 "get_counterclockwise_corner_around_vertex",
1393 &MeshType::get_counterclockwise_corner_around_vertex,
1395 R
"(Get the counterclockwise corner around the vertex associated with the input corner.
1398 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1399 corners based on edge-connectivity) will be visited.
1401 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1402 is not well defined. It will return `invalid_index` in this case.
1404:param corner: The input corner index.
1406:returns: The counterclockwise corner index or `invalid_index` if none exists.)");
1407 surface_mesh_class.def(
1408 "get_clockwise_corner_around_vertex",
1409 &MeshType::get_clockwise_corner_around_vertex,
1411 R
"(Get the clockwise corner around the vertex associated with the input corner.
1414 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1415 corners based on edge-connectivity) will be visited.
1417 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1418 is not well defined. It will return `invalid_index` in this case.
1420:param corner: The input corner index.
1422:returns: The clockwise corner index or `invalid_index` if none exists.)");
1423 surface_mesh_class.def(
1424 "get_one_facet_around_edge",
1427 R
"(Get one facet adjacent to an edge.
1429:param edge_id: edge index
1431:returns: facet index adjacent to the edge)");
1432 surface_mesh_class.def(
1433 "get_one_corner_around_edge",
1436 R
"(Get one corner around an edge.
1438:param edge_id: edge index
1440:returns: corner index around the edge)");
1441 surface_mesh_class.def(
1442 "get_one_corner_around_vertex",
1445 R
"(Get one corner around a vertex.
1447:param vertex_id: vertex index
1449:returns: corner index around the vertex)");
1450 surface_mesh_class.def(
1454 R
"(Check if an edge is on the boundary.
1456:param edge_id: edge index
1458:returns: True if the edge is on the boundary, False otherwise)");
1460 surface_mesh_class.def(
1461 "foreach_facet_around_edge",
1462 [](MeshType& self, Index edge_id, std::function<
void(Index)>& func) {
1463 self.foreach_facet_around_edge(edge_id, func);
1467 R
"(Iterate over all facets around an edge.
1469:param edge_id: edge index
1470:param func: function to call for each facet index
1472.. code-block:: python
1474 mesh.foreach_facet_around_edge(eid, lambda fid: print(fid))
1477 surface_mesh_class.def(
1478 "foreach_facet_around_vertex",
1479 [](MeshType& self, Index vertex_id, std::function<
void(Index)>& func) {
1480 self.foreach_facet_around_vertex(vertex_id, func);
1484 R
"(Iterate over all facets around a vertex.
1486:param vertex_id: vertex index
1487:param func: function to call for each facet index
1489.. code-block:: python
1491 mesh.foreach_facet_around_vertex(vid, lambda fid: print(fid))
1494 surface_mesh_class.def(
1495 "foreach_facet_around_facet",
1496 [](MeshType& self, Index facet_id, std::function<
void(Index)>& func) {
1497 self.foreach_facet_around_facet(facet_id, func);
1501 R
"(Iterate over all adjacent facets around a facet.
1503:param facet_id: facet index
1504:param func: function to call for each adjacent facet index
1506.. code-block:: python
1508 mesh.foreach_facet_around_facet(fid, lambda afid: print(afid))
1511 surface_mesh_class.def(
1512 "foreach_corner_around_edge",
1513 [](MeshType& self, Index edge_id, std::function<
void(Index)>& func) {
1514 self.foreach_corner_around_edge(edge_id, func);
1518 R
"(Iterate over all corners around an edge.
1520:param edge_id: edge index
1521:param func: function to call for each corner index
1523.. code-block:: python
1525 mesh.foreach_corner_around_edge(eid, lambda cid: print(cid))
1528 surface_mesh_class.def(
1529 "foreach_corner_around_vertex",
1530 [](MeshType& self, Index vertex_id, std::function<
void(Index)>& func) {
1531 self.foreach_corner_around_vertex(vertex_id, func);
1535 R
"(Iterate over all corners around a vertex.
1537:param vertex_id: vertex index
1538:param func: function to call for each corner index
1540.. code-block:: python
1542 mesh.foreach_corner_around_vertex(vid, lambda cid: print(cid))
1545 surface_mesh_class.def(
1546 "foreach_edge_around_vertex",
1547 [](MeshType& self, Index vertex_id, std::function<
void(Index)>& func) {
1548 self.foreach_edge_around_vertex_with_duplicates(vertex_id, func);
1552 R
"(Iterate over all edges around a vertex.
1555 Each incident edge will be visited once for each incident facet.
1556 Thus, manifold edge will be visited exactly twice,
1557 boundary edge will be visited exactly once,
1558 non-manifold edges will be visited more than twice.
1560:param vertex_id: vertex index
1561:param func: function to call for each edge index
1563.. code-block:: python
1565 mesh.foreach_edge_around_vertex(vid, lambda eid: print(eid))
1569 surface_mesh_class.def(
1570 "facets_around_facet",
1571 [](MeshType& self, Index facet_id) {
1572 std::vector<Index> result;
1573 self.foreach_facet_around_facet(facet_id, [&](Index fid) { result.push_back(fid); });
1577 R
"(Get all adjacent facets around a facet.
1579:param facet_id: facet index
1581:returns: list of adjacent facet indices
1583.. code-block:: python
1585 facets = mesh.facets_around_facet(fid)
1590 surface_mesh_class.def(
1591 "facets_around_vertex",
1592 [](MeshType& self, Index vertex_id) {
1593 std::vector<Index> result;
1594 self.foreach_facet_around_vertex(vertex_id, [&](Index fid) { result.push_back(fid); });
1598 R
"(Get all facets around a vertex.
1600:param vertex_id: vertex index
1602:returns: list of facet indices
1604.. code-block:: python
1606 facets = mesh.facets_around_vertex(vid)
1611 surface_mesh_class.def(
1612 "facets_around_edge",
1613 [](MeshType& self, Index edge_id) {
1614 std::vector<Index> result;
1615 self.foreach_facet_around_edge(edge_id, [&](Index fid) { result.push_back(fid); });
1619 R
"(Get all facets around an edge.
1621:param edge_id: edge index
1623:returns: list of facet indices
1625.. code-block:: python
1627 facets = mesh.facets_around_edge(eid)
1632 surface_mesh_class.def(
1633 "corners_around_edge",
1634 [](MeshType& self, Index edge_id) {
1635 std::vector<Index> result;
1636 self.foreach_corner_around_edge(edge_id, [&](Index cid) { result.push_back(cid); });
1640 R
"(Get all corners around an edge.
1642:param edge_id: edge index
1644:returns: list of corner indices
1646.. code-block:: python
1648 corners = mesh.corners_around_edge(eid)
1653 surface_mesh_class.def(
1654 "corners_around_vertex",
1655 [](MeshType& self, Index vertex_id) {
1656 std::vector<Index> result;
1657 self.foreach_corner_around_vertex(vertex_id, [&](Index cid) { result.push_back(cid); });
1661 R
"(Get all corners around a vertex.
1663:param vertex_id: vertex index
1665:returns: list of corner indices
1667.. code-block:: python
1669 corners = mesh.corners_around_vertex(vid)
1674 surface_mesh_class.def(
1675 "edges_around_vertex",
1676 [](MeshType& self, Index vertex_id) {
1677 std::vector<Index> result;
1678 self.foreach_edge_around_vertex_with_duplicates(vertex_id, [&](Index eid) {
1679 result.push_back(eid);
1684 R
"(Get all edges around a vertex.
1687 Each incident edge will be visited once for each incident facet.
1688 Thus, manifold edge will be visited exactly twice,
1689 boundary edge will be visited exactly once,
1690 non-manifold edges will be visited more than twice.
1692:param vertex_id: vertex index
1694:returns: list of edge indices (with duplicates)
1696.. code-block:: python
1698 edges = mesh.edges_around_vertex(vid)
1705 MeshType* mesh =
nullptr;
1707 std::vector<AttributeId> get_metadata()
const
1710 lagrange::AttributeMatcher opts;
1711 opts.
usages = AttributeUsage::String;
1717 auto meta_data_class = nb::class_<MetaData>(m,
"MetaData",
"Metadata `dict` of the mesh");
1718 meta_data_class.def(
"__len__", [](
const MetaData& self) {
1719 auto data = self.get_metadata();
1722 meta_data_class.def(
"__getitem__", [](
const MetaData& self, std::string_view key) {
1723 return self.mesh->get_metadata(key);
1725 meta_data_class.def(
1727 [](MetaData& self, std::string_view key, std::string_view value) {
1729 if (self.mesh->has_attribute(key)) {
1730 self.mesh->set_metadata(key, value);
1732 self.mesh->create_metadata(key, value);
1735 meta_data_class.def(
"__delitem__", [](MetaData& self, std::string_view key) {
1737 self.mesh->delete_attribute(key);
1739 meta_data_class.def(
"__repr__", [](
const MetaData& self) -> std::string {
1740 auto data = self.get_metadata();
1741 if (data.empty())
return "MetaData({})";
1744 for (
auto id : data) {
1745 auto name = self.mesh->get_attribute_name(
id);
1746 auto value = self.mesh->get_metadata(
id);
1747 fmt::format_to(std::back_inserter(r),
" {}: {},\n", name, value);
1749 return lagrange::format(
"MetaData(\n{})", r);
1752 surface_mesh_class.def_prop_ro(
1754 [](MeshType& self) {
1756 meta_data.mesh = &self;
1759 "Metadata of the mesh.");
1761 surface_mesh_class.def(
1762 "get_matching_attribute_ids",
1764 std::optional<AttributeElement> element,
1765 std::optional<AttributeUsage> usage,
1766 Index num_channels) {
1767 AttributeMatcher opts;
1768 if (usage.has_value()) {
1769 opts.
usages = usage.value();
1771 if (element.has_value()) {
1777 "element"_a = nb::none(),
1778 "usage"_a = nb::none(),
1779 "num_channels"_a = 0,
1780 R
"(Get all matching attribute ids with the desired element type, usage and number of channels.
1782:param element: The target element type. None matches all element types.
1783:param usage: The target usage type. None matches all usage types.
1784:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1786:returns: A list of attribute ids matching the target element, usage and number of channels.
1789 surface_mesh_class.def(
1790 "get_matching_attribute_id",
1792 std::optional<AttributeElement> element,
1793 std::optional<AttributeUsage> usage,
1794 Index num_channels) {
1795 std::optional<AttributeId> result;
1796 self.seq_foreach_attribute_id([&](
AttributeId attr_id) {
1797 if (result.has_value()) {
1800 const auto name = self.get_attribute_name(attr_id);
1801 if (self.attr_name_is_reserved(name))
return;
1802 const auto& attr = self.get_attribute_base(attr_id);
1803 if (element && attr.get_element_type() != *element)
return;
1804 if (usage && attr.get_usage() != *usage)
return;
1805 if (num_channels != 0 && attr.get_num_channels() != num_channels)
return;
1810 "element"_a = nb::none(),
1811 "usage"_a = nb::none(),
1812 "num_channels"_a = 0,
1813 R
"(Get one matching attribute id with the desired element type, usage and number of channels.
1815:param element: The target element type. None matches all element types.
1816:param usage: The target usage type. None matches all usage types.
1817:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1819:returns: An attribute id matching the target element, usage and number of channels, if found. None otherwise.
1822 surface_mesh_class.def(
1824 [](MeshType& self) -> MeshType {
1825 MeshType mesh = self;
1828 R
"(Create a shallow copy of this mesh.)");
1830 surface_mesh_class.def(
1832 [](MeshType& self, [[maybe_unused]] std::optional<nb::dict> memo) -> MeshType {
1833 MeshType mesh = self;
1839 using AttributeType = std::decay_t<
decltype(attr)>;
1840 if constexpr (AttributeType::IsIndexed) {
1841 auto& value_attr = attr.values();
1842 if (value_attr.is_external()) {
1843 value_attr.create_internal_copy();
1845 auto& index_attr = attr.indices();
1846 if (index_attr.is_external()) {
1847 index_attr.create_internal_copy();
1850 if (attr.is_external()) {
1851 attr.create_internal_copy();
1857 "memo"_a = nb::none(),
1858 R
"(Create a deep copy of this mesh.)");
1860 surface_mesh_class.def(
1862 [](MeshType& self,
bool strip) -> MeshType {
1864 return MeshType::stripped_copy(self);
1866 auto py_self = nb::find(self);
1867 return nb::cast<MeshType>(py_self.attr(
"__deepcopy__")());
1871 R
"(Create a deep copy of this mesh.
1873:param strip: If True, strip the mesh of all attributes except for the reserved attributes.)");
Index find_edge_from_vertices(Index v0, Index v1) const
Definition Mesh.h:694
bool is_boundary_edge(Index e) const
Definition Mesh.h:821
Index get_one_corner_around_edge(Index e) const
Definition Mesh.h:795
Index get_edge(Index f, Index lv) const
Definition Mesh.h:664
std::array< Index, 2 > get_edge_vertices(Index e) const
Retrieve edge endpoints.
Definition Mesh.h:730
Index get_one_facet_around_edge(Index e) const
Definition Mesh.h:782
Index get_one_corner_around_vertex(Index v) const
Definition Mesh.h:808
Index get_num_edges() const
Definition Mesh.h:650
Definition PyAttribute.h:24
Definition PyIndexedAttribute.h:27
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
AttributeReorientPolicy
Policy for updating attribute values when reorienting mesh facets.
Definition AttributeFwd.h:192
uint32_t AttributeId
Identified to be used to access an attribute.
Definition AttributeFwd.h:73
AttributeUsage
Usage tag indicating how the attribute should behave under mesh transformations.
Definition AttributeFwd.h:54
#define LA_ATTRIBUTE_INDEX_X(mode, data)
X Macro arguments for the Attribute<> class.
Definition AttributeTypes.h:80
AttributeElement
Type of element to which the attribute is attached.
Definition AttributeFwd.h:26
AttributeDeletePolicy
Policy for attribute deletion of reserved attribute names.
Definition AttributeFwd.h:172
#define LA_ATTRIBUTE_X(mode, data)
X Macro arguments for the Attribute<> class.
Definition AttributeTypes.h:48
@ None
Do not reorient attribute values when flipping mesh facets.
Definition AttributeFwd.h:193
@ ErrorIfReserved
Default deletion policy, throw an exception if attribute name is reserved.
Definition AttributeFwd.h:87
@ Position
Mesh attribute must have exactly dim channels.
Definition AttributeFwd.h:57
@ Tangent
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:59
@ Vector
Mesh attribute can have any number of channels (including 1 channel).
Definition AttributeFwd.h:55
@ CornerIndex
Single channel integer attribute indexing a mesh corner.
Definition AttributeFwd.h:65
@ VertexIndex
Single channel integer attribute indexing a mesh vertex.
Definition AttributeFwd.h:63
@ EdgeIndex
Single channel integer attribute indexing a mesh edge.
Definition AttributeFwd.h:66
@ Normal
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:58
@ FacetIndex
Single channel integer attribute indexing a mesh facet.
Definition AttributeFwd.h:64
@ Color
Mesh attribute can have 1, 2, 3 or 4 channels.
Definition AttributeFwd.h:61
@ UV
Mesh attribute must have exactly 2 channels.
Definition AttributeFwd.h:62
@ Bitangent
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:60
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
@ WarnAndCopy
Logs a warning and copy the buffer data if it grows beyond the buffer capacity.
Definition AttributeFwd.h:106
@ Value
Values that are not attached to a specific element.
Definition AttributeFwd.h:42
@ Edge
Per-edge mesh attributes.
Definition AttributeFwd.h:34
@ Indexed
Indexed mesh attributes.
Definition AttributeFwd.h:45
@ Facet
Per-facet mesh attributes.
Definition AttributeFwd.h:31
@ Corner
Per-corner mesh attributes.
Definition AttributeFwd.h:37
@ Vertex
Per-vertex mesh attributes.
Definition AttributeFwd.h:28
@ ErrorIfReserved
Default deletion policy, throw an exception if attribute name is reserved.
Definition AttributeFwd.h:173
void par_foreach_attribute_write(SurfaceMesh< Scalar, Index > &mesh, Visitor &&vis)
Applies a function in parallel to each attribute of a mesh.
Definition foreach_attribute.h:408
std::vector< AttributeId > find_matching_attributes(const SurfaceMesh< Scalar, Index > &mesh, const AttributeMatcher &options)
Finds all attributes with the specified usage/element type/number of channels.
Definition find_matching_attributes.cpp:80
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:175
#define la_debug_assert(...)
Debug assertion check.
Definition assert.h:195
SharedSpan< T > make_shared_span(const std::shared_ptr< Y > &r, T *element_ptr, size_t size)
Created a SharedSpan object around an internal buffer of a parent object.
Definition SharedSpan.h:101
::nonstd::span< T, Extent > span
A bounds-safe view for sequences of objects.
Definition span.h:27
constexpr T invalid()
You can use invalid<T>() to get a value that can represent "invalid" values, such as invalid indices ...
Definition invalid.h:40
BitField< AttributeElement > element_types
List of attribute element types to include. By default, all element types are included.
Definition find_matching_attributes.h:40
BitField< AttributeUsage > usages
List of attribute usages to include. By default, all usages are included.
Definition find_matching_attributes.h:37
size_t num_channels
Number of channels to match against. Default value is 0, which disables this test.
Definition find_matching_attributes.h:43