Lagrange
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Modules Pages
bind_surface_mesh.h
1/*
2 * Copyright 2022 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14// clang-format off
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>
23// clang-format on
24
25#include "PyAttribute.h"
26
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>
40
41namespace lagrange::python {
42
43template <typename Scalar, typename Index>
44void bind_surface_mesh(nanobind::module_& m)
45{
46 namespace nb = nanobind;
47 using namespace nb::literals;
48 using namespace lagrange;
49 using namespace lagrange::python;
50
52
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(
56 "add_vertex",
57 [](MeshType& self, Tensor<Scalar> b) {
58 auto [data, shape, stride] = tensor_to_span(b);
59 la_runtime_assert(is_dense(shape, stride));
60 la_runtime_assert(check_shape(shape, self.get_dimension()));
61 self.add_vertex(data);
62 },
63 "vertex"_a,
64 R"(Add a vertex to the mesh.
65
66:param vertex: vertex coordinates)");
67
68 // Handy overload to take python list as argument.
69 surface_mesh_class.def("add_vertex", [](MeshType& self, nb::list b) {
70 la_runtime_assert(static_cast<Index>(b.size()) == self.get_dimension());
71 if (self.get_dimension() == 3) {
72 self.add_vertex(
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])});
76 } else {
77 throw std::runtime_error("Dimension mismatch in vertex tensor");
78 }
79 });
80
81 surface_mesh_class.def("add_vertices", [](MeshType& self, Tensor<Scalar> b) {
82 auto [data, shape, stride] = tensor_to_span(b);
83 la_runtime_assert(is_dense(shape, stride));
84 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
85 self.add_vertices(static_cast<Index>(shape[0]), data);
86 });
87
88 // TODO: combine all add facet functions into `add_facets`.
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);
92 la_runtime_assert(is_dense(shape, stride));
93 la_runtime_assert(check_shape(shape, invalid<size_t>(), 3));
94 self.add_triangles(static_cast<Index>(shape[0]), data);
95 });
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);
99 la_runtime_assert(is_dense(shape, stride));
100 la_runtime_assert(check_shape(shape, invalid<size_t>(), 4));
101 self.add_quads(static_cast<Index>(shape[0]), data);
102 });
103 surface_mesh_class.def("add_polygon", [](MeshType& self, Tensor<Index> b) {
104 auto [data, shape, stride] = tensor_to_span(b);
105 la_runtime_assert(is_dense(shape, stride));
106 la_runtime_assert(is_vector(shape));
107 self.add_polygon(data);
108 });
109 surface_mesh_class.def("add_polygons", [](MeshType& self, Tensor<Index> b) {
110 auto [data, shape, stride] = tensor_to_span(b);
111 la_runtime_assert(is_dense(shape, stride));
112 self.add_polygons(static_cast<Index>(shape[0]), static_cast<Index>(shape[1]), data);
113 });
114 surface_mesh_class.def(
115 "add_hybrid",
116 [](MeshType& self, Tensor<Index> sizes, Tensor<Index> indices) {
117 auto [size_data, size_shape, size_stride] = tensor_to_span(sizes);
118 la_runtime_assert(is_dense(size_shape, size_stride));
119 la_runtime_assert(is_vector(size_shape));
120
121 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
122 la_runtime_assert(is_dense(index_shape, index_stride));
123 la_runtime_assert(is_vector(index_shape));
124
125 self.add_hybrid(size_data, index_data);
126 });
127 surface_mesh_class.def("remove_vertices", [](MeshType& self, Tensor<Index> b) {
128 auto [data, shape, stride] = tensor_to_span(b);
129 la_runtime_assert(is_dense(shape, stride));
130 la_runtime_assert(is_vector(shape));
131 self.remove_vertices(data);
132 });
133 surface_mesh_class.def(
134 "remove_vertices",
135 [](MeshType& self, nb::list b) {
136 std::vector<Index> indices;
137 for (auto i : b) {
138 indices.push_back(nb::cast<Index>(i));
139 }
140 self.remove_vertices(indices);
141 },
142 "vertices"_a,
143 R"(Remove selected vertices from the mesh.
144
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);
148 la_runtime_assert(is_dense(shape, stride));
149 la_runtime_assert(is_vector(shape));
150 self.remove_facets(data);
151 });
152 surface_mesh_class.def(
153 "remove_facets",
154 [](MeshType& self, nb::list b) {
155 std::vector<Index> indices;
156 for (auto i : b) {
157 indices.push_back(nb::cast<Index>(i));
158 }
159 self.remove_facets(indices);
160 },
161 "facets"_a,
162 R"(Remove selected facets from the mesh.
163
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);
178 surface_mesh_class.def_prop_ro("num_edges", &MeshType::get_num_edges);
179 surface_mesh_class.def("get_position", [](MeshType& self, Index i) {
180 return span_to_tensor(self.get_position(i), nb::find(&self));
181 });
182 surface_mesh_class.def("ref_position", [](MeshType& self, Index i) {
183 return span_to_tensor(self.ref_position(i), nb::find(&self));
184 });
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));
193 });
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));
196 });
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(
200 "create_attribute",
201 [](MeshType& self,
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;
211
212 // Infer number of channels.
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) {
224 n = 1;
225 }
226 } else {
227 throw nb::type_error("Either number of channels or initial values are required!");
228 }
229
230 // Infer element type.
231 AttributeElement elem_type;
232 std::visit(
233 [&](auto&& value) {
234 using T = std::decay_t<decltype(value)>;
235 if constexpr (std::is_same_v<T, AttributeElement>) {
236 elem_type = value;
237 } else if (with_initial_indices) {
238 elem_type = AttributeElement::Indexed;
239 } else if constexpr (std::is_same_v<T, std::string_view>) {
240 if (value == "Vertex") {
241 elem_type = AttributeElement::Vertex;
242 } else if (value == "Facet") {
243 elem_type = AttributeElement::Facet;
244 } else if (value == "Edge") {
245 elem_type = AttributeElement::Edge;
246 } else if (value == "Corner") {
247 elem_type = AttributeElement::Corner;
248 } else if (value == "Value") {
249 elem_type = AttributeElement::Value;
250 } else if (value == "Indexed") {
251 elem_type = AttributeElement::Indexed;
252 } else {
253 throw nb::type_error("Invalid element type!");
254 }
255 } else if (with_initial_values) {
256 // TODO guess element type based on the shape of 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 =
261 self.has_edges() ? self.get_num_edges() : invalid<Index>();
262
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);
270 }
271 la_debug_assert(num_rows != invalid<Index>());
272
273 if (num_rows == num_vertices) {
275 num_rows != num_facets,
276 "Cannot infer attribute element due to ambiguity: vertices vs "
277 "facets");
279 num_rows != num_edges,
280 "Cannot infer attribute element due to ambiguity: vertices vs "
281 "edges");
283 num_rows != num_corners,
284 "Cannot infer attribute element due to ambiguity: vertices vs "
285 "corners");
286 elem_type = AttributeElement::Vertex;
287 } else if (num_rows == num_facets) {
289 num_rows != num_edges,
290 "Cannot infer attribute element due to ambiguity: facets vs "
291 "edges");
293 num_rows != num_corners,
294 "Cannot infer attribute element due to ambiguity: facets vs "
295 "corners");
296 elem_type = AttributeElement::Facet;
297 } else if (num_rows == num_corners) {
299 num_rows != num_edges,
300 "Cannot infer attribute element due to ambiguity: corners vs "
301 "edges");
302 elem_type = AttributeElement::Corner;
303 } else if (num_rows == num_edges) {
304 elem_type = AttributeElement::Edge;
305 } else {
306 throw nb::type_error(
307 "Cannot infer attribute element type from initial_values!");
308 }
309 } else {
310 throw nb::type_error("Invalid element type!");
311 }
312 },
313 element);
314
315 // Infer usage.
316 AttributeUsage usage_type;
317 std::visit(
318 [&](auto&& value) {
319 using T = std::decay_t<decltype(value)>;
320 if constexpr (std::is_same_v<T, AttributeUsage>) {
321 usage_type = value;
322 } else if constexpr (std::is_same_v<T, std::string_view>) {
323 if (value == "Vector") {
324 usage_type = AttributeUsage::Vector;
325 } else if (value == "Scalar") {
326 usage_type = AttributeUsage::Scalar;
327 } else if (value == "Position") {
328 usage_type = AttributeUsage::Position;
329 } else if (value == "Normal") {
330 usage_type = AttributeUsage::Normal;
331 } else if (value == "Tangent") {
332 usage_type = AttributeUsage::Tangent;
333 } else if (value == "Bitangent") {
334 usage_type = AttributeUsage::Bitangent;
335 } else if (value == "Color") {
336 usage_type = AttributeUsage::Color;
337 } else if (value == "UV") {
338 usage_type = AttributeUsage::UV;
339 } else if (value == "VertexIndex") {
340 usage_type = AttributeUsage::VertexIndex;
341 } else if (value == "FacetIndex") {
342 usage_type = AttributeUsage::FacetIndex;
343 } else if (value == "CornerIndex") {
344 usage_type = AttributeUsage::CornerIndex;
345 } else if (value == "EdgeIndex") {
346 usage_type = AttributeUsage::EdgeIndex;
347 } else {
348 throw nb::type_error("Invalid usage type!");
349 }
350 } else {
351 if (n == 1) {
352 usage_type = AttributeUsage::Scalar;
353 } else {
354 usage_type = AttributeUsage::Vector;
355 }
356 }
357 },
358 usage);
359
360 auto create_attribute = [&](auto values) {
361 using ValueType = typename std::decay_t<decltype(values)>::element_type;
362
363 span<const ValueType> init_values;
364 span<const Index> init_indices;
365 std::vector<Index> index_storage;
366
367 // Extract initial values.
368 if (with_initial_values) {
369 init_values = values;
370 la_debug_assert(values.size() % n == 0);
371 } else {
373 num_channels.has_value(),
374 "Number of channels is required when initial values are not provided!");
376 dtype.has_value(),
377 "dtype is required when initial values are not provided!");
378 }
379
380 // Extract initial indices.
381 if (const Tensor<Index>* tensor_ptr =
382 std::get_if<Tensor<Index>>(&initial_indices)) {
383 la_debug_assert(with_initial_indices);
384 const auto& indices = *tensor_ptr;
385 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
386 la_runtime_assert(is_dense(index_shape, index_stride));
387 init_indices = index_data;
388 } else if (
389 GenericTensor* generic_tensor_ptr =
390 std::get_if<GenericTensor>(&initial_indices)) {
391 la_debug_assert(with_initial_indices);
392 auto& indices = *generic_tensor_ptr;
393 index_storage.resize(indices.size());
394
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()); \
399 }
400 LA_ATTRIBUTE_INDEX_X(create_attribute_index, 0)
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)) {
404 la_debug_assert(with_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());
408 }
409
410 return self.template create_attribute<ValueType>(
411 name,
412 elem_type,
413 usage_type,
414 n,
415 init_values,
416 init_indices,
418 };
419
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"); \
430 } \
431 return create_attribute(value_data); \
432 }
433 LA_ATTRIBUTE_X(create_attribute, 0)
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)) {
442 // Native python float is a C double.
443 span<double> local_values;
444 return create_attribute(local_values);
445 } else if (t.is(np.attr("float32"))) {
446 span<float> local_values;
447 return create_attribute(local_values);
448 } else if (t.is(np.attr("float64"))) {
449 span<double> local_values;
450 return create_attribute(local_values);
451 } else if (t.is(np.attr("int8"))) {
452 span<int8_t> local_values;
453 return create_attribute(local_values);
454 } else if (t.is(np.attr("int16"))) {
455 span<int16_t> local_values;
456 return create_attribute(local_values);
457 } else if (t.is(np.attr("int32"))) {
458 span<int32_t> local_values;
459 return create_attribute(local_values);
460 } else if (t.is(np.attr("int64"))) {
461 span<int64_t> local_values;
462 return create_attribute(local_values);
463 } else if (t.is(np.attr("uint8"))) {
464 span<uint8_t> local_values;
465 return create_attribute(local_values);
466 } else if (t.is(np.attr("uint16"))) {
467 span<uint16_t> local_values;
468 return create_attribute(local_values);
469 } else if (t.is(np.attr("uint32"))) {
470 span<uint32_t> local_values;
471 return create_attribute(local_values);
472 } else if (t.is(np.attr("uint64"))) {
473 span<uint64_t> local_values;
474 return create_attribute(local_values);
475 }
476 }
477 throw nb::type_error("`initial_values` and `dtype` cannot both be None!");
478 },
479 "name"_a,
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(),
486 nb::sig(
487 "def create_attribute(self, "
488 "name: str, "
489 "element: typing.Union[AttributeElement, "
490 "typing.Literal["
491 "'Vertex', 'Facet', 'Edge', 'Corner', 'Value', 'Indexed'"
492 "], None] = None, "
493 "usage: typing.Union[AttributeUsage, "
494 "typing.Literal["
495 "'Vector', 'Scalar', 'Position', 'Normal', 'Tangent', 'Bitangent', 'Color', 'UV', "
496 "'VertexIndex', 'FacetIndex', 'CornerIndex', 'EdgeIndex'"
497 "], None] = None, "
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.
503
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.
511
512:returns: The id of the created attribute.
513
514.. note::
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.
518
519.. note::
520 If `usage` is None, it will be derived based on the shape of `initial_values` or `num_channels` if specified.)");
521
522 surface_mesh_class.def(
523 "create_attribute_from",
524 &MeshType::template create_attribute_from<Scalar, Index>,
525 "name"_a,
526 "source_mesh"_a,
527 "source_name"_a = "",
528 R"(Shallow copy an attribute from another mesh.
529
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`.
533
534:returns: The id of the created attribute.)");
535
536 surface_mesh_class.def(
537 "wrap_as_attribute",
538 [](MeshType& self,
539 std::string_view name,
540 AttributeElement element,
541 AttributeUsage usage,
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);
546 la_runtime_assert(is_dense(shape, stride));
547 Index num_channels = shape.size() == 1 ? 1 : static_cast<Index>(shape[1]);
548 AttributeId id;
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(
552 name,
553 element,
554 usage,
555 num_channels,
556 make_shared_span(owner, data.data(), data.size()));
557 } else {
558 id = self.wrap_as_attribute(
559 name,
560 element,
561 usage,
562 num_channels,
563 make_shared_span(owner, data.data(), data.size()));
564 }
565 auto& attr = self.template ref_attribute<ValueType>(id);
566 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
567 return id;
568 };
569
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); \
574 }
575 LA_ATTRIBUTE_X(wrap_as_attribute, 0)
576#undef LA_X_wrap_as_attribute
577 throw nb::type_error("Unsupported value type!");
578 },
579 "name"_a,
580 "element"_a,
581 "usage"_a,
582 "values"_a,
583 R"(Wrap an existing numpy array as an attribute.
584
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.
589
590:returns: The id of the created attribute.)");
591 surface_mesh_class.def(
592 "wrap_as_indexed_attribute",
593 [](MeshType& self,
594 std::string_view name,
595 AttributeUsage usage,
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);
602 la_runtime_assert(is_dense(value_shape, value_stride));
603 la_runtime_assert(is_dense(index_shape, index_stride));
604 Index num_values = static_cast<Index>(value_shape[0]);
605 Index num_channels =
606 value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]);
607 AttributeId id;
608
609 auto value_owner = std::make_shared<nb::object>(nb::find(values));
610 auto index_owner = std::make_shared<nb::object>(nb::find(indices));
611
612 if constexpr (std::is_const_v<ValueType>) {
613 id = self.wrap_as_const_indexed_attribute(
614 name,
615 usage,
616 num_values,
617 num_channels,
618 make_shared_span(value_owner, value_data.data(), value_data.size()),
619 make_shared_span(index_owner, index_data.data(), index_data.size()));
620 } else {
621 id = self.wrap_as_indexed_attribute(
622 name,
623 usage,
624 num_values,
625 num_channels,
626 make_shared_span(value_owner, value_data.data(), value_data.size()),
627 make_shared_span(index_owner, index_data.data(), index_data.size()));
628 }
629 auto& attr = self.template ref_indexed_attribute<ValueType>(id);
630 attr.values().set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
631 attr.indices().set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
632 return id;
633 };
634
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); \
639 }
640 LA_ATTRIBUTE_X(wrap_as_indexed_attribute, 0)
641#undef LA_X_wrap_as_indexed_attribute
642 throw nb::type_error("Unsupported value type!");
643 },
644 "name"_a,
645 "usage"_a,
646 "values"_a,
647 "indices"_a,
648 R"(Wrap an existing numpy array as an indexed attribute.
649
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.
654
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(
659 "delete_attribute",
660 &MeshType::delete_attribute,
661 "name"_a,
662 "policy"_a /*= AttributeDeletePolicy::ErrorIfReserved*/);
663 surface_mesh_class.def(
664 "delete_attribute",
665 [](MeshType& self, std::string_view name) { self.delete_attribute(name); },
666 "name"_a,
667 R"(Delete an attribute by name.
668
669:param name: Name of the attribute.)");
670 surface_mesh_class.def(
671 "delete_attribute",
672 [](MeshType& self, AttributeId id) { self.delete_attribute(self.get_attribute_name(id)); },
673 "id"_a,
674 R"(Delete an attribute by id.
675
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",
680 static_cast<bool (MeshType::*)(AttributeId) const>(&MeshType::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));
684
685 // This is a helper method for trigger copy-on-write mechanism for a given attribute.
686 auto ensure_attribute_is_not_shared = [](MeshType& mesh, AttributeId id) {
687 auto& attr_base = mesh.get_attribute_base(id);
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); \
692 } \
693 } else { \
694 if (dynamic_cast<const Attribute<ValueType>*>(&attr_base)) { \
695 [[maybe_unused]] auto& attr = mesh.template ref_attribute<ValueType>(id); \
696 } \
697 }
698 LA_ATTRIBUTE_X(trigger_copy_on_write, 0)
699#undef LA_X_trigger_copy_on_write
700 };
701
702 surface_mesh_class.def(
703 "attribute",
704 [&](MeshType& self, AttributeId id, bool sharing) {
706 !self.is_attribute_indexed(id),
707 fmt::format(
708 "Attribute {} is indexed! Please use `indexed_attribute` property "
709 "instead.",
710 id));
711 if (!sharing) ensure_attribute_is_not_shared(self, id);
712 return PyAttribute(self._ref_attribute_ptr(id));
713 },
714 "id"_a,
715 "sharing"_a = true,
716 R"(Get an attribute by id.
717
718:param id: Id of the attribute.
719:param sharing: Whether to allow sharing the attribute with other meshes.
720
721:returns: The attribute.)");
722 surface_mesh_class.def(
723 "attribute",
724 [&](MeshType& self, std::string_view name, bool sharing) {
726 !self.is_attribute_indexed(name),
727 fmt::format(
728 "Attribute \"{}\" is indexed! Please use `indexed_attribute` property "
729 "instead.",
730 name));
731 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
732 return PyAttribute(self._ref_attribute_ptr(name));
733 },
734 "name"_a,
735 "sharing"_a = true,
736 R"(Get an attribute by name.
737
738:param name: Name of the attribute.
739:param sharing: Whether to allow sharing the attribute with other meshes.
740
741:return: The attribute.)");
742 surface_mesh_class.def(
743 "indexed_attribute",
744 [&](MeshType& self, AttributeId id, bool sharing) {
746 self.is_attribute_indexed(id),
747 fmt::format(
748 "Attribute {} is not indexed! Please use `attribute` property instead.",
749 id));
750 if (!sharing) ensure_attribute_is_not_shared(self, id);
751 return PyIndexedAttribute(self._ref_attribute_ptr(id));
752 },
753 "id"_a,
754 "sharing"_a = true,
755 R"(Get an indexed attribute by id.
756
757:param id: Id of the attribute.
758:param sharing: Whether to allow sharing the attribute with other meshes.
759
760:returns: The indexed attribute.)");
761 surface_mesh_class.def(
762 "indexed_attribute",
763 [&](MeshType& self, std::string_view name, bool sharing) {
765 self.is_attribute_indexed(name),
766 fmt::format(
767 "Attribute \"{}\"is not indexed! Please use `attribute` property instead.",
768 name));
769 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
770 return PyIndexedAttribute(self._ref_attribute_ptr(name));
771 },
772 "name"_a,
773 "sharing"_a = true,
774 R"(Get an indexed attribute by name.
775
776:param name: Name of the attribute.
777:param sharing: Whether to allow sharing the attribute with other meshes.
778
779:returns: The indexed attribute.)");
780 surface_mesh_class.def("__attribute_ref_count__", [](MeshType& self, AttributeId id) {
781 auto ptr = self._get_attribute_ptr(id);
782 return ptr.use_count();
783 });
784 surface_mesh_class.def_prop_rw(
785 "vertices",
786 [](const MeshType& self) {
787 const auto& attr = self.get_vertex_to_position();
788 return attribute_to_tensor(attr, nb::find(&self));
789 },
790 [](MeshType& self, Tensor<Scalar> tensor) {
791 auto [values, shape, stride] = tensor_to_span(tensor);
792 la_runtime_assert(is_dense(shape, stride));
793 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
794
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(
798 make_shared_span(owner, values.data(), values.size()),
799 static_cast<Index>(num_vertices));
800 auto& attr = self.template ref_attribute<Scalar>(id);
801 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
802 },
803 "Vertices of the mesh.");
804 surface_mesh_class.def_prop_rw(
805 "facets",
806 [](const MeshType& self) {
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));
813 } else {
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));
817 }
818 },
819 [](MeshType& self, Tensor<Index> tensor) {
820 auto [values, shape, stride] = tensor_to_span(tensor);
821 la_runtime_assert(is_dense(shape, stride));
822
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(
827 make_shared_span(owner, values.data(), values.size()),
828 static_cast<Index>(num_facets),
829 static_cast<Index>(vertex_per_facet));
830 auto& attr = self.template ref_attribute<Scalar>(id);
831 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
832 },
833 "Facets of the mesh.");
834 surface_mesh_class.def(
835 "wrap_as_vertices",
836 [](MeshType& self, Tensor<Scalar> tensor, Index num_vertices) {
837 auto [values, shape, stride] = tensor_to_span(tensor);
838 la_runtime_assert(is_dense(shape, stride));
839 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
840
841 auto owner = std::make_shared<nb::object>(nb::find(tensor));
842 auto id = self.wrap_as_vertices(
843 make_shared_span(owner, values.data(), values.size()),
844 num_vertices);
845 auto& attr = self.template ref_attribute<Scalar>(id);
846 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
847 return id;
848 },
849 "tensor"_a,
850 "num_vertices"_a,
851 R"(Wrap a tensor as vertices.
852
853:param tensor: The tensor to wrap.
854:param num_vertices: Number of vertices.
855
856:return: The id of the wrapped vertices attribute.)");
857 surface_mesh_class.def(
858 "wrap_as_facets",
859 [](MeshType& self, Tensor<Index> tensor, Index num_facets, Index vertex_per_facet) {
860 auto [values, shape, stride] = tensor_to_span(tensor);
861 la_runtime_assert(is_dense(shape, stride));
862
863 auto owner = std::make_shared<nb::object>(nb::find(tensor));
864 auto id = self.wrap_as_facets(
865 make_shared_span(owner, values.data(), values.size()),
866 num_facets,
867 vertex_per_facet);
868 auto& attr = self.template ref_attribute<Scalar>(id);
869 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
870 return id;
871 },
872 "tensor"_a,
873 "num_facets"_a,
874 "vertex_per_facet"_a,
875 R"(Wrap a tensor as a list of regular facets.
876
877:param tensor: The tensor to wrap.
878:param num_facets: Number of facets.
879:param vertex_per_facet: Number of vertices per facet.
880
881:return: The id of the wrapped facet attribute.)");
882 surface_mesh_class.def(
883 "wrap_as_facets",
884 [](MeshType& self,
885 Tensor<Index> offsets,
886 Index num_facets,
887 Tensor<Index> facets,
888 Index num_corners) {
889 auto [offsets_data, offsets_shape, offsets_stride] = tensor_to_span(offsets);
890 auto [facets_data, facets_shape, facets_stride] = tensor_to_span(facets);
891 la_runtime_assert(is_dense(offsets_shape, offsets_stride));
892 la_runtime_assert(is_dense(facets_shape, facets_stride));
893
894 auto offsets_owner = std::make_shared<nb::object>(nb::find(offsets));
895 auto facets_owner = std::make_shared<nb::object>(nb::find(facets));
896
897 auto id = self.wrap_as_facets(
898 make_shared_span(offsets_owner, offsets_data.data(), offsets_data.size()),
899 num_facets,
900 make_shared_span(facets_owner, facets_data.data(), facets_data.size()),
901 num_corners);
902 auto& attr = self.template ref_attribute<Scalar>(id);
903 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
904 return id;
905 },
906 "offsets"_a,
907 "num_facets"_a,
908 "facets"_a,
909 "num_corners"_a,
910 R"(Wrap a tensor as a list of hybrid facets.
911
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.
916
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(
977 "initialize_edges",
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());
981 la_runtime_assert(is_dense(edge_shape, edge_stride));
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);
986 } else {
987 self.initialize_edges();
988 }
989 },
990 "edges"_a = nb::none(),
991 R"(Initialize the edges.
992
993The `edges` tensor provides a predefined ordering of the edges.
994If not provided, the edges are initialized in an arbitrary order.
995
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);
999 surface_mesh_class.def("get_edge", &MeshType::get_edge);
1000 surface_mesh_class.def("get_corner_edge", &MeshType::get_corner_edge);
1001 surface_mesh_class.def("get_edge_vertices", &MeshType::get_edge_vertices);
1002 surface_mesh_class.def("find_edge_from_vertices", &MeshType::find_edge_from_vertices);
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,
1020 "corner"_a,
1021 R"(Get the counterclockwise corner around the vertex associated with the input corner.
1022
1023.. note::
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.
1026
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.
1029
1030:param corner: The input corner index.
1031
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,
1036 "corner"_a,
1037 R"(Get the clockwise corner around the vertex associated with the input corner.
1038
1039.. note::
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.
1042
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.
1045
1046:param corner: The input corner index.
1047
1048:returns: The clockwise corner index or `invalid_index` if none exists.)");
1049 surface_mesh_class.def("get_one_facet_around_edge", &MeshType::get_one_facet_around_edge);
1050 surface_mesh_class.def("get_one_corner_around_edge", &MeshType::get_one_corner_around_edge);
1051 surface_mesh_class.def("get_one_corner_around_vertex", &MeshType::get_one_corner_around_vertex);
1052 surface_mesh_class.def("is_boundary_edge", &MeshType::is_boundary_edge);
1053
1054 struct MetaData
1055 {
1056 MeshType* mesh = nullptr;
1057
1058 std::vector<AttributeId> get_metadata() const
1059 {
1060 la_runtime_assert(mesh != nullptr, "MetaData is not initialized.");
1064 opts.num_channels = 1;
1065 return lagrange::find_matching_attributes(*mesh, opts);
1066 }
1067 };
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();
1071 return data.size();
1072 });
1073 meta_data_class.def("__getitem__", [](const MetaData& self, std::string_view key) {
1074 return self.mesh->get_metadata(key);
1075 });
1076 meta_data_class.def(
1077 "__setitem__",
1078 [](MetaData& self, std::string_view key, std::string_view value) {
1079 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1080 if (self.mesh->has_attribute(key)) {
1081 self.mesh->set_metadata(key, value);
1082 } else {
1083 self.mesh->create_metadata(key, value);
1084 }
1085 });
1086 meta_data_class.def("__delitem__", [](MetaData& self, std::string_view key) {
1087 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1088 self.mesh->delete_attribute(key);
1089 });
1090 meta_data_class.def("__repr__", [](const MetaData& self) -> std::string {
1091 auto data = self.get_metadata();
1092 if (data.empty()) return "MetaData({})";
1093
1094 std::string r;
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);
1099 }
1100 return fmt::format("MetaData(\n{})", r);
1101 });
1102
1103 surface_mesh_class.def_prop_ro(
1104 "metadata",
1105 [](MeshType& self) {
1106 MetaData meta_data;
1107 meta_data.mesh = &self;
1108 return meta_data;
1109 },
1110 "Metadata of the mesh.");
1111
1112 surface_mesh_class.def(
1113 "get_matching_attribute_ids",
1114 [](MeshType& self,
1115 std::optional<AttributeElement> element,
1116 std::optional<AttributeUsage> usage,
1117 Index num_channels) {
1118 AttributeMatcher opts;
1119 if (usage.has_value()) {
1120 opts.usages = usage.value();
1121 }
1122 if (element.has_value()) {
1123 opts.element_types = element.value();
1124 }
1125 opts.num_channels = num_channels;
1126 return find_matching_attributes(self, opts);
1127 },
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.
1132
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.
1136
1137:returns: A list of attribute ids matching the target element, usage and number of channels.
1138)");
1139
1140 surface_mesh_class.def(
1141 "get_matching_attribute_id",
1142 [](MeshType& self,
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()) {
1149 return;
1150 }
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;
1157 result = attr_id;
1158 });
1159 return result;
1160 },
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.
1165
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.
1169
1170:returns: An attribute id matching the target element, usage and number of channels, if found. None otherwise.
1171)");
1172
1173 surface_mesh_class.def(
1174 "__copy__",
1175 [](MeshType& self) -> MeshType {
1176 MeshType mesh = self;
1177 return mesh;
1178 },
1179 R"(Create a shallow copy of this mesh.)");
1180
1181 surface_mesh_class.def(
1182 "__deepcopy__",
1183 [](MeshType& self, [[maybe_unused]] std::optional<nb::dict> memo) -> MeshType {
1184 MeshType mesh = self;
1185 par_foreach_attribute_write(mesh, [](auto&& attr) {
1186 // For most of the attributes, just getting a writable reference will trigger a
1187 // copy of the buffer thanks to the copy-on-write mechanism and the default
1188 // CopyIfExternal copy policy.
1189
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();
1195 }
1196 auto& index_attr = attr.indices();
1197 if (index_attr.is_external()) {
1198 index_attr.create_internal_copy();
1199 }
1200 } else {
1201 if (attr.is_external()) {
1202 attr.create_internal_copy();
1203 }
1204 }
1205 });
1206 return mesh;
1207 },
1208 "memo"_a = nb::none(),
1209 R"(Create a deep copy of this mesh.)");
1210
1211 surface_mesh_class.def(
1212 "clone",
1213 [](MeshType& self, bool strip) -> MeshType {
1214 if (strip) {
1215 return MeshType::stripped_copy(self);
1216 } else {
1217 auto py_self = nb::find(self);
1218 return nb::cast<MeshType>(py_self.attr("__deepcopy__")());
1219 }
1220 },
1221 "strip"_a = false,
1222 R"(Create a deep copy of this mesh.
1223
1224:param strip: If True, strip the mesh of all attributes except for the reserved attributes.)");
1225}
1226
1227} // namespace lagrange::python
Definition: Mesh.h:48
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