15#include <lagrange/utils/warnoff.h>
16#include <nanobind/nanobind.h>
17#include <nanobind/stl/array.h>
18#include <nanobind/stl/optional.h>
19#include <nanobind/stl/string.h>
20#include <nanobind/stl/string_view.h>
21#include <nanobind/stl/variant.h>
22#include <lagrange/utils/warnon.h>
25#include "PyAttribute.h"
27#include <lagrange/Attribute.h>
28#include <lagrange/AttributeFwd.h>
29#include <lagrange/AttributeTypes.h>
30#include <lagrange/IndexedAttribute.h>
31#include <lagrange/Logger.h>
32#include <lagrange/SurfaceMesh.h>
33#include <lagrange/find_matching_attributes.h>
34#include <lagrange/foreach_attribute.h>
35#include <lagrange/python/tensor_utils.h>
36#include <lagrange/utils/BitField.h>
37#include <lagrange/utils/Error.h>
38#include <lagrange/utils/assert.h>
39#include <lagrange/utils/invalid.h>
41namespace lagrange::python {
43template <
typename Scalar,
typename Index>
44void bind_surface_mesh(nanobind::module_& m)
46 namespace nb = nanobind;
47 using namespace nb::literals;
49 using namespace lagrange::python;
53 auto surface_mesh_class = nb::class_<MeshType>(m,
"SurfaceMesh",
"Surface mesh data structure");
54 surface_mesh_class.def(nb::init<Index>(), nb::arg(
"dimension") = 3);
55 surface_mesh_class.def(
57 [](
MeshType& self, Tensor<Scalar> b) {
58 auto [data, shape, stride] = tensor_to_span(b);
61 self.add_vertex(data);
64 R
"(Add a vertex to the mesh.
66:param vertex: vertex coordinates)");
69 surface_mesh_class.def(
"add_vertex", [](
MeshType& self, nb::list b) {
71 if (self.get_dimension() == 3) {
73 {nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1]), nb::cast<Scalar>(b[2])});
74 }
else if (self.get_dimension() == 2) {
75 self.add_vertex({nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1])});
77 throw std::runtime_error(
"Dimension mismatch in vertex tensor");
81 surface_mesh_class.def(
"add_vertices", [](
MeshType& self, Tensor<Scalar> b) {
82 auto [data, shape, stride] = tensor_to_span(b);
85 self.add_vertices(
static_cast<Index
>(shape[0]), data);
89 surface_mesh_class.def(
"add_triangle", &MeshType::add_triangle);
90 surface_mesh_class.def(
"add_triangles", [](
MeshType& self, Tensor<Index> b) {
91 auto [data, shape, stride] = tensor_to_span(b);
94 self.add_triangles(
static_cast<Index
>(shape[0]), data);
96 surface_mesh_class.def(
"add_quad", &MeshType::add_quad);
97 surface_mesh_class.def(
"add_quads", [](
MeshType& self, Tensor<Index> b) {
98 auto [data, shape, stride] = tensor_to_span(b);
101 self.add_quads(
static_cast<Index
>(shape[0]), data);
103 surface_mesh_class.def(
"add_polygon", [](
MeshType& self, Tensor<Index> b) {
104 auto [data, shape, stride] = tensor_to_span(b);
107 self.add_polygon(data);
109 surface_mesh_class.def(
"add_polygons", [](
MeshType& self, Tensor<Index> b) {
110 auto [data, shape, stride] = tensor_to_span(b);
112 self.add_polygons(
static_cast<Index
>(shape[0]),
static_cast<Index
>(shape[1]), data);
114 surface_mesh_class.def(
116 [](
MeshType& self, Tensor<Index> sizes, Tensor<Index> indices) {
117 auto [size_data, size_shape, size_stride] = tensor_to_span(sizes);
121 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
125 self.add_hybrid(size_data, index_data);
127 surface_mesh_class.def(
"remove_vertices", [](
MeshType& self, Tensor<Index> b) {
128 auto [data, shape, stride] = tensor_to_span(b);
131 self.remove_vertices(data);
133 surface_mesh_class.def(
136 std::vector<Index> indices;
138 indices.push_back(nb::cast<Index>(i));
140 self.remove_vertices(indices);
143 R
"(Remove selected vertices from the mesh.
145:param vertices: list of vertex indices to remove)");
146 surface_mesh_class.def("remove_facets", [](
MeshType& self, Tensor<Index> b) {
147 auto [data, shape, stride] = tensor_to_span(b);
150 self.remove_facets(data);
152 surface_mesh_class.def(
155 std::vector<Index> indices;
157 indices.push_back(nb::cast<Index>(i));
159 self.remove_facets(indices);
162 R
"(Remove selected facets from the mesh.
164:param facets: list of facet indices to remove)");
165 surface_mesh_class.def("clear_vertices", &MeshType::clear_vertices);
166 surface_mesh_class.def(
"clear_facets", &MeshType::clear_facets);
167 surface_mesh_class.def(
"shrink_to_fit", &MeshType::shrink_to_fit);
168 surface_mesh_class.def(
"compress_if_regular", &MeshType::compress_if_regular);
169 surface_mesh_class.def_prop_ro(
"is_triangle_mesh", &MeshType::is_triangle_mesh);
170 surface_mesh_class.def_prop_ro(
"is_quad_mesh", &MeshType::is_quad_mesh);
171 surface_mesh_class.def_prop_ro(
"is_regular", &MeshType::is_regular);
172 surface_mesh_class.def_prop_ro(
"is_hybrid", &MeshType::is_hybrid);
173 surface_mesh_class.def_prop_ro(
"dimension", &MeshType::get_dimension);
174 surface_mesh_class.def_prop_ro(
"vertex_per_facet", &MeshType::get_vertex_per_facet);
175 surface_mesh_class.def_prop_ro(
"num_vertices", &MeshType::get_num_vertices);
176 surface_mesh_class.def_prop_ro(
"num_facets", &MeshType::get_num_facets);
177 surface_mesh_class.def_prop_ro(
"num_corners", &MeshType::get_num_corners);
179 surface_mesh_class.def(
"get_position", [](
MeshType& self, Index i) {
180 return span_to_tensor(self.get_position(i), nb::find(&self));
182 surface_mesh_class.def(
"ref_position", [](
MeshType& self, Index i) {
183 return span_to_tensor(self.ref_position(i), nb::find(&self));
185 surface_mesh_class.def(
"get_facet_size", &MeshType::get_facet_size);
186 surface_mesh_class.def(
"get_facet_vertex", &MeshType::get_facet_vertex);
187 surface_mesh_class.def(
"get_facet_corner_begin", &MeshType::get_facet_corner_begin);
188 surface_mesh_class.def(
"get_facet_corner_end", &MeshType::get_facet_corner_end);
189 surface_mesh_class.def(
"get_corner_vertex", &MeshType::get_corner_vertex);
190 surface_mesh_class.def(
"get_corner_facet", &MeshType::get_corner_facet);
191 surface_mesh_class.def(
"get_facet_vertices", [](
MeshType& self, Index f) {
192 return span_to_tensor(self.get_facet_vertices(f), nb::find(&self));
194 surface_mesh_class.def(
"ref_facet_vertices", [](
MeshType& self, Index f) {
195 return span_to_tensor(self.ref_facet_vertices(f), nb::find(&self));
197 surface_mesh_class.def(
"get_attribute_id", &MeshType::get_attribute_id);
198 surface_mesh_class.def(
"get_attribute_name", &MeshType::get_attribute_name);
199 surface_mesh_class.def(
202 std::string_view name,
203 std::variant<std::monostate, AttributeElement, std::string_view> element,
204 std::variant<std::monostate, AttributeUsage, std::string_view> usage,
205 std::variant<std::monostate, GenericTensor, nb::list> initial_values,
206 std::variant<std::monostate, Tensor<Index>, GenericTensor, nb::list> initial_indices,
207 std::optional<Index> num_channels,
208 std::optional<nb::type_object> dtype) {
209 const bool with_initial_values = initial_values.index() != 0;
210 const bool with_initial_indices = initial_indices.index() != 0;
213 Index n = invalid<Index>();
214 if (num_channels.has_value()) {
215 n = num_channels.value();
216 }
else if (with_initial_values) {
217 if (initial_values.index() == 1) {
218 const auto& values = std::get<GenericTensor>(initial_values);
220 values.ndim() == 1 || values.ndim() == 2,
221 "Only vector or matrix are accepted as initial values.");
222 n = values.ndim() == 1 ? 1 :
static_cast<Index
>(values.shape(1));
223 }
else if (initial_values.index() == 2) {
227 throw nb::type_error(
"Either number of channels or initial values are required!");
234 using T = std::decay_t<
decltype(value)>;
235 if constexpr (std::is_same_v<T, AttributeElement>) {
237 }
else if (with_initial_indices) {
239 }
else if constexpr (std::is_same_v<T, std::string_view>) {
240 if (value ==
"Vertex") {
242 }
else if (value ==
"Facet") {
244 }
else if (value ==
"Edge") {
246 }
else if (value ==
"Corner") {
248 }
else if (value ==
"Value") {
250 }
else if (value ==
"Indexed") {
253 throw nb::type_error(
"Invalid element type!");
255 }
else if (with_initial_values) {
257 const Index num_vertices = self.get_num_vertices();
258 const Index num_facets = self.get_num_facets();
259 const Index num_corners = self.get_num_corners();
260 const Index num_edges =
263 Index num_rows = invalid<Index>();
264 if (initial_values.index() == 1) {
265 const auto& values = std::get<GenericTensor>(initial_values);
266 num_rows = values.shape(0);
267 }
else if (initial_values.index() == 2) {
268 const auto& values = std::get<nb::list>(initial_values);
269 num_rows = nb::len(values);
273 if (num_rows == num_vertices) {
275 num_rows != num_facets,
276 "Cannot infer attribute element due to ambiguity: vertices vs "
279 num_rows != num_edges,
280 "Cannot infer attribute element due to ambiguity: vertices vs "
283 num_rows != num_corners,
284 "Cannot infer attribute element due to ambiguity: vertices vs "
287 }
else if (num_rows == num_facets) {
289 num_rows != num_edges,
290 "Cannot infer attribute element due to ambiguity: facets vs "
293 num_rows != num_corners,
294 "Cannot infer attribute element due to ambiguity: facets vs "
297 }
else if (num_rows == num_corners) {
299 num_rows != num_edges,
300 "Cannot infer attribute element due to ambiguity: corners vs "
303 }
else if (num_rows == num_edges) {
306 throw nb::type_error(
307 "Cannot infer attribute element type from initial_values!");
310 throw nb::type_error(
"Invalid element type!");
319 using T = std::decay_t<
decltype(value)>;
320 if constexpr (std::is_same_v<T, AttributeUsage>) {
322 }
else if constexpr (std::is_same_v<T, std::string_view>) {
323 if (value ==
"Vector") {
325 }
else if (value ==
"Scalar") {
327 }
else if (value ==
"Position") {
329 }
else if (value ==
"Normal") {
331 }
else if (value ==
"Tangent") {
333 }
else if (value ==
"Bitangent") {
335 }
else if (value ==
"Color") {
337 }
else if (value ==
"UV") {
339 }
else if (value ==
"VertexIndex") {
341 }
else if (value ==
"FacetIndex") {
343 }
else if (value ==
"CornerIndex") {
345 }
else if (value ==
"EdgeIndex") {
348 throw nb::type_error(
"Invalid usage type!");
360 auto create_attribute = [&](
auto values) {
361 using ValueType =
typename std::decay_t<
decltype(values)>::element_type;
365 std::vector<Index> index_storage;
368 if (with_initial_values) {
369 init_values = values;
373 num_channels.has_value(),
374 "Number of channels is required when initial values are not provided!");
377 "dtype is required when initial values are not provided!");
381 if (
const Tensor<Index>* tensor_ptr =
382 std::get_if<Tensor<Index>>(&initial_indices)) {
384 const auto& indices = *tensor_ptr;
385 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
387 init_indices = index_data;
389 GenericTensor* generic_tensor_ptr =
390 std::get_if<GenericTensor>(&initial_indices)) {
392 auto& indices = *generic_tensor_ptr;
393 index_storage.resize(indices.size());
395#define LA_X_create_attribute_index(_, IndexType) \
396 if (indices.dtype() == nb::dtype<IndexType>()) { \
397 auto view = indices.template view<IndexType, nb::ndim<1>>(); \
398 std::copy(view.data(), view.data() + indices.size(), index_storage.begin()); \
401#undef LA_X_create_attribute_index
402 init_indices =
span<Index>(index_storage.data(), index_storage.size());
403 }
else if (
const nb::list* list_ptr = std::get_if<nb::list>(&initial_indices)) {
405 const nb::list& py_list = *list_ptr;
406 index_storage = nb::cast<std::vector<Index>>(py_list);
407 init_indices =
span<Index>(index_storage.begin(), index_storage.size());
410 return self.template create_attribute<ValueType>(
420 if (
const GenericTensor* tensor_ptr = std::get_if<GenericTensor>(&initial_values)) {
421 const auto& values = *tensor_ptr;
422#define LA_X_create_attribute(_, ValueType) \
423 if (values.dtype() == nb::dtype<ValueType>()) { \
424 Tensor<ValueType> local_values(values.handle()); \
425 auto [value_data, value_shape, value_stride] = tensor_to_span(local_values); \
426 la_runtime_assert(is_dense(value_shape, value_stride)); \
427 if (num_channels.has_value()) { \
428 Index nn = value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]); \
429 la_runtime_assert(nn == n, "Number of channels does not match initial_values"); \
431 return create_attribute(value_data); \
434#undef LA_X_create_attribute
435 }
else if (
const nb::list* list_ptr = std::get_if<nb::list>(&initial_values)) {
436 auto values = nb::cast<std::vector<double>>(*list_ptr);
437 return create_attribute(
span<double>(values.data(), values.size()));
438 }
else if (dtype.has_value()) {
439 const auto& t = dtype.value();
440 auto np = nb::module_::import_(
"numpy");
441 if (t.is(&PyFloat_Type)) {
444 return create_attribute(local_values);
445 }
else if (t.is(np.attr(
"float32"))) {
447 return create_attribute(local_values);
448 }
else if (t.is(np.attr(
"float64"))) {
450 return create_attribute(local_values);
451 }
else if (t.is(np.attr(
"int8"))) {
453 return create_attribute(local_values);
454 }
else if (t.is(np.attr(
"int16"))) {
456 return create_attribute(local_values);
457 }
else if (t.is(np.attr(
"int32"))) {
459 return create_attribute(local_values);
460 }
else if (t.is(np.attr(
"int64"))) {
462 return create_attribute(local_values);
463 }
else if (t.is(np.attr(
"uint8"))) {
465 return create_attribute(local_values);
466 }
else if (t.is(np.attr(
"uint16"))) {
468 return create_attribute(local_values);
469 }
else if (t.is(np.attr(
"uint32"))) {
471 return create_attribute(local_values);
472 }
else if (t.is(np.attr(
"uint64"))) {
474 return create_attribute(local_values);
477 throw nb::type_error(
"`initial_values` and `dtype` cannot both be None!");
480 "element"_a = nb::none(),
481 "usage"_a = nb::none(),
482 "initial_values"_a = nb::none(),
483 "initial_indices"_a = nb::none(),
484 "num_channels"_a = nb::none(),
485 "dtype"_a = nb::none(),
487 "def create_attribute(self, "
489 "element: typing.Union[AttributeElement, "
491 "'Vertex', 'Facet', 'Edge', 'Corner', 'Value', 'Indexed'"
493 "usage: typing.Union[AttributeUsage, "
495 "'Vector', 'Scalar', 'Position', 'Normal', 'Tangent', 'Bitangent', 'Color', 'UV', "
496 "'VertexIndex', 'FacetIndex', 'CornerIndex', 'EdgeIndex'"
498 "initial_values: typing.Union[numpy.typing.NDArray, typing.List[float], None] = None, "
499 "initial_indices: typing.Union[numpy.typing.NDArray, typing.List[int], None] = None, "
500 "num_channels: typing.Optional[int] = None, "
501 "dtype: typing.Optional[numpy.typing.DTypeLike] = None) -> AttributeId"),
502 R
"(Create an attribute.
504:param name: Name of the attribute.
505:param element: Element type of the attribute. If None, derive from the shape of initial values.
506:param usage: Usage type of the attribute. If None, derive from the shape of initial values or the number of channels.
507:param initial_values: Initial values of the attribute.
508:param initial_indices: Initial indices of the attribute (Indexed attribute only).
509:param num_channels: Number of channels of the attribute.
510:param dtype: Data type of the attribute.
512:returns: The id of the created attribute.
515 If `element` is None, it will be derived based on the cardinality of the mesh elements.
516 If there is an ambiguity, an exception will be raised.
517 In addition, explicit `element` specification is required for value attributes.
520 If `usage` is None, it will be derived based on the shape of `initial_values` or `num_channels` if specified.)");
522 surface_mesh_class.def(
523 "create_attribute_from",
524 &MeshType::template create_attribute_from<Scalar, Index>,
527 "source_name"_a =
"",
528 R
"(Shallow copy an attribute from another mesh.
530:param name: Name of the attribute.
531:param source_mesh: Source mesh.
532:param source_name: Name of the attribute in the source mesh. If empty, use the same name as `name`.
534:returns: The id of the created attribute.)");
536 surface_mesh_class.def(
539 std::string_view name,
542 GenericTensor values) {
543 auto wrap_as_attribute = [&](
auto tensor) {
544 using ValueType =
typename std::decay_t<
decltype(tensor)>::Scalar;
545 auto [data, shape, stride] = tensor_to_span(tensor);
547 Index num_channels = shape.size() == 1 ? 1 :
static_cast<Index
>(shape[1]);
549 auto owner = std::make_shared<nb::object>(nb::find(values));
550 if constexpr (std::is_const_v<ValueType>) {
551 id = self.wrap_as_const_attribute(
558 id = self.wrap_as_attribute(
565 auto& attr = self.template ref_attribute<ValueType>(
id);
570#define LA_X_wrap_as_attribute(_, ValueType) \
571 if (values.dtype() == nb::dtype<ValueType>()) { \
572 Tensor<ValueType> local_values(values.handle()); \
573 return wrap_as_attribute(local_values); \
576#undef LA_X_wrap_as_attribute
577 throw nb::type_error(
"Unsupported value type!");
583 R
"(Wrap an existing numpy array as an attribute.
585:param name: Name of the attribute.
586:param element: Element type of the attribute.
587:param usage: Usage type of the attribute.
588:param values: Values of the attribute.
590:returns: The id of the created attribute.)");
591 surface_mesh_class.def(
592 "wrap_as_indexed_attribute",
594 std::string_view name,
596 GenericTensor values,
597 Tensor<Index> indices) {
598 auto wrap_as_indexed_attribute = [&](
auto value_tensor,
auto index_tensor) {
599 using ValueType =
typename std::decay_t<
decltype(value_tensor)>::Scalar;
600 auto [value_data, value_shape, value_stride] = tensor_to_span(value_tensor);
601 auto [index_data, index_shape, index_stride] = tensor_to_span(index_tensor);
604 Index num_values =
static_cast<Index
>(value_shape[0]);
606 value_shape.size() == 1 ? 1 :
static_cast<Index
>(value_shape[1]);
609 auto value_owner = std::make_shared<nb::object>(nb::find(values));
610 auto index_owner = std::make_shared<nb::object>(nb::find(indices));
612 if constexpr (std::is_const_v<ValueType>) {
613 id = self.wrap_as_const_indexed_attribute(
621 id = self.wrap_as_indexed_attribute(
629 auto& attr = self.template ref_indexed_attribute<ValueType>(
id);
635#define LA_X_wrap_as_indexed_attribute(_, ValueType) \
636 if (values.dtype() == nb::dtype<ValueType>()) { \
637 Tensor<ValueType> local_values(values.handle()); \
638 return wrap_as_indexed_attribute(local_values, indices); \
641#undef LA_X_wrap_as_indexed_attribute
642 throw nb::type_error(
"Unsupported value type!");
648 R
"(Wrap an existing numpy array as an indexed attribute.
650:param name: Name of the attribute.
651:param usage: Usage type of the attribute.
652:param values: Values of the attribute.
653:param indices: Indices of the attribute.
655:returns: The id of the created attribute.)");
656 surface_mesh_class.def("duplicate_attribute", &MeshType::duplicate_attribute);
657 surface_mesh_class.def(
"rename_attribute", &MeshType::rename_attribute);
658 surface_mesh_class.def(
660 &MeshType::delete_attribute,
663 surface_mesh_class.def(
665 [](
MeshType& self, std::string_view name) { self.delete_attribute(name); },
667 R
"(Delete an attribute by name.
669:param name: Name of the attribute.)");
670 surface_mesh_class.def(
672 [](
MeshType& self,
AttributeId id) { self.delete_attribute(self.get_attribute_name(
id)); },
674 R
"(Delete an attribute by id.
676:param id: Id of the attribute.)");
677 surface_mesh_class.def("has_attribute", &MeshType::has_attribute);
678 surface_mesh_class.def(
679 "is_attribute_indexed",
681 surface_mesh_class.def(
682 "is_attribute_indexed",
683 static_cast<bool (
MeshType::*)(std::string_view) const
>(&MeshType::is_attribute_indexed));
688#define LA_X_trigger_copy_on_write(_, ValueType) \
689 if (mesh.is_attribute_indexed(id)) { \
690 if (dynamic_cast<const IndexedAttribute<ValueType, Index>*>(&attr_base)) { \
691 [[maybe_unused]] auto& attr = mesh.template ref_indexed_attribute<ValueType>(id); \
694 if (dynamic_cast<const Attribute<ValueType>*>(&attr_base)) { \
695 [[maybe_unused]] auto& attr = mesh.template ref_attribute<ValueType>(id); \
699#undef LA_X_trigger_copy_on_write
702 surface_mesh_class.def(
706 !self.is_attribute_indexed(
id),
708 "Attribute {} is indexed! Please use `indexed_attribute` property "
711 if (!sharing) ensure_attribute_is_not_shared(self,
id);
716 R
"(Get an attribute by id.
718:param id: Id of the attribute.
719:param sharing: Whether to allow sharing the attribute with other meshes.
721:returns: The attribute.)");
722 surface_mesh_class.def(
724 [&](
MeshType& self, std::string_view name,
bool sharing) {
726 !self.is_attribute_indexed(name),
728 "Attribute \"{}\" is indexed! Please use `indexed_attribute` property "
731 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
736 R
"(Get an attribute by name.
738:param name: Name of the attribute.
739:param sharing: Whether to allow sharing the attribute with other meshes.
741:return: The attribute.)");
742 surface_mesh_class.def(
746 self.is_attribute_indexed(
id),
748 "Attribute {} is not indexed! Please use `attribute` property instead.",
750 if (!sharing) ensure_attribute_is_not_shared(self,
id);
755 R
"(Get an indexed attribute by id.
757:param id: Id of the attribute.
758:param sharing: Whether to allow sharing the attribute with other meshes.
760:returns: The indexed attribute.)");
761 surface_mesh_class.def(
763 [&](
MeshType& self, std::string_view name,
bool sharing) {
765 self.is_attribute_indexed(name),
767 "Attribute \"{}\"is not indexed! Please use `attribute` property instead.",
769 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
774 R
"(Get an indexed attribute by name.
776:param name: Name of the attribute.
777:param sharing: Whether to allow sharing the attribute with other meshes.
779:returns: The indexed attribute.)");
781 auto ptr = self._get_attribute_ptr(
id);
782 return ptr.use_count();
784 surface_mesh_class.def_prop_rw(
787 const auto& attr = self.get_vertex_to_position();
788 return attribute_to_tensor(attr, nb::find(&self));
790 [](
MeshType& self, Tensor<Scalar> tensor) {
791 auto [values, shape, stride] = tensor_to_span(tensor);
795 size_t num_vertices = shape.size() == 1 ? 1 : shape[0];
796 auto owner = std::make_shared<nb::object>(nb::find(tensor));
797 auto id = self.wrap_as_vertices(
799 static_cast<Index
>(num_vertices));
800 auto& attr = self.template ref_attribute<Scalar>(
id);
803 "Vertices of the mesh.");
804 surface_mesh_class.def_prop_rw(
807 if (self.is_regular()) {
808 const auto& attr = self.get_corner_to_vertex();
809 const size_t shape[2] = {
810 static_cast<size_t>(self.get_num_facets()),
811 static_cast<size_t>(self.get_vertex_per_facet())};
812 return attribute_to_tensor(attr, shape, nb::find(&self));
814 logger().warn(
"Mesh is not regular, returning the flattened facets.");
815 const auto& attr = self.get_corner_to_vertex();
816 return attribute_to_tensor(attr, nb::find(&self));
819 [](
MeshType& self, Tensor<Index> tensor) {
820 auto [values, shape, stride] = tensor_to_span(tensor);
823 const size_t num_facets = shape.size() == 1 ? 1 : shape[0];
824 const size_t vertex_per_facet = shape.size() == 1 ? shape[0] : shape[1];
825 auto owner = std::make_shared<nb::object>(nb::find(tensor));
826 auto id = self.wrap_as_facets(
828 static_cast<Index
>(num_facets),
829 static_cast<Index
>(vertex_per_facet));
830 auto& attr = self.template ref_attribute<Scalar>(
id);
833 "Facets of the mesh.");
834 surface_mesh_class.def(
836 [](
MeshType& self, Tensor<Scalar> tensor, Index num_vertices) {
837 auto [values, shape, stride] = tensor_to_span(tensor);
841 auto owner = std::make_shared<nb::object>(nb::find(tensor));
842 auto id = self.wrap_as_vertices(
845 auto& attr = self.template ref_attribute<Scalar>(
id);
851 R
"(Wrap a tensor as vertices.
853:param tensor: The tensor to wrap.
854:param num_vertices: Number of vertices.
856:return: The id of the wrapped vertices attribute.)");
857 surface_mesh_class.def(
859 [](
MeshType& self, Tensor<Index> tensor, Index num_facets, Index vertex_per_facet) {
860 auto [values, shape, stride] = tensor_to_span(tensor);
863 auto owner = std::make_shared<nb::object>(nb::find(tensor));
864 auto id = self.wrap_as_facets(
868 auto& attr = self.template ref_attribute<Scalar>(
id);
874 "vertex_per_facet"_a,
875 R
"(Wrap a tensor as a list of regular facets.
877:param tensor: The tensor to wrap.
878:param num_facets: Number of facets.
879:param vertex_per_facet: Number of vertices per facet.
881:return: The id of the wrapped facet attribute.)");
882 surface_mesh_class.def(
885 Tensor<Index> offsets,
887 Tensor<Index> facets,
889 auto [offsets_data, offsets_shape, offsets_stride] = tensor_to_span(offsets);
890 auto [facets_data, facets_shape, facets_stride] = tensor_to_span(facets);
894 auto offsets_owner = std::make_shared<nb::object>(nb::find(offsets));
895 auto facets_owner = std::make_shared<nb::object>(nb::find(facets));
897 auto id = self.wrap_as_facets(
902 auto& attr = self.template ref_attribute<Scalar>(
id);
910 R
"(Wrap a tensor as a list of hybrid facets.
912:param offsets: The offset indices into the facets array.
913:param num_facets: Number of facets.
914:param facets: The indices of the vertices of the facets.
915:param num_corners: Number of corners.
917:return: The id of the wrapped facet attribute.)");
918 surface_mesh_class.def_static("attr_name_is_reserved", &MeshType::attr_name_is_reserved);
919 surface_mesh_class.def_prop_ro_static(
920 "attr_name_vertex_to_position",
921 [](nb::handle) {
return MeshType::attr_name_vertex_to_position(); },
922 "The name of the attribute that stores the vertex positions.");
923 surface_mesh_class.def_prop_ro_static(
924 "attr_name_corner_to_vertex",
925 [](nb::handle) {
return MeshType::attr_name_corner_to_vertex(); },
926 "The name of the attribute that stores the corner to vertex mapping.");
927 surface_mesh_class.def_prop_ro_static(
928 "attr_name_facet_to_first_corner",
929 [](nb::handle) {
return MeshType::attr_name_facet_to_first_corner(); },
930 "The name of the attribute that stores the facet to first corner mapping.");
931 surface_mesh_class.def_prop_ro_static(
932 "attr_name_corner_to_facet",
933 [](nb::handle) {
return MeshType::attr_name_corner_to_facet(); },
934 "The name of the attribute that stores the corner to facet mapping.");
935 surface_mesh_class.def_prop_ro_static(
936 "attr_name_corner_to_edge",
937 [](nb::handle) {
return MeshType::attr_name_corner_to_edge(); },
938 "The name of the attribute that stores the corner to edge mapping.");
939 surface_mesh_class.def_prop_ro_static(
940 "attr_name_edge_to_first_corner",
941 [](nb::handle) {
return MeshType::attr_name_edge_to_first_corner(); },
942 "The name of the attribute that stores the edge to first corner mapping.");
943 surface_mesh_class.def_prop_ro_static(
944 "attr_name_next_corner_around_edge",
945 [](nb::handle) {
return MeshType::attr_name_next_corner_around_edge(); },
946 "The name of the attribute that stores the next corner around edge mapping.");
947 surface_mesh_class.def_prop_ro_static(
948 "attr_name_vertex_to_first_corner",
949 [](nb::handle) {
return MeshType::attr_name_vertex_to_first_corner(); },
950 "The name of the attribute that stores the vertex to first corner mapping.");
951 surface_mesh_class.def_prop_ro_static(
952 "attr_name_next_corner_around_vertex",
953 [](nb::handle) {
return MeshType::attr_name_next_corner_around_vertex(); },
954 "The name of the attribute that stores the next corner around vertex mapping.");
955 surface_mesh_class.def_prop_ro(
956 "attr_id_vertex_to_position",
957 &MeshType::attr_id_vertex_to_position);
958 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_vertex", &MeshType::attr_id_corner_to_vertex);
959 surface_mesh_class.def_prop_ro(
960 "attr_id_facet_to_first_corner",
961 &MeshType::attr_id_facet_to_first_corner);
962 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_facet", &MeshType::attr_id_corner_to_facet);
963 surface_mesh_class.def_prop_ro(
"attr_id_corner_to_edge", &MeshType::attr_id_corner_to_edge);
964 surface_mesh_class.def_prop_ro(
965 "attr_id_edge_to_first_corner",
966 &MeshType::attr_id_edge_to_first_corner);
967 surface_mesh_class.def_prop_ro(
968 "attr_id_next_corner_around_edge",
969 &MeshType::attr_id_next_corner_around_edge);
970 surface_mesh_class.def_prop_ro(
971 "attr_id_vertex_to_first_corner",
972 &MeshType::attr_id_vertex_to_first_corner);
973 surface_mesh_class.def_prop_ro(
974 "attr_id_next_corner_around_vertex",
975 &MeshType::attr_id_next_corner_around_vertex);
976 surface_mesh_class.def(
978 [](
MeshType& self, std::optional<Tensor<Index>> tensor) {
979 if (tensor.has_value()) {
980 auto [edge_data, edge_shape, edge_stride] = tensor_to_span(tensor.value());
983 edge_data.empty() || check_shape(edge_shape, invalid<size_t>(), 2),
984 "Edge tensor mush be of the shape num_edges x 2");
985 self.initialize_edges(edge_data);
987 self.initialize_edges();
990 "edges"_a = nb::none(),
991 R
"(Initialize the edges.
993The `edges` tensor provides a predefined ordering of the edges.
994If not provided, the edges are initialized in an arbitrary order.
996:param edges: M x 2 tensor of predefined edge vertex indices, where M is the number of edges.)");
997 surface_mesh_class.def("clear_edges", &MeshType::clear_edges);
998 surface_mesh_class.def_prop_ro(
"has_edges", &MeshType::has_edges);
1000 surface_mesh_class.def(
"get_corner_edge", &MeshType::get_corner_edge);
1003 surface_mesh_class.def(
"get_first_corner_around_edge", &MeshType::get_first_corner_around_edge);
1004 surface_mesh_class.def(
"get_next_corner_around_edge", &MeshType::get_next_corner_around_edge);
1005 surface_mesh_class.def(
1006 "get_first_corner_around_vertex",
1007 &MeshType::get_first_corner_around_vertex);
1008 surface_mesh_class.def(
1009 "get_next_corner_around_vertex",
1010 &MeshType::get_next_corner_around_vertex);
1011 surface_mesh_class.def(
1012 "count_num_corners_around_edge",
1013 &MeshType::count_num_corners_around_edge);
1014 surface_mesh_class.def(
1015 "count_num_corners_around_vertex",
1016 &MeshType::count_num_corners_around_vertex);
1017 surface_mesh_class.def(
1018 "get_counterclockwise_corner_around_vertex",
1019 &MeshType::get_counterclockwise_corner_around_vertex,
1021 R
"(Get the counterclockwise corner around the vertex associated with the input corner.
1024 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1025 corners based on edge-connectivity) will be visited.
1027 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1028 is not well defined. It will return `invalid_index` in this case.
1030:param corner: The input corner index.
1032:returns: The counterclockwise corner index or `invalid_index` if none exists.)");
1033 surface_mesh_class.def(
1034 "get_clockwise_corner_around_vertex",
1035 &MeshType::get_clockwise_corner_around_vertex,
1037 R
"(Get the clockwise corner around the vertex associated with the input corner.
1040 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1041 corners based on edge-connectivity) will be visited.
1043 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1044 is not well defined. It will return `invalid_index` in this case.
1046:param corner: The input corner index.
1048:returns: The clockwise corner index or `invalid_index` if none exists.)");
1058 std::vector<AttributeId> get_metadata()
const
1068 auto meta_data_class = nb::class_<MetaData>(m,
"MetaData",
"Metadata `dict` of the mesh");
1069 meta_data_class.def(
"__len__", [](
const MetaData& self) {
1070 auto data = self.get_metadata();
1073 meta_data_class.def(
"__getitem__", [](
const MetaData& self, std::string_view key) {
1074 return self.mesh->get_metadata(key);
1076 meta_data_class.def(
1078 [](MetaData& self, std::string_view key, std::string_view value) {
1080 if (self.mesh->has_attribute(key)) {
1081 self.mesh->set_metadata(key, value);
1083 self.mesh->create_metadata(key, value);
1086 meta_data_class.def(
"__delitem__", [](MetaData& self, std::string_view key) {
1088 self.mesh->delete_attribute(key);
1090 meta_data_class.def(
"__repr__", [](
const MetaData& self) -> std::string {
1091 auto data = self.get_metadata();
1092 if (data.empty())
return "MetaData({})";
1095 for (
auto id : data) {
1096 auto name = self.mesh->get_attribute_name(
id);
1097 auto value = self.mesh->get_metadata(
id);
1098 fmt::format_to(std::back_inserter(r),
" {}: {},\n", name, value);
1100 return fmt::format(
"MetaData(\n{})", r);
1103 surface_mesh_class.def_prop_ro(
1107 meta_data.mesh = &self;
1110 "Metadata of the mesh.");
1112 surface_mesh_class.def(
1113 "get_matching_attribute_ids",
1115 std::optional<AttributeElement> element,
1116 std::optional<AttributeUsage> usage,
1117 Index num_channels) {
1119 if (usage.has_value()) {
1120 opts.
usages = usage.value();
1122 if (element.has_value()) {
1128 "element"_a = nb::none(),
1129 "usage"_a = nb::none(),
1130 "num_channels"_a = 0,
1131 R
"(Get all matching attribute ids with the desired element type, usage and number of channels.
1133:param element: The target element type. None matches all element types.
1134:param usage: The target usage type. None matches all usage types.
1135:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1137:returns: A list of attribute ids matching the target element, usage and number of channels.
1140 surface_mesh_class.def(
1141 "get_matching_attribute_id",
1143 std::optional<AttributeElement> element,
1144 std::optional<AttributeUsage> usage,
1145 Index num_channels) {
1146 std::optional<AttributeId> result;
1147 self.seq_foreach_attribute_id([&](
AttributeId attr_id) {
1148 if (result.has_value()) {
1151 const auto name = self.get_attribute_name(attr_id);
1152 if (self.attr_name_is_reserved(name))
return;
1153 const auto& attr = self.get_attribute_base(attr_id);
1154 if (element && attr.get_element_type() != *element)
return;
1155 if (usage && attr.get_usage() != *usage)
return;
1156 if (num_channels != 0 && attr.get_num_channels() != num_channels)
return;
1161 "element"_a = nb::none(),
1162 "usage"_a = nb::none(),
1163 "num_channels"_a = 0,
1164 R
"(Get one matching attribute id with the desired element type, usage and number of channels.
1166:param element: The target element type. None matches all element types.
1167:param usage: The target usage type. None matches all usage types.
1168:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1170:returns: An attribute id matching the target element, usage and number of channels, if found. None otherwise.
1173 surface_mesh_class.def(
1179 R
"(Create a shallow copy of this mesh.)");
1181 surface_mesh_class.def(
1183 [](
MeshType& self, [[maybe_unused]] std::optional<nb::dict> memo) ->
MeshType {
1190 using AttributeType = std::decay_t<
decltype(attr)>;
1191 if constexpr (AttributeType::IsIndexed) {
1192 auto& value_attr = attr.values();
1193 if (value_attr.is_external()) {
1194 value_attr.create_internal_copy();
1196 auto& index_attr = attr.indices();
1197 if (index_attr.is_external()) {
1198 index_attr.create_internal_copy();
1201 if (attr.is_external()) {
1202 attr.create_internal_copy();
1208 "memo"_a = nb::none(),
1209 R
"(Create a deep copy of this mesh.)");
1211 surface_mesh_class.def(
1215 return MeshType::stripped_copy(self);
1217 auto py_self = nb::find(self);
1218 return nb::cast<MeshType>(py_self.attr(
"__deepcopy__")());
1222 R
"(Create a deep copy of this mesh.
1224: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
Retrieve the edge id from end vertices (v0, v1).
Definition: Mesh.h:694
bool is_boundary_edge(Index e) const
Determines whether the specified edge e is a boundary edge.
Definition: Mesh.h:821
Index get_one_corner_around_edge(Index e) const
Get the index of one corner around a given edge.
Definition: Mesh.h:795
Index get_edge(Index f, Index lv) const
Gets the edge index corresponding to (f, lv) – (f, lv+1).
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
Get the index of one facet around a given edge.
Definition: Mesh.h:782
Index get_one_corner_around_vertex(Index v) const
Get the index of one corner around a given vertex.
Definition: Mesh.h:808
Index get_num_edges() const
Gets the number of edges.
Definition: Mesh.h:650
A general purpose polygonal mesh class.
Definition: SurfaceMesh.h:66
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:1264
Definition: PyAttribute.h:24
Definition: PyIndexedAttribute.h:26
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.
@ String
Mesh attribute is a metadata string (stored as a uint8_t buffer).
@ Position
Mesh attribute must have exactly dim channels.
@ Tangent
Mesh attribute can have dim or dim + 1 channels.
@ Vector
Mesh attribute can have any number of channels (including 1 channel).
@ CornerIndex
Single channel integer attribute indexing a mesh corner.
@ VertexIndex
Single channel integer attribute indexing a mesh vertex.
@ EdgeIndex
Single channel integer attribute indexing a mesh edge.
@ Normal
Mesh attribute can have dim or dim + 1 channels.
@ FacetIndex
Single channel integer attribute indexing a mesh facet.
@ Color
Mesh attribute can have 1, 2, 3 or 4 channels.
@ UV
Mesh attribute must have exactly 2 channels.
@ Bitangent
Mesh attribute can have dim or dim + 1 channels.
@ Scalar
Mesh attribute must have exactly 1 channel.
@ WarnAndCopy
Logs a warning and copy the buffer data if it grows beyond the buffer capacity.
@ 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:414
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:169
#define la_debug_assert(...)
Debug assertion check.
Definition: assert.h:189
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
Main namespace for Lagrange.
Definition: AABBIGL.h:30
Helper object to match attributes based on usage, element type, and number of channels.
Definition: find_matching_attributes.h:35
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