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>
37namespace lagrange::python {
39template <
typename Scalar,
typename Index>
40void bind_surface_mesh(nanobind::module_& m)
42 namespace nb = nanobind;
43 using namespace nb::literals;
44 using namespace lagrange;
45 using namespace lagrange::python;
47 using MeshType = SurfaceMesh<Scalar, Index>;
49 auto surface_mesh_class = nb::class_<MeshType>(m,
"SurfaceMesh",
"Surface mesh data structure");
50 surface_mesh_class.def(nb::init<Index>(), nb::arg(
"dimension") = 3);
51 surface_mesh_class.def(
53 [](MeshType& self, Tensor<Scalar> b) {
54 auto [data, shape, stride] = tensor_to_span(b);
57 self.add_vertex(data);
60 R
"(Add a vertex to the mesh.
62:param vertex: vertex coordinates)");
65 surface_mesh_class.def(
67 [](MeshType& self, nb::list b) {
69 if (self.get_dimension() == 3) {
71 {nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1]), nb::cast<Scalar>(b[2])});
72 }
else if (self.get_dimension() == 2) {
73 self.add_vertex({nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1])});
75 throw std::runtime_error(
"Dimension mismatch in vertex tensor");
79 R
"(Add a vertex to the mesh.
81:param vertex: vertex coordinates as a list)");
83 surface_mesh_class.def(
85 [](MeshType& self, Tensor<Scalar> b) {
86 auto [data, shape, stride] = tensor_to_span(b);
89 self.add_vertices(
static_cast<Index
>(shape[0]), data);
92 R
"(Add multiple vertices to the mesh.
94:param vertices: N x D tensor of vertex coordinates, where N is the number of vertices and D is the dimension)");
97 surface_mesh_class.def(
99 &MeshType::add_triangle,
103 R
"(Add a triangle to the mesh.
105:param v0: first vertex index
106:param v1: second vertex index
107:param v2: third vertex index
109:returns: facet index of the added triangle)");
110 surface_mesh_class.def(
112 [](MeshType& self, Tensor<Index> b) {
113 auto [data, shape, stride] = tensor_to_span(b);
116 self.add_triangles(
static_cast<Index
>(shape[0]), data);
119 R
"(Add multiple triangles to the mesh.
121:param triangles: N x 3 tensor of vertex indices, where N is the number of triangles)");
122 surface_mesh_class.def(
129 R
"(Add a quad to the mesh.
131:param v0: first vertex index
132:param v1: second vertex index
133:param v2: third vertex index
134:param v3: fourth vertex index
136:returns: facet index of the added quad)");
137 surface_mesh_class.def(
139 [](MeshType& self, Tensor<Index> b) {
140 auto [data, shape, stride] = tensor_to_span(b);
143 self.add_quads(
static_cast<Index
>(shape[0]), data);
146 R
"(Add multiple quads to the mesh.
148:param quads: N x 4 tensor of vertex indices, where N is the number of quads)");
149 surface_mesh_class.def(
151 [](MeshType& self, Tensor<Index> b) {
152 auto [data, shape, stride] = tensor_to_span(b);
155 self.add_polygon(data);
158 R
"(Add a polygon to the mesh.
160:param vertices: 1D tensor of vertex indices defining the polygon
162:returns: facet index of the added polygon)");
163 surface_mesh_class.def(
165 [](MeshType& self, Tensor<Index> b) {
166 auto [data, shape, stride] = tensor_to_span(b);
168 self.add_polygons(
static_cast<Index
>(shape[0]),
static_cast<Index
>(shape[1]), data);
171 R
"(Add multiple regular polygons to the mesh.
173: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)");
174 surface_mesh_class.def(
176 [](MeshType& self, Tensor<Index> sizes, Tensor<Index> indices) {
177 auto [size_data, size_shape, size_stride] = tensor_to_span(sizes);
181 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
185 self.add_hybrid(size_data, index_data);
189 R
"(Add hybrid facets (polygons with varying number of vertices) to the mesh.
191:param sizes: 1D tensor specifying the number of vertices for each facet
192:param indices: 1D tensor of vertex indices for all facets concatenated together)");
193 surface_mesh_class.def(
195 [](MeshType& self, Tensor<Index> b) {
196 auto [data, shape, stride] = tensor_to_span(b);
199 self.remove_vertices(data);
202 R
"(Remove selected vertices from the mesh.
204:param vertices: 1D tensor of vertex indices to remove)");
205 surface_mesh_class.def(
207 [](MeshType& self, nb::list b) {
208 std::vector<Index> indices;
210 indices.push_back(nb::cast<Index>(i));
212 self.remove_vertices(indices);
215 R
"(Remove selected vertices from the mesh.
217:param vertices: list of vertex indices to remove)");
218 surface_mesh_class.def(
220 [](MeshType& self, Tensor<Index> b) {
221 auto [data, shape, stride] = tensor_to_span(b);
224 self.remove_facets(data);
227 R
"(Remove selected facets from the mesh.
229:param facets: 1D tensor of facet indices to remove)");
230 surface_mesh_class.def(
232 [](MeshType& self, nb::list b) {
233 std::vector<Index> indices;
235 indices.push_back(nb::cast<Index>(i));
237 self.remove_facets(indices);
240 R
"(Remove selected facets from the mesh.
242:param facets: list of facet indices to remove)");
243 surface_mesh_class.def(
245 &MeshType::clear_vertices,
246 R
"(Remove all vertices from the mesh.)");
247 surface_mesh_class.def(
249 &MeshType::clear_facets,
250 R
"(Remove all facets from the mesh.)");
251 surface_mesh_class.def(
253 &MeshType::shrink_to_fit,
254 R
"(Shrink the internal storage to fit the current mesh size.)");
255 surface_mesh_class.def(
256 "compress_if_regular",
257 &MeshType::compress_if_regular,
258 R
"(Compress the mesh representation if it is regular (all facets have the same number of vertices).
260:returns: True if the mesh was compressed, False otherwise)");
261 surface_mesh_class.def_prop_ro("is_triangle_mesh", &MeshType::is_triangle_mesh);
262 surface_mesh_class.def_prop_ro(
"is_quad_mesh", &MeshType::is_quad_mesh);
263 surface_mesh_class.def_prop_ro(
"is_regular", &MeshType::is_regular);
264 surface_mesh_class.def_prop_ro(
"is_hybrid", &MeshType::is_hybrid);
265 surface_mesh_class.def_prop_ro(
"dimension", &MeshType::get_dimension);
266 surface_mesh_class.def_prop_ro(
"vertex_per_facet", &MeshType::get_vertex_per_facet);
267 surface_mesh_class.def_prop_ro(
"num_vertices", &MeshType::get_num_vertices);
268 surface_mesh_class.def_prop_ro(
"num_facets", &MeshType::get_num_facets);
269 surface_mesh_class.def_prop_ro(
"num_corners", &MeshType::get_num_corners);
271 surface_mesh_class.def(
273 [](MeshType& self, Index i) {
274 return span_to_tensor(self.get_position(i), nb::find(&self));
277 R
"(Get the position of a vertex.
279:param vertex_id: vertex index
281:returns: position coordinates as a tensor)");
282 surface_mesh_class.def(
284 [](MeshType& self, Index i) {
285 return span_to_tensor(self.ref_position(i), nb::find(&self));
288 R
"(Get a mutable reference to the position of a vertex.
290:param vertex_id: vertex index
292:returns: mutable position coordinates as a tensor)");
293 surface_mesh_class.def(
295 &MeshType::get_facet_size,
297 R
"(Get the number of vertices in a facet.
299:param facet_id: facet index
301:returns: number of vertices in the facet)");
302 surface_mesh_class.def(
304 &MeshType::get_facet_vertex,
307 R
"(Get a vertex index from a facet.
309:param facet_id: facet index
310:param local_vertex_id: local vertex index within the facet (0 to facet_size-1)
312:returns: global vertex index)");
313 surface_mesh_class.def(
314 "get_facet_corner_begin",
315 &MeshType::get_facet_corner_begin,
317 R
"(Get the first corner index of a facet.
319:param facet_id: facet index
321:returns: first corner index of the facet)");
322 surface_mesh_class.def(
323 "get_facet_corner_end",
324 &MeshType::get_facet_corner_end,
326 R
"(Get the end corner index of a facet (one past the last corner).
328:param facet_id: facet index
330:returns: end corner index of the facet)");
331 surface_mesh_class.def(
333 &MeshType::get_corner_vertex,
335 R
"(Get the vertex index associated with a corner.
337:param corner_id: corner index
339:returns: vertex index)");
340 surface_mesh_class.def(
342 &MeshType::get_corner_facet,
344 R
"(Get the facet index associated with a corner.
346:param corner_id: corner index
348:returns: facet index)");
349 surface_mesh_class.def(
350 "get_facet_vertices",
351 [](MeshType& self, Index f) {
352 return span_to_tensor(self.get_facet_vertices(f), nb::find(&self));
355 R
"(Get all vertex indices of a facet.
357:param facet_id: facet index
359:returns: vertex indices as a tensor)");
360 surface_mesh_class.def(
361 "ref_facet_vertices",
362 [](MeshType& self, Index f) {
363 return span_to_tensor(self.ref_facet_vertices(f), nb::find(&self));
366 R
"(Get a mutable reference to all vertex indices of a facet.
368:param facet_id: facet index
370:returns: mutable vertex indices as a tensor)");
371 surface_mesh_class.def(
373 &MeshType::get_attribute_id,
375 R
"(Get the attribute ID by name.
377:param name: attribute name
379:returns: attribute ID)");
380 surface_mesh_class.def(
381 "get_attribute_name",
382 &MeshType::get_attribute_name,
384 R
"(Get the attribute name by ID.
386:param id: attribute ID
388:returns: attribute name)");
389 surface_mesh_class.def(
392 std::string_view name,
393 std::variant<std::monostate, AttributeElement, std::string_view> element,
394 std::variant<std::monostate, AttributeUsage, std::string_view> usage,
395 std::variant<std::monostate, GenericTensor, nb::list> initial_values,
396 std::variant<std::monostate, Tensor<Index>, GenericTensor, nb::list> initial_indices,
397 std::optional<Index> num_channels,
398 std::optional<nb::type_object> dtype) {
399 const bool with_initial_values = initial_values.index() != 0;
400 const bool with_initial_indices = initial_indices.index() != 0;
404 if (num_channels.has_value()) {
405 n = num_channels.value();
406 }
else if (with_initial_values) {
407 if (initial_values.index() == 1) {
408 const auto& values = std::get<GenericTensor>(initial_values);
410 values.ndim() == 1 || values.ndim() == 2,
411 "Only vector or matrix are accepted as initial values.");
412 n = values.ndim() == 1 ? 1 :
static_cast<Index
>(values.shape(1));
413 }
else if (initial_values.index() == 2) {
417 throw nb::type_error(
"Either number of channels or initial values are required!");
424 using T = std::decay_t<
decltype(value)>;
425 if constexpr (std::is_same_v<T, AttributeElement>) {
427 }
else if (with_initial_indices) {
429 }
else if constexpr (std::is_same_v<T, std::string_view>) {
430 if (value ==
"Vertex") {
432 }
else if (value ==
"Facet") {
434 }
else if (value ==
"Edge") {
436 }
else if (value ==
"Corner") {
438 }
else if (value ==
"Value") {
440 }
else if (value ==
"Indexed") {
443 throw nb::type_error(
"Invalid element type!");
445 }
else if (with_initial_values) {
447 const Index num_vertices = self.get_num_vertices();
448 const Index num_facets = self.get_num_facets();
449 const Index num_corners = self.get_num_corners();
450 const Index num_edges =
454 if (initial_values.index() == 1) {
455 const auto& values = std::get<GenericTensor>(initial_values);
456 num_rows = values.shape(0);
457 }
else if (initial_values.index() == 2) {
458 const auto& values = std::get<nb::list>(initial_values);
459 num_rows = nb::len(values);
463 if (num_rows == num_vertices) {
465 num_rows != num_facets,
466 "Cannot infer attribute element due to ambiguity: vertices vs "
469 num_rows != num_edges,
470 "Cannot infer attribute element due to ambiguity: vertices vs "
473 num_rows != num_corners,
474 "Cannot infer attribute element due to ambiguity: vertices vs "
477 }
else if (num_rows == num_facets) {
479 num_rows != num_edges,
480 "Cannot infer attribute element due to ambiguity: facets vs "
483 num_rows != num_corners,
484 "Cannot infer attribute element due to ambiguity: facets vs "
487 }
else if (num_rows == num_corners) {
489 num_rows != num_edges,
490 "Cannot infer attribute element due to ambiguity: corners vs "
493 }
else if (num_rows == num_edges) {
496 throw nb::type_error(
497 "Cannot infer attribute element type from initial_values!");
500 throw nb::type_error(
"Invalid element type!");
509 using T = std::decay_t<
decltype(value)>;
510 if constexpr (std::is_same_v<T, AttributeUsage>) {
512 }
else if constexpr (std::is_same_v<T, std::string_view>) {
513 if (value ==
"Vector") {
515 }
else if (value ==
"Scalar") {
517 }
else if (value ==
"Position") {
519 }
else if (value ==
"Normal") {
521 }
else if (value ==
"Tangent") {
523 }
else if (value ==
"Bitangent") {
525 }
else if (value ==
"Color") {
527 }
else if (value ==
"UV") {
529 }
else if (value ==
"VertexIndex") {
531 }
else if (value ==
"FacetIndex") {
533 }
else if (value ==
"CornerIndex") {
535 }
else if (value ==
"EdgeIndex") {
538 throw nb::type_error(
"Invalid usage type!");
550 auto create_attribute = [&](
auto values) {
551 using ValueType =
typename std::decay_t<
decltype(values)>::element_type;
555 std::vector<Index> index_storage;
558 if (with_initial_values) {
559 init_values = values;
563 num_channels.has_value(),
564 "Number of channels is required when initial values are not provided!");
567 "dtype is required when initial values are not provided!");
571 if (
const Tensor<Index>* tensor_ptr =
572 std::get_if<Tensor<Index>>(&initial_indices)) {
574 const auto& indices = *tensor_ptr;
575 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
577 init_indices = index_data;
579 GenericTensor* generic_tensor_ptr =
580 std::get_if<GenericTensor>(&initial_indices)) {
582 auto& indices = *generic_tensor_ptr;
583 index_storage.resize(indices.size());
585#define LA_X_create_attribute_index(_, IndexType) \
586 if (indices.dtype() == nb::dtype<IndexType>()) { \
587 auto view = indices.template view<IndexType, nb::ndim<1>>(); \
588 std::copy(view.data(), view.data() + indices.size(), index_storage.begin()); \
591#undef LA_X_create_attribute_index
592 init_indices =
span<Index>(index_storage.data(), index_storage.size());
593 }
else if (
const nb::list* list_ptr = std::get_if<nb::list>(&initial_indices)) {
595 const nb::list& py_list = *list_ptr;
596 index_storage = nb::cast<std::vector<Index>>(py_list);
597 init_indices =
span<Index>(index_storage.begin(), index_storage.size());
600 return self.template create_attribute<ValueType>(
610 if (
const GenericTensor* tensor_ptr = std::get_if<GenericTensor>(&initial_values)) {
611 const auto& values = *tensor_ptr;
612#define LA_X_create_attribute(_, ValueType) \
613 if (values.dtype() == nb::dtype<ValueType>()) { \
614 Tensor<ValueType> local_values(values.handle()); \
615 auto [value_data, value_shape, value_stride] = tensor_to_span(local_values); \
616 la_runtime_assert(is_dense(value_shape, value_stride)); \
617 if (num_channels.has_value()) { \
618 Index nn = value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]); \
619 la_runtime_assert(nn == n, "Number of channels does not match initial_values"); \
621 return create_attribute(value_data); \
624#undef LA_X_create_attribute
625 }
else if (
const nb::list* list_ptr = std::get_if<nb::list>(&initial_values)) {
626 auto values = nb::cast<std::vector<double>>(*list_ptr);
627 return create_attribute(
span<double>(values.data(), values.size()));
628 }
else if (dtype.has_value()) {
629 const auto& t = dtype.value();
630 auto np = nb::module_::import_(
"numpy");
631 if (t.is(&PyFloat_Type)) {
634 return create_attribute(local_values);
635 }
else if (t.is(np.attr(
"float32"))) {
637 return create_attribute(local_values);
638 }
else if (t.is(np.attr(
"float64"))) {
640 return create_attribute(local_values);
641 }
else if (t.is(np.attr(
"int8"))) {
643 return create_attribute(local_values);
644 }
else if (t.is(np.attr(
"int16"))) {
646 return create_attribute(local_values);
647 }
else if (t.is(np.attr(
"int32"))) {
649 return create_attribute(local_values);
650 }
else if (t.is(np.attr(
"int64"))) {
652 return create_attribute(local_values);
653 }
else if (t.is(np.attr(
"uint8"))) {
655 return create_attribute(local_values);
656 }
else if (t.is(np.attr(
"uint16"))) {
658 return create_attribute(local_values);
659 }
else if (t.is(np.attr(
"uint32"))) {
661 return create_attribute(local_values);
662 }
else if (t.is(np.attr(
"uint64"))) {
664 return create_attribute(local_values);
667 throw nb::type_error(
"`initial_values` and `dtype` cannot both be None!");
670 "element"_a = nb::none(),
671 "usage"_a = nb::none(),
672 "initial_values"_a = nb::none(),
673 "initial_indices"_a = nb::none(),
674 "num_channels"_a = nb::none(),
675 "dtype"_a = nb::none(),
677 "def create_attribute(self, "
679 "element: typing.Union[AttributeElement, "
681 "'Vertex', 'Facet', 'Edge', 'Corner', 'Value', 'Indexed'"
683 "usage: typing.Union[AttributeUsage, "
685 "'Vector', 'Scalar', 'Position', 'Normal', 'Tangent', 'Bitangent', 'Color', 'UV', "
686 "'VertexIndex', 'FacetIndex', 'CornerIndex', 'EdgeIndex'"
688 "initial_values: typing.Union[numpy.typing.NDArray, typing.List[float], None] = None, "
689 "initial_indices: typing.Union[numpy.typing.NDArray, typing.List[int], None] = None, "
690 "num_channels: typing.Optional[int] = None, "
691 "dtype: typing.Optional[numpy.typing.DTypeLike] = None) -> int"),
692 R
"(Create an attribute.
694:param name: Name of the attribute.
695:param element: Element type of the attribute. If None, derive from the shape of initial values.
696:param usage: Usage type of the attribute. If None, derive from the shape of initial values or the number of channels.
697:param initial_values: Initial values of the attribute.
698:param initial_indices: Initial indices of the attribute (Indexed attribute only).
699:param num_channels: Number of channels of the attribute.
700:param dtype: Data type of the attribute.
702:returns: The id of the created attribute.
705 If `element` is None, it will be derived based on the cardinality of the mesh elements.
706 If there is an ambiguity, an exception will be raised.
707 In addition, explicit `element` specification is required for value attributes.
710 If `usage` is None, it will be derived based on the shape of `initial_values` or `num_channels` if specified.)");
712 surface_mesh_class.def(
713 "create_attribute_from",
714 &MeshType::template create_attribute_from<Scalar, Index>,
717 "source_name"_a =
"",
718 R
"(Shallow copy an attribute from another mesh.
720:param name: Name of the attribute.
721:param source_mesh: Source mesh.
722:param source_name: Name of the attribute in the source mesh. If empty, use the same name as `name`.
724:returns: The id of the created attribute.)");
726 surface_mesh_class.def(
729 std::string_view name,
732 GenericTensor values) {
733 auto wrap_as_attribute = [&](
auto tensor) {
734 using ValueType =
typename std::decay_t<
decltype(tensor)>
::Scalar;
735 auto [data, shape, stride] = tensor_to_span(tensor);
737 Index num_channels = shape.size() == 1 ? 1 :
static_cast<Index
>(shape[1]);
739 auto owner = std::make_shared<nb::object>(nb::find(values));
740 if constexpr (std::is_const_v<ValueType>) {
741 id = self.wrap_as_const_attribute(
748 id = self.wrap_as_attribute(
755 auto& attr = self.template ref_attribute<ValueType>(
id);
760#define LA_X_wrap_as_attribute(_, ValueType) \
761 if (values.dtype() == nb::dtype<ValueType>()) { \
762 Tensor<ValueType> local_values(values.handle()); \
763 return wrap_as_attribute(local_values); \
766#undef LA_X_wrap_as_attribute
767 throw nb::type_error(
"Unsupported value type!");
773 R
"(Wrap an existing numpy array as an attribute.
775:param name: Name of the attribute.
776:param element: Element type of the attribute.
777:param usage: Usage type of the attribute.
778:param values: Values of the attribute.
780:returns: The id of the created attribute.)");
781 surface_mesh_class.def(
782 "wrap_as_indexed_attribute",
784 std::string_view name,
786 GenericTensor values,
787 Tensor<Index> indices) {
788 auto wrap_as_indexed_attribute = [&](
auto value_tensor,
auto index_tensor) {
789 using ValueType =
typename std::decay_t<
decltype(value_tensor)>
::Scalar;
790 auto [value_data, value_shape, value_stride] = tensor_to_span(value_tensor);
791 auto [index_data, index_shape, index_stride] = tensor_to_span(index_tensor);
794 Index num_values =
static_cast<Index
>(value_shape[0]);
796 value_shape.size() == 1 ? 1 :
static_cast<Index
>(value_shape[1]);
799 auto value_owner = std::make_shared<nb::object>(nb::find(values));
800 auto index_owner = std::make_shared<nb::object>(nb::find(indices));
802 if constexpr (std::is_const_v<ValueType>) {
803 id = self.wrap_as_const_indexed_attribute(
811 id = self.wrap_as_indexed_attribute(
819 auto& attr = self.template ref_indexed_attribute<ValueType>(
id);
825#define LA_X_wrap_as_indexed_attribute(_, ValueType) \
826 if (values.dtype() == nb::dtype<ValueType>()) { \
827 Tensor<ValueType> local_values(values.handle()); \
828 return wrap_as_indexed_attribute(local_values, indices); \
831#undef LA_X_wrap_as_indexed_attribute
832 throw nb::type_error(
"Unsupported value type!");
838 R
"(Wrap an existing numpy array as an indexed attribute.
840:param name: Name of the attribute.
841:param usage: Usage type of the attribute.
842:param values: Values of the attribute.
843:param indices: Indices of the attribute.
845:returns: The id of the created attribute.)");
846 surface_mesh_class.def(
847 "duplicate_attribute",
848 &MeshType::duplicate_attribute,
851 R
"(Duplicate an attribute with a new name.
853:param old_name: name of the attribute to duplicate
854:param new_name: name for the new attribute
856:returns: attribute ID of the duplicated attribute)");
857 surface_mesh_class.def(
859 &MeshType::rename_attribute,
862 R
"(Rename an attribute.
864:param old_name: current name of the attribute
865:param new_name: new name for the attribute)");
866 surface_mesh_class.def(
868 &MeshType::delete_attribute,
871 surface_mesh_class.def(
873 [](MeshType& self, std::string_view name) { self.delete_attribute(name); },
875 R
"(Delete an attribute by name.
877:param name: Name of the attribute.)");
878 surface_mesh_class.def(
880 [](MeshType& self,
AttributeId id) { self.delete_attribute(self.get_attribute_name(
id)); },
882 R
"(Delete an attribute by id.
884:param id: Id of the attribute.)");
885 surface_mesh_class.def(
887 &MeshType::has_attribute,
889 R
"(Check if an attribute exists.
891:param name: attribute name
893:returns: True if the attribute exists, False otherwise)");
894 surface_mesh_class.def(
895 "is_attribute_indexed",
896 static_cast<bool (MeshType::*)(
AttributeId) const
>(&MeshType::is_attribute_indexed),
898 R
"(Check if an attribute is indexed.
900:param id: attribute ID
902:returns: True if the attribute is indexed, False otherwise)");
903 surface_mesh_class.def(
904 "is_attribute_indexed",
905 static_cast<bool (MeshType::*)(std::string_view) const
>(&MeshType::is_attribute_indexed),
907 R
"(Check if an attribute is indexed.
909:param name: attribute name
911:returns: True if the attribute is indexed, False otherwise)");
914 auto ensure_attribute_is_not_shared = [](MeshType& mesh,
AttributeId id) {
916#define LA_X_trigger_copy_on_write(_, ValueType) \
917 if (mesh.is_attribute_indexed(id)) { \
918 if (dynamic_cast<const IndexedAttribute<ValueType, Index>*>(&attr_base)) { \
919 [[maybe_unused]] auto& attr = mesh.template ref_indexed_attribute<ValueType>(id); \
922 if (dynamic_cast<const Attribute<ValueType>*>(&attr_base)) { \
923 [[maybe_unused]] auto& attr = mesh.template ref_attribute<ValueType>(id); \
927#undef LA_X_trigger_copy_on_write
930 surface_mesh_class.def(
932 [&](MeshType& self,
AttributeId id,
bool sharing) {
934 !self.is_attribute_indexed(
id),
936 "Attribute {} is indexed! Please use `indexed_attribute` property "
939 if (!sharing) ensure_attribute_is_not_shared(self,
id);
944 R
"(Get an attribute by id.
946:param id: Id of the attribute.
947:param sharing: Whether to allow sharing the attribute with other meshes.
949:returns: The attribute.)");
950 surface_mesh_class.def(
952 [&](MeshType& self, std::string_view name,
bool sharing) {
954 !self.is_attribute_indexed(name),
956 "Attribute \"{}\" is indexed! Please use `indexed_attribute` property "
959 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
964 R
"(Get an attribute by name.
966:param name: Name of the attribute.
967:param sharing: Whether to allow sharing the attribute with other meshes.
969:return: The attribute.)");
970 surface_mesh_class.def(
972 [&](MeshType& self,
AttributeId id,
bool sharing) {
974 self.is_attribute_indexed(
id),
976 "Attribute {} is not indexed! Please use `attribute` property instead.",
978 if (!sharing) ensure_attribute_is_not_shared(self,
id);
983 R
"(Get an indexed attribute by id.
985:param id: Id of the attribute.
986:param sharing: Whether to allow sharing the attribute with other meshes.
988:returns: The indexed attribute.)");
989 surface_mesh_class.def(
991 [&](MeshType& self, std::string_view name,
bool sharing) {
993 self.is_attribute_indexed(name),
995 "Attribute \"{}\"is not indexed! Please use `attribute` property instead.",
997 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
1002 R
"(Get an indexed attribute by name.
1004:param name: Name of the attribute.
1005:param sharing: Whether to allow sharing the attribute with other meshes.
1007:returns: The indexed attribute.)");
1008 surface_mesh_class.def(
1009 "__attribute_ref_count__",
1011 auto ptr = self._get_attribute_ptr(
id);
1012 return ptr.use_count();
1015 R
"(Get the reference count of an attribute (for debugging purposes).
1017:param id: attribute ID
1019:returns: reference count of the attribute)");
1020 surface_mesh_class.def_prop_rw(
1022 [](
const MeshType& self) {
1023 const auto& attr = self.get_vertex_to_position();
1024 return attribute_to_tensor(attr, nb::find(&self));
1026 [](MeshType& self, Tensor<Scalar> tensor) {
1027 auto [values, shape, stride] = tensor_to_span(tensor);
1031 size_t num_vertices = shape.size() == 1 ? 1 : shape[0];
1032 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1033 auto id = self.wrap_as_vertices(
1035 static_cast<Index
>(num_vertices));
1036 auto& attr = self.template ref_attribute<Scalar>(
id);
1039 "Vertices of the mesh.");
1040 surface_mesh_class.def_prop_rw(
1042 [](
const MeshType& self) {
1043 if (self.is_regular()) {
1044 const auto& attr = self.get_corner_to_vertex();
1045 const size_t shape[2] = {
1046 static_cast<size_t>(self.get_num_facets()),
1047 static_cast<size_t>(self.get_vertex_per_facet())};
1048 return attribute_to_tensor(attr, shape, nb::find(&self));
1050 logger().warn(
"Mesh is not regular, returning the flattened facets.");
1051 const auto& attr = self.get_corner_to_vertex();
1052 return attribute_to_tensor(attr, nb::find(&self));
1055 [](MeshType& self, Tensor<Index> tensor) {
1056 auto [values, shape, stride] = tensor_to_span(tensor);
1059 const size_t num_facets = shape.size() == 1 ? 1 : shape[0];
1060 const size_t vertex_per_facet = shape.size() == 1 ? shape[0] : shape[1];
1061 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1062 auto id = self.wrap_as_facets(
1064 static_cast<Index
>(num_facets),
1065 static_cast<Index
>(vertex_per_facet));
1066 auto& attr = self.template ref_attribute<Index>(
id);
1069 "Facets of the mesh.");
1070 surface_mesh_class.def_prop_ro(
1072 [](MeshType& self) {
1073 self.initialize_edges();
1075 std::vector<Index> data(num_edges * 2);
1076 tbb::parallel_for(Index{0}, num_edges, [&](Index i) {
1079 data[i * 2 + 1] = v1;
1081 nb::ndarray<Index, nb::shape<-1, 2>, nb::numpy, nb::c_contig, nb::device::cpu> edges(
1083 {static_cast<size_t>(num_edges), 2});
1084 return edges.cast();
1086 "Edges of the mesh.");
1087 surface_mesh_class.def(
1089 [](MeshType& self, Tensor<Scalar> tensor, Index num_vertices) {
1090 auto [values, shape, stride] = tensor_to_span(tensor);
1094 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1095 auto id = self.wrap_as_vertices(
1098 auto& attr = self.template ref_attribute<Scalar>(
id);
1104 R
"(Wrap a tensor as vertices.
1106:param tensor: The tensor to wrap.
1107:param num_vertices: Number of vertices.
1109:return: The id of the wrapped vertices attribute.)");
1110 surface_mesh_class.def(
1112 [](MeshType& self, Tensor<Index> tensor, Index num_facets, Index vertex_per_facet) {
1113 auto [values, shape, stride] = tensor_to_span(tensor);
1116 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1117 auto id = self.wrap_as_facets(
1121 auto& attr = self.template ref_attribute<Index>(
id);
1127 "vertex_per_facet"_a,
1128 R
"(Wrap a tensor as a list of regular facets.
1130:param tensor: The tensor to wrap.
1131:param num_facets: Number of facets.
1132:param vertex_per_facet: Number of vertices per facet.
1134:return: The id of the wrapped facet attribute.)");
1135 surface_mesh_class.def(
1138 Tensor<Index> offsets,
1140 Tensor<Index> facets,
1141 Index num_corners) {
1142 auto [offsets_data, offsets_shape, offsets_stride] = tensor_to_span(offsets);
1143 auto [facets_data, facets_shape, facets_stride] = tensor_to_span(facets);
1147 auto offsets_owner = std::make_shared<nb::object>(nb::find(offsets));
1148 auto facets_owner = std::make_shared<nb::object>(nb::find(facets));
1150 auto id = self.wrap_as_facets(
1155 auto& attr = self.template ref_attribute<Index>(
id);
1163 R
"(Wrap a tensor as a list of hybrid facets.
1165:param offsets: The offset indices into the facets array.
1166:param num_facets: Number of facets.
1167:param facets: The indices of the vertices of the facets.
1168:param num_corners: Number of corners.
1170:return: The id of the wrapped facet attribute.)");
1171 surface_mesh_class.def_static(
1172 "attr_name_is_reserved",
1173 &MeshType::attr_name_is_reserved,
1175 R
"(Check if an attribute name is reserved.
1177:param name: attribute name to check
1179:returns: True if the name is reserved, False otherwise)");
1180 surface_mesh_class.def_prop_ro_static(
1181 "attr_name_vertex_to_position",
1182 [](nb::handle) {
return MeshType::attr_name_vertex_to_position(); },
1183 "The name of the attribute that stores the vertex positions.");
1184 surface_mesh_class.def_prop_ro_static(
1185 "attr_name_corner_to_vertex",
1186 [](nb::handle) {
return MeshType::attr_name_corner_to_vertex(); },
1187 "The name of the attribute that stores the corner to vertex mapping.");
1188 surface_mesh_class.def_prop_ro_static(
1189 "attr_name_facet_to_first_corner",
1190 [](nb::handle) {
return MeshType::attr_name_facet_to_first_corner(); },
1191 "The name of the attribute that stores the facet to first corner mapping.");
1192 surface_mesh_class.def_prop_ro_static(
1193 "attr_name_corner_to_facet",
1194 [](nb::handle) {
return MeshType::attr_name_corner_to_facet(); },
1195 "The name of the attribute that stores the corner to facet mapping.");
1196 surface_mesh_class.def_prop_ro_static(
1197 "attr_name_corner_to_edge",
1198 [](nb::handle) {
return MeshType::attr_name_corner_to_edge(); },
1199 "The name of the attribute that stores the corner to edge mapping.");
1200 surface_mesh_class.def_prop_ro_static(
1201 "attr_name_edge_to_first_corner",
1202 [](nb::handle) {
return MeshType::attr_name_edge_to_first_corner(); },
1203 "The name of the attribute that stores the edge to first corner mapping.");
1204 surface_mesh_class.def_prop_ro_static(
1205 "attr_name_next_corner_around_edge",
1206 [](nb::handle) {
return MeshType::attr_name_next_corner_around_edge(); },
1207 "The name of the attribute that stores the next corner around edge mapping.");
1208 surface_mesh_class.def_prop_ro_static(
1209 "attr_name_vertex_to_first_corner",
1210 [](nb::handle) {
return MeshType::attr_name_vertex_to_first_corner(); },
1211 "The name of the attribute that stores the vertex to first corner mapping.");
1212 surface_mesh_class.def_prop_ro_static(
1213 "attr_name_next_corner_around_vertex",
1214 [](nb::handle) {
return MeshType::attr_name_next_corner_around_vertex(); },
1215 "The name of the attribute that stores the next corner around vertex mapping.");
1216 surface_mesh_class.def_prop_ro(
1217 "attr_id_vertex_to_position",
1218 &MeshType::attr_id_vertex_to_position);
1219 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_vertex", &MeshType::attr_id_corner_to_vertex);
1220 surface_mesh_class.def_prop_ro(
1221 "attr_id_facet_to_first_corner",
1222 &MeshType::attr_id_facet_to_first_corner);
1223 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_facet", &MeshType::attr_id_corner_to_facet);
1224 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_edge", &MeshType::attr_id_corner_to_edge);
1225 surface_mesh_class.def_prop_ro(
1226 "attr_id_edge_to_first_corner",
1227 &MeshType::attr_id_edge_to_first_corner);
1228 surface_mesh_class.def_prop_ro(
1229 "attr_id_next_corner_around_edge",
1230 &MeshType::attr_id_next_corner_around_edge);
1231 surface_mesh_class.def_prop_ro(
1232 "attr_id_vertex_to_first_corner",
1233 &MeshType::attr_id_vertex_to_first_corner);
1234 surface_mesh_class.def_prop_ro(
1235 "attr_id_next_corner_around_vertex",
1236 &MeshType::attr_id_next_corner_around_vertex);
1237 surface_mesh_class.def(
1239 [](MeshType& self, std::optional<Tensor<Index>> tensor) {
1240 if (tensor.has_value()) {
1241 auto [edge_data, edge_shape, edge_stride] = tensor_to_span(tensor.value());
1245 "Edge tensor mush be of the shape num_edges x 2");
1246 self.initialize_edges(edge_data);
1248 self.initialize_edges();
1251 "edges"_a = nb::none(),
1252 R
"(Initialize the edges.
1254The `edges` tensor provides a predefined ordering of the edges.
1255If not provided, the edges are initialized in an arbitrary order.
1257:param edges: M x 2 tensor of predefined edge vertex indices, where M is the number of edges.)");
1258 surface_mesh_class.def(
1260 &MeshType::clear_edges,
1261 R
"(Clear all edge connectivity information.)");
1262 surface_mesh_class.def_prop_ro("has_edges", &MeshType::has_edges);
1263 surface_mesh_class.def(
1268 R
"(Get the edge index associated with a local vertex of a facet.
1270:param facet_id: facet index
1271:param lv: local vertex index of the facet
1273:returns: edge index)");
1274 surface_mesh_class.def(
1276 &MeshType::get_corner_edge,
1278 R
"(Get the edge index associated with a corner.
1280:param corner_id: corner index
1282:returns: edge index)");
1283 surface_mesh_class.def(
1284 "get_edge_vertices",
1287 R
"(Get the two vertex indices of an edge.
1289:param edge_id: edge index
1291:returns: tuple of (vertex1_id, vertex2_id))");
1292 surface_mesh_class.def(
1293 "find_edge_from_vertices",
1297 R
"(Find the edge connecting two vertices.
1299:param vertex1_id: first vertex index
1300:param vertex2_id: second vertex index
1302:returns: edge index, or invalid_index if no such edge exists)");
1303 surface_mesh_class.def(
1304 "get_first_corner_around_edge",
1305 &MeshType::get_first_corner_around_edge,
1307 R
"(Get the first corner around an edge.
1309:param edge_id: edge index
1311:returns: first corner index around the edge)");
1312 surface_mesh_class.def(
1313 "get_next_corner_around_edge",
1314 &MeshType::get_next_corner_around_edge,
1316 R
"(Get the next corner around the same edge.
1318:param corner_id: current corner index
1320:returns: next corner index around the same edge)");
1321 surface_mesh_class.def(
1322 "get_first_corner_around_vertex",
1323 &MeshType::get_first_corner_around_vertex,
1325 R
"(Get the first corner around a vertex.
1327:param vertex_id: vertex index
1329:returns: first corner index around the vertex)");
1330 surface_mesh_class.def(
1331 "get_next_corner_around_vertex",
1332 &MeshType::get_next_corner_around_vertex,
1334 R
"(Get the next corner around the same vertex.
1336:param corner_id: current corner index
1338:returns: next corner index around the same vertex)");
1339 surface_mesh_class.def(
1340 "count_num_corners_around_edge",
1341 &MeshType::count_num_corners_around_edge,
1343 R
"(Count the number of corners around an edge.
1345:param edge_id: edge index
1347:returns: number of corners around the edge)");
1348 surface_mesh_class.def(
1349 "count_num_corners_around_vertex",
1350 &MeshType::count_num_corners_around_vertex,
1352 R
"(Count the number of corners around a vertex.
1354:param vertex_id: vertex index
1356:returns: number of corners around the vertex)");
1357 surface_mesh_class.def(
1358 "get_counterclockwise_corner_around_vertex",
1359 &MeshType::get_counterclockwise_corner_around_vertex,
1361 R
"(Get the counterclockwise corner around the vertex associated with the input corner.
1364 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1365 corners based on edge-connectivity) will be visited.
1367 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1368 is not well defined. It will return `invalid_index` in this case.
1370:param corner: The input corner index.
1372:returns: The counterclockwise corner index or `invalid_index` if none exists.)");
1373 surface_mesh_class.def(
1374 "get_clockwise_corner_around_vertex",
1375 &MeshType::get_clockwise_corner_around_vertex,
1377 R
"(Get the clockwise corner around the vertex associated with the input corner.
1380 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1381 corners based on edge-connectivity) will be visited.
1383 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1384 is not well defined. It will return `invalid_index` in this case.
1386:param corner: The input corner index.
1388:returns: The clockwise corner index or `invalid_index` if none exists.)");
1389 surface_mesh_class.def(
1390 "get_one_facet_around_edge",
1393 R
"(Get one facet adjacent to an edge.
1395:param edge_id: edge index
1397:returns: facet index adjacent to the edge)");
1398 surface_mesh_class.def(
1399 "get_one_corner_around_edge",
1402 R
"(Get one corner around an edge.
1404:param edge_id: edge index
1406:returns: corner index around the edge)");
1407 surface_mesh_class.def(
1408 "get_one_corner_around_vertex",
1411 R
"(Get one corner around a vertex.
1413:param vertex_id: vertex index
1415:returns: corner index around the vertex)");
1416 surface_mesh_class.def(
1420 R
"(Check if an edge is on the boundary.
1422:param edge_id: edge index
1424:returns: True if the edge is on the boundary, False otherwise)");
1428 MeshType* mesh =
nullptr;
1430 std::vector<AttributeId> get_metadata()
const
1433 lagrange::AttributeMatcher opts;
1434 opts.
usages = AttributeUsage::String;
1440 auto meta_data_class = nb::class_<MetaData>(m,
"MetaData",
"Metadata `dict` of the mesh");
1441 meta_data_class.def(
"__len__", [](
const MetaData& self) {
1442 auto data = self.get_metadata();
1445 meta_data_class.def(
"__getitem__", [](
const MetaData& self, std::string_view key) {
1446 return self.mesh->get_metadata(key);
1448 meta_data_class.def(
1450 [](MetaData& self, std::string_view key, std::string_view value) {
1452 if (self.mesh->has_attribute(key)) {
1453 self.mesh->set_metadata(key, value);
1455 self.mesh->create_metadata(key, value);
1458 meta_data_class.def(
"__delitem__", [](MetaData& self, std::string_view key) {
1460 self.mesh->delete_attribute(key);
1462 meta_data_class.def(
"__repr__", [](
const MetaData& self) -> std::string {
1463 auto data = self.get_metadata();
1464 if (data.empty())
return "MetaData({})";
1467 for (
auto id : data) {
1468 auto name = self.mesh->get_attribute_name(
id);
1469 auto value = self.mesh->get_metadata(
id);
1470 fmt::format_to(std::back_inserter(r),
" {}: {},\n", name, value);
1472 return fmt::format(
"MetaData(\n{})", r);
1475 surface_mesh_class.def_prop_ro(
1477 [](MeshType& self) {
1479 meta_data.mesh = &self;
1482 "Metadata of the mesh.");
1484 surface_mesh_class.def(
1485 "get_matching_attribute_ids",
1487 std::optional<AttributeElement> element,
1488 std::optional<AttributeUsage> usage,
1489 Index num_channels) {
1490 AttributeMatcher opts;
1491 if (usage.has_value()) {
1492 opts.
usages = usage.value();
1494 if (element.has_value()) {
1500 "element"_a = nb::none(),
1501 "usage"_a = nb::none(),
1502 "num_channels"_a = 0,
1503 R
"(Get all matching attribute ids with the desired element type, usage and number of channels.
1505:param element: The target element type. None matches all element types.
1506:param usage: The target usage type. None matches all usage types.
1507:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1509:returns: A list of attribute ids matching the target element, usage and number of channels.
1512 surface_mesh_class.def(
1513 "get_matching_attribute_id",
1515 std::optional<AttributeElement> element,
1516 std::optional<AttributeUsage> usage,
1517 Index num_channels) {
1518 std::optional<AttributeId> result;
1519 self.seq_foreach_attribute_id([&](
AttributeId attr_id) {
1520 if (result.has_value()) {
1523 const auto name = self.get_attribute_name(attr_id);
1524 if (self.attr_name_is_reserved(name))
return;
1525 const auto& attr = self.get_attribute_base(attr_id);
1526 if (element && attr.get_element_type() != *element)
return;
1527 if (usage && attr.get_usage() != *usage)
return;
1528 if (num_channels != 0 && attr.get_num_channels() != num_channels)
return;
1533 "element"_a = nb::none(),
1534 "usage"_a = nb::none(),
1535 "num_channels"_a = 0,
1536 R
"(Get one matching attribute id with the desired element type, usage and number of channels.
1538:param element: The target element type. None matches all element types.
1539:param usage: The target usage type. None matches all usage types.
1540:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1542:returns: An attribute id matching the target element, usage and number of channels, if found. None otherwise.
1545 surface_mesh_class.def(
1547 [](MeshType& self) -> MeshType {
1548 MeshType mesh = self;
1551 R
"(Create a shallow copy of this mesh.)");
1553 surface_mesh_class.def(
1555 [](MeshType& self, [[maybe_unused]] std::optional<nb::dict> memo) -> MeshType {
1556 MeshType mesh = self;
1562 using AttributeType = std::decay_t<
decltype(attr)>;
1563 if constexpr (AttributeType::IsIndexed) {
1564 auto& value_attr = attr.values();
1565 if (value_attr.is_external()) {
1566 value_attr.create_internal_copy();
1568 auto& index_attr = attr.indices();
1569 if (index_attr.is_external()) {
1570 index_attr.create_internal_copy();
1573 if (attr.is_external()) {
1574 attr.create_internal_copy();
1580 "memo"_a = nb::none(),
1581 R
"(Create a deep copy of this mesh.)");
1583 surface_mesh_class.def(
1585 [](MeshType& self,
bool strip) -> MeshType {
1587 return MeshType::stripped_copy(self);
1589 auto py_self = nb::find(self);
1590 return nb::cast<MeshType>(py_self.attr(
"__deepcopy__")());
1594 R
"(Create a deep copy of this mesh.
1596: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
const AttributeBase & get_attribute_base(std::string_view name) const
Gets a read-only reference to the base class of attribute given its name.
Definition SurfaceMesh.cpp:1270
Definition PyAttribute.h:24
Definition PyIndexedAttribute.h:27
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
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
#define LA_ATTRIBUTE_X(mode, data)
X Macro arguments for the Attribute<> class.
Definition AttributeTypes.h:48
@ 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
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:174
#define la_debug_assert(...)
Debug assertion check.
Definition assert.h:194
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