Lagrange
Loading...
Searching...
No Matches
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#include "PyAttribute.h"
15
16#include <lagrange/Attribute.h>
17#include <lagrange/AttributeFwd.h>
18#include <lagrange/AttributeTypes.h>
19#include <lagrange/IndexedAttribute.h>
20#include <lagrange/Logger.h>
21#include <lagrange/SurfaceMesh.h>
22#include <lagrange/find_matching_attributes.h>
23#include <lagrange/foreach_attribute.h>
24#include <lagrange/python/binding.h>
25#include <lagrange/python/tensor_utils.h>
26#include <lagrange/utils/BitField.h>
27#include <lagrange/utils/Error.h>
28#include <lagrange/utils/assert.h>
29#include <lagrange/utils/invalid.h>
30
31// clang-format off
32#include <lagrange/utils/warnoff.h>
33#include <tbb/parallel_for.h>
34#include <lagrange/utils/warnon.h>
35// clang-format on
36
37namespace lagrange::python {
38
39template <typename Scalar, typename Index>
40void bind_surface_mesh(nanobind::module_& m)
41{
42 namespace nb = nanobind;
43 using namespace nb::literals;
44 using namespace lagrange;
45 using namespace lagrange::python;
46
47 using MeshType = SurfaceMesh<Scalar, Index>;
48
49 auto surface_mesh_class = nb::class_<MeshType>(m, "SurfaceMesh", "Surface mesh data structure");
50 surface_mesh_class.def(nb::init<Index>(), nb::arg("dimension") = 3);
51 surface_mesh_class.def(
52 "add_vertex",
53 [](MeshType& self, Tensor<Scalar> b) {
54 auto [data, shape, stride] = tensor_to_span(b);
55 la_runtime_assert(is_dense(shape, stride));
56 la_runtime_assert(check_shape(shape, self.get_dimension()));
57 self.add_vertex(data);
58 },
59 "vertex"_a,
60 R"(Add a vertex to the mesh.
61
62:param vertex: vertex coordinates)");
63
64 // Handy overload to take python list as argument.
65 surface_mesh_class.def(
66 "add_vertex",
67 [](MeshType& self, nb::list b) {
68 la_runtime_assert(static_cast<Index>(b.size()) == self.get_dimension());
69 if (self.get_dimension() == 3) {
70 self.add_vertex(
71 {nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1]), nb::cast<Scalar>(b[2])});
72 } else if (self.get_dimension() == 2) {
73 self.add_vertex({nb::cast<Scalar>(b[0]), nb::cast<Scalar>(b[1])});
74 } else {
75 throw std::runtime_error("Dimension mismatch in vertex tensor");
76 }
77 },
78 "vertex"_a,
79 R"(Add a vertex to the mesh.
80
81:param vertex: vertex coordinates as a list)");
82
83 surface_mesh_class.def(
84 "add_vertices",
85 [](MeshType& self, Tensor<Scalar> b) {
86 auto [data, shape, stride] = tensor_to_span(b);
87 la_runtime_assert(is_dense(shape, stride));
88 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
89 self.add_vertices(static_cast<Index>(shape[0]), data);
90 },
91 "vertices"_a,
92 R"(Add multiple vertices to the mesh.
93
94:param vertices: N x D tensor of vertex coordinates, where N is the number of vertices and D is the dimension)");
95
96 // TODO: combine all add facet functions into `add_facets`.
97 surface_mesh_class.def(
98 "add_triangle",
99 &MeshType::add_triangle,
100 "v0"_a,
101 "v1"_a,
102 "v2"_a,
103 R"(Add a triangle to the mesh.
104
105:param v0: first vertex index
106:param v1: second vertex index
107:param v2: third vertex index
108
109:returns: facet index of the added triangle)");
110 surface_mesh_class.def(
111 "add_triangles",
112 [](MeshType& self, Tensor<Index> b) {
113 auto [data, shape, stride] = tensor_to_span(b);
114 la_runtime_assert(is_dense(shape, stride));
115 la_runtime_assert(check_shape(shape, invalid<size_t>(), 3));
116 self.add_triangles(static_cast<Index>(shape[0]), data);
117 },
118 "triangles"_a,
119 R"(Add multiple triangles to the mesh.
120
121:param triangles: N x 3 tensor of vertex indices, where N is the number of triangles)");
122 surface_mesh_class.def(
123 "add_quad",
124 &MeshType::add_quad,
125 "v0"_a,
126 "v1"_a,
127 "v2"_a,
128 "v3"_a,
129 R"(Add a quad to the mesh.
130
131:param v0: first vertex index
132:param v1: second vertex index
133:param v2: third vertex index
134:param v3: fourth vertex index
135
136:returns: facet index of the added quad)");
137 surface_mesh_class.def(
138 "add_quads",
139 [](MeshType& self, Tensor<Index> b) {
140 auto [data, shape, stride] = tensor_to_span(b);
141 la_runtime_assert(is_dense(shape, stride));
142 la_runtime_assert(check_shape(shape, invalid<size_t>(), 4));
143 self.add_quads(static_cast<Index>(shape[0]), data);
144 },
145 "quads"_a,
146 R"(Add multiple quads to the mesh.
147
148:param quads: N x 4 tensor of vertex indices, where N is the number of quads)");
149 surface_mesh_class.def(
150 "add_polygon",
151 [](MeshType& self, Tensor<Index> b) {
152 auto [data, shape, stride] = tensor_to_span(b);
153 la_runtime_assert(is_dense(shape, stride));
154 la_runtime_assert(is_vector(shape));
155 self.add_polygon(data);
156 },
157 "vertices"_a,
158 R"(Add a polygon to the mesh.
159
160:param vertices: 1D tensor of vertex indices defining the polygon
161
162:returns: facet index of the added polygon)");
163 surface_mesh_class.def(
164 "add_polygons",
165 [](MeshType& self, Tensor<Index> b) {
166 auto [data, shape, stride] = tensor_to_span(b);
167 la_runtime_assert(is_dense(shape, stride));
168 self.add_polygons(static_cast<Index>(shape[0]), static_cast<Index>(shape[1]), data);
169 },
170 "polygons"_a,
171 R"(Add multiple regular polygons to the mesh.
172
173:param polygons: N x K tensor of vertex indices, where N is the number of polygons and K is the number of vertices per polygon)");
174 surface_mesh_class.def(
175 "add_hybrid",
176 [](MeshType& self, Tensor<Index> sizes, Tensor<Index> indices) {
177 auto [size_data, size_shape, size_stride] = tensor_to_span(sizes);
178 la_runtime_assert(is_dense(size_shape, size_stride));
179 la_runtime_assert(is_vector(size_shape));
180
181 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
182 la_runtime_assert(is_dense(index_shape, index_stride));
183 la_runtime_assert(is_vector(index_shape));
184
185 self.add_hybrid(size_data, index_data);
186 },
187 "sizes"_a,
188 "indices"_a,
189 R"(Add hybrid facets (polygons with varying number of vertices) to the mesh.
190
191:param sizes: 1D tensor specifying the number of vertices for each facet
192:param indices: 1D tensor of vertex indices for all facets concatenated together)");
193 surface_mesh_class.def(
194 "remove_vertices",
195 [](MeshType& self, Tensor<Index> b) {
196 auto [data, shape, stride] = tensor_to_span(b);
197 la_runtime_assert(is_dense(shape, stride));
198 la_runtime_assert(is_vector(shape));
199 self.remove_vertices(data);
200 },
201 "vertices"_a,
202 R"(Remove selected vertices from the mesh.
203
204:param vertices: 1D tensor of vertex indices to remove)");
205 surface_mesh_class.def(
206 "remove_vertices",
207 [](MeshType& self, nb::list b) {
208 std::vector<Index> indices;
209 for (auto i : b) {
210 indices.push_back(nb::cast<Index>(i));
211 }
212 self.remove_vertices(indices);
213 },
214 "vertices"_a,
215 R"(Remove selected vertices from the mesh.
216
217:param vertices: list of vertex indices to remove)");
218 surface_mesh_class.def(
219 "remove_facets",
220 [](MeshType& self, Tensor<Index> b) {
221 auto [data, shape, stride] = tensor_to_span(b);
222 la_runtime_assert(is_dense(shape, stride));
223 la_runtime_assert(is_vector(shape));
224 self.remove_facets(data);
225 },
226 "facets"_a,
227 R"(Remove selected facets from the mesh.
228
229:param facets: 1D tensor of facet indices to remove)");
230 surface_mesh_class.def(
231 "remove_facets",
232 [](MeshType& self, nb::list b) {
233 std::vector<Index> indices;
234 for (auto i : b) {
235 indices.push_back(nb::cast<Index>(i));
236 }
237 self.remove_facets(indices);
238 },
239 "facets"_a,
240 R"(Remove selected facets from the mesh.
241
242:param facets: list of facet indices to remove)");
243 surface_mesh_class.def(
244 "clear_vertices",
245 &MeshType::clear_vertices,
246 R"(Remove all vertices from the mesh.)");
247 surface_mesh_class.def(
248 "clear_facets",
249 &MeshType::clear_facets,
250 R"(Remove all facets from the mesh.)");
251 surface_mesh_class.def(
252 "shrink_to_fit",
253 &MeshType::shrink_to_fit,
254 R"(Shrink the internal storage to fit the current mesh size.)");
255 surface_mesh_class.def(
256 "compress_if_regular",
257 &MeshType::compress_if_regular,
258 R"(Compress the mesh representation if it is regular (all facets have the same number of vertices).
259
260:returns: True if the mesh was compressed, False otherwise)");
261 surface_mesh_class.def_prop_ro("is_triangle_mesh", &MeshType::is_triangle_mesh);
262 surface_mesh_class.def_prop_ro("is_quad_mesh", &MeshType::is_quad_mesh);
263 surface_mesh_class.def_prop_ro("is_regular", &MeshType::is_regular);
264 surface_mesh_class.def_prop_ro("is_hybrid", &MeshType::is_hybrid);
265 surface_mesh_class.def_prop_ro("dimension", &MeshType::get_dimension);
266 surface_mesh_class.def_prop_ro("vertex_per_facet", &MeshType::get_vertex_per_facet);
267 surface_mesh_class.def_prop_ro("num_vertices", &MeshType::get_num_vertices);
268 surface_mesh_class.def_prop_ro("num_facets", &MeshType::get_num_facets);
269 surface_mesh_class.def_prop_ro("num_corners", &MeshType::get_num_corners);
270 surface_mesh_class.def_prop_ro("num_edges", &MeshType::get_num_edges);
271 surface_mesh_class.def(
272 "get_position",
273 [](MeshType& self, Index i) {
274 return span_to_tensor(self.get_position(i), nb::find(&self));
275 },
276 "vertex_id"_a,
277 R"(Get the position of a vertex.
278
279:param vertex_id: vertex index
280
281:returns: position coordinates as a tensor)");
282 surface_mesh_class.def(
283 "ref_position",
284 [](MeshType& self, Index i) {
285 return span_to_tensor(self.ref_position(i), nb::find(&self));
286 },
287 "vertex_id"_a,
288 R"(Get a mutable reference to the position of a vertex.
289
290:param vertex_id: vertex index
291
292:returns: mutable position coordinates as a tensor)");
293 surface_mesh_class.def(
294 "get_facet_size",
295 &MeshType::get_facet_size,
296 "facet_id"_a,
297 R"(Get the number of vertices in a facet.
298
299:param facet_id: facet index
300
301:returns: number of vertices in the facet)");
302 surface_mesh_class.def(
303 "get_facet_vertex",
304 &MeshType::get_facet_vertex,
305 "facet_id"_a,
306 "local_vertex_id"_a,
307 R"(Get a vertex index from a facet.
308
309:param facet_id: facet index
310:param local_vertex_id: local vertex index within the facet (0 to facet_size-1)
311
312:returns: global vertex index)");
313 surface_mesh_class.def(
314 "get_facet_corner_begin",
315 &MeshType::get_facet_corner_begin,
316 "facet_id"_a,
317 R"(Get the first corner index of a facet.
318
319:param facet_id: facet index
320
321:returns: first corner index of the facet)");
322 surface_mesh_class.def(
323 "get_facet_corner_end",
324 &MeshType::get_facet_corner_end,
325 "facet_id"_a,
326 R"(Get the end corner index of a facet (one past the last corner).
327
328:param facet_id: facet index
329
330:returns: end corner index of the facet)");
331 surface_mesh_class.def(
332 "get_corner_vertex",
333 &MeshType::get_corner_vertex,
334 "corner_id"_a,
335 R"(Get the vertex index associated with a corner.
336
337:param corner_id: corner index
338
339:returns: vertex index)");
340 surface_mesh_class.def(
341 "get_corner_facet",
342 &MeshType::get_corner_facet,
343 "corner_id"_a,
344 R"(Get the facet index associated with a corner.
345
346:param corner_id: corner index
347
348:returns: facet index)");
349 surface_mesh_class.def(
350 "get_facet_vertices",
351 [](MeshType& self, Index f) {
352 return span_to_tensor(self.get_facet_vertices(f), nb::find(&self));
353 },
354 "facet_id"_a,
355 R"(Get all vertex indices of a facet.
356
357:param facet_id: facet index
358
359:returns: vertex indices as a tensor)");
360 surface_mesh_class.def(
361 "ref_facet_vertices",
362 [](MeshType& self, Index f) {
363 return span_to_tensor(self.ref_facet_vertices(f), nb::find(&self));
364 },
365 "facet_id"_a,
366 R"(Get a mutable reference to all vertex indices of a facet.
367
368:param facet_id: facet index
369
370:returns: mutable vertex indices as a tensor)");
371 surface_mesh_class.def(
372 "get_attribute_id",
373 &MeshType::get_attribute_id,
374 "name"_a,
375 R"(Get the attribute ID by name.
376
377:param name: attribute name
378
379:returns: attribute ID)");
380 surface_mesh_class.def(
381 "get_attribute_name",
382 &MeshType::get_attribute_name,
383 "id"_a,
384 R"(Get the attribute name by ID.
385
386:param id: attribute ID
387
388:returns: attribute name)");
389 surface_mesh_class.def(
390 "create_attribute",
391 [](MeshType& self,
392 std::string_view name,
393 std::variant<std::monostate, AttributeElement, std::string_view> element,
394 std::variant<std::monostate, AttributeUsage, std::string_view> usage,
395 std::variant<std::monostate, GenericTensor, nb::list> initial_values,
396 std::variant<std::monostate, Tensor<Index>, GenericTensor, nb::list> initial_indices,
397 std::optional<Index> num_channels,
398 std::optional<nb::type_object> dtype) {
399 const bool with_initial_values = initial_values.index() != 0;
400 const bool with_initial_indices = initial_indices.index() != 0;
401
402 // Infer number of channels.
403 Index n = invalid<Index>();
404 if (num_channels.has_value()) {
405 n = num_channels.value();
406 } else if (with_initial_values) {
407 if (initial_values.index() == 1) {
408 const auto& values = std::get<GenericTensor>(initial_values);
410 values.ndim() == 1 || values.ndim() == 2,
411 "Only vector or matrix are accepted as initial values.");
412 n = values.ndim() == 1 ? 1 : static_cast<Index>(values.shape(1));
413 } else if (initial_values.index() == 2) {
414 n = 1;
415 }
416 } else {
417 throw nb::type_error("Either number of channels or initial values are required!");
418 }
419
420 // Infer element type.
421 AttributeElement elem_type;
422 std::visit(
423 [&](auto&& value) {
424 using T = std::decay_t<decltype(value)>;
425 if constexpr (std::is_same_v<T, AttributeElement>) {
426 elem_type = value;
427 } else if (with_initial_indices) {
428 elem_type = AttributeElement::Indexed;
429 } else if constexpr (std::is_same_v<T, std::string_view>) {
430 if (value == "Vertex") {
431 elem_type = AttributeElement::Vertex;
432 } else if (value == "Facet") {
433 elem_type = AttributeElement::Facet;
434 } else if (value == "Edge") {
435 elem_type = AttributeElement::Edge;
436 } else if (value == "Corner") {
437 elem_type = AttributeElement::Corner;
438 } else if (value == "Value") {
439 elem_type = AttributeElement::Value;
440 } else if (value == "Indexed") {
441 elem_type = AttributeElement::Indexed;
442 } else {
443 throw nb::type_error("Invalid element type!");
444 }
445 } else if (with_initial_values) {
446 // TODO guess element type based on the shape of initial values.
447 const Index num_vertices = self.get_num_vertices();
448 const Index num_facets = self.get_num_facets();
449 const Index num_corners = self.get_num_corners();
450 const Index num_edges =
451 self.has_edges() ? self.get_num_edges() : invalid<Index>();
452
453 Index num_rows = invalid<Index>();
454 if (initial_values.index() == 1) {
455 const auto& values = std::get<GenericTensor>(initial_values);
456 num_rows = static_cast<Index>(values.shape(0));
457 } else if (initial_values.index() == 2) {
458 const auto& values = std::get<nb::list>(initial_values);
459 num_rows = static_cast<Index>(nb::len(values));
460 }
461 la_debug_assert(num_rows != invalid<Index>());
462
463 if (num_rows == num_vertices) {
465 num_rows != num_facets,
466 "Cannot infer attribute element due to ambiguity: vertices vs "
467 "facets");
469 num_rows != num_edges,
470 "Cannot infer attribute element due to ambiguity: vertices vs "
471 "edges");
473 num_rows != num_corners,
474 "Cannot infer attribute element due to ambiguity: vertices vs "
475 "corners");
476 elem_type = AttributeElement::Vertex;
477 } else if (num_rows == num_facets) {
479 num_rows != num_edges,
480 "Cannot infer attribute element due to ambiguity: facets vs "
481 "edges");
483 num_rows != num_corners,
484 "Cannot infer attribute element due to ambiguity: facets vs "
485 "corners");
486 elem_type = AttributeElement::Facet;
487 } else if (num_rows == num_corners) {
489 num_rows != num_edges,
490 "Cannot infer attribute element due to ambiguity: corners vs "
491 "edges");
492 elem_type = AttributeElement::Corner;
493 } else if (num_rows == num_edges) {
494 elem_type = AttributeElement::Edge;
495 } else {
496 throw nb::type_error(
497 "Cannot infer attribute element type from initial_values!");
498 }
499 } else {
500 throw nb::type_error("Invalid element type!");
501 }
502 },
503 element);
504
505 // Infer usage.
506 AttributeUsage usage_type;
507 std::visit(
508 [&](auto&& value) {
509 using T = std::decay_t<decltype(value)>;
510 if constexpr (std::is_same_v<T, AttributeUsage>) {
511 usage_type = value;
512 } else if constexpr (std::is_same_v<T, std::string_view>) {
513 if (value == "Vector") {
514 usage_type = AttributeUsage::Vector;
515 } else if (value == "Scalar") {
516 usage_type = AttributeUsage::Scalar;
517 } else if (value == "Position") {
518 usage_type = AttributeUsage::Position;
519 } else if (value == "Normal") {
520 usage_type = AttributeUsage::Normal;
521 } else if (value == "Tangent") {
522 usage_type = AttributeUsage::Tangent;
523 } else if (value == "Bitangent") {
524 usage_type = AttributeUsage::Bitangent;
525 } else if (value == "Color") {
526 usage_type = AttributeUsage::Color;
527 } else if (value == "UV") {
528 usage_type = AttributeUsage::UV;
529 } else if (value == "VertexIndex") {
530 usage_type = AttributeUsage::VertexIndex;
531 } else if (value == "FacetIndex") {
532 usage_type = AttributeUsage::FacetIndex;
533 } else if (value == "CornerIndex") {
534 usage_type = AttributeUsage::CornerIndex;
535 } else if (value == "EdgeIndex") {
536 usage_type = AttributeUsage::EdgeIndex;
537 } else {
538 throw nb::type_error("Invalid usage type!");
539 }
540 } else {
541 if (n == 1) {
542 usage_type = AttributeUsage::Scalar;
543 } else {
544 usage_type = AttributeUsage::Vector;
545 }
546 }
547 },
548 usage);
549
550 auto create_attribute = [&](auto values) {
551 using ValueType = typename std::decay_t<decltype(values)>::element_type;
552
553 span<const ValueType> init_values;
554 span<const Index> init_indices;
555 std::vector<Index> index_storage;
556
557 // Extract initial values.
558 if (with_initial_values) {
559 init_values = values;
560 la_debug_assert(values.size() % n == 0);
561 } else {
563 num_channels.has_value(),
564 "Number of channels is required when initial values are not provided!");
566 dtype.has_value(),
567 "dtype is required when initial values are not provided!");
568 }
569
570 // Extract initial indices.
571 if (const Tensor<Index>* tensor_ptr =
572 std::get_if<Tensor<Index>>(&initial_indices)) {
573 la_debug_assert(with_initial_indices);
574 const auto& indices = *tensor_ptr;
575 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
576 la_runtime_assert(is_dense(index_shape, index_stride));
577 init_indices = index_data;
578 } else if (
579 GenericTensor* generic_tensor_ptr =
580 std::get_if<GenericTensor>(&initial_indices)) {
581 la_debug_assert(with_initial_indices);
582 auto& indices = *generic_tensor_ptr;
583 index_storage.resize(indices.size());
584
585#define LA_X_create_attribute_index(_, IndexType) \
586 if (indices.dtype() == nb::dtype<IndexType>()) { \
587 auto view = indices.template view<IndexType, nb::ndim<1>>(); \
588 std::copy(view.data(), view.data() + indices.size(), index_storage.begin()); \
589 }
590 LA_ATTRIBUTE_INDEX_X(create_attribute_index, 0)
591#undef LA_X_create_attribute_index
592 init_indices = span<Index>(index_storage.data(), index_storage.size());
593 } else if (const nb::list* list_ptr = std::get_if<nb::list>(&initial_indices)) {
594 la_debug_assert(with_initial_indices);
595 const nb::list& py_list = *list_ptr;
596 index_storage = nb::cast<std::vector<Index>>(py_list);
597 init_indices = span<Index>(index_storage.begin(), index_storage.size());
598 }
599
600 return self.template create_attribute<ValueType>(
601 name,
602 elem_type,
603 usage_type,
604 n,
605 init_values,
606 init_indices,
608 };
609
610 if (const GenericTensor* tensor_ptr = std::get_if<GenericTensor>(&initial_values)) {
611 const auto& values = *tensor_ptr;
612#define LA_X_create_attribute(_, ValueType) \
613 if (values.dtype() == nb::dtype<ValueType>()) { \
614 Tensor<ValueType> local_values(values.handle()); \
615 auto [value_data, value_shape, value_stride] = tensor_to_span(local_values); \
616 la_runtime_assert(is_dense(value_shape, value_stride)); \
617 if (num_channels.has_value()) { \
618 Index nn = value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]); \
619 la_runtime_assert(nn == n, "Number of channels does not match initial_values"); \
620 } \
621 return create_attribute(value_data); \
622 }
623 LA_ATTRIBUTE_X(create_attribute, 0)
624#undef LA_X_create_attribute
625 } else if (const nb::list* list_ptr = std::get_if<nb::list>(&initial_values)) {
626 auto values = nb::cast<std::vector<double>>(*list_ptr);
627 return create_attribute(span<double>(values.data(), values.size()));
628 } else if (dtype.has_value()) {
629 const auto& t = dtype.value();
630 auto np = nb::module_::import_("numpy");
631 if (t.is(&PyFloat_Type)) {
632 // Native python float is a C double.
633 span<double> local_values;
634 return create_attribute(local_values);
635 } else if (t.is(np.attr("float32"))) {
636 span<float> local_values;
637 return create_attribute(local_values);
638 } else if (t.is(np.attr("float64"))) {
639 span<double> local_values;
640 return create_attribute(local_values);
641 } else if (t.is(np.attr("int8"))) {
642 span<int8_t> local_values;
643 return create_attribute(local_values);
644 } else if (t.is(np.attr("int16"))) {
645 span<int16_t> local_values;
646 return create_attribute(local_values);
647 } else if (t.is(np.attr("int32"))) {
648 span<int32_t> local_values;
649 return create_attribute(local_values);
650 } else if (t.is(np.attr("int64"))) {
651 span<int64_t> local_values;
652 return create_attribute(local_values);
653 } else if (t.is(np.attr("uint8"))) {
654 span<uint8_t> local_values;
655 return create_attribute(local_values);
656 } else if (t.is(np.attr("uint16"))) {
657 span<uint16_t> local_values;
658 return create_attribute(local_values);
659 } else if (t.is(np.attr("uint32"))) {
660 span<uint32_t> local_values;
661 return create_attribute(local_values);
662 } else if (t.is(np.attr("uint64"))) {
663 span<uint64_t> local_values;
664 return create_attribute(local_values);
665 }
666 }
667 throw nb::type_error("`initial_values` and `dtype` cannot both be None!");
668 },
669 "name"_a,
670 "element"_a = nb::none(),
671 "usage"_a = nb::none(),
672 "initial_values"_a = nb::none(),
673 "initial_indices"_a = nb::none(),
674 "num_channels"_a = nb::none(),
675 "dtype"_a = nb::none(),
676 nb::sig(
677 "def create_attribute(self, "
678 "name: str, "
679 "element: typing.Union[AttributeElement, "
680 "typing.Literal["
681 "'Vertex', 'Facet', 'Edge', 'Corner', 'Value', 'Indexed'"
682 "], None] = None, "
683 "usage: typing.Union[AttributeUsage, "
684 "typing.Literal["
685 "'Vector', 'Scalar', 'Position', 'Normal', 'Tangent', 'Bitangent', 'Color', 'UV', "
686 "'VertexIndex', 'FacetIndex', 'CornerIndex', 'EdgeIndex'"
687 "], None] = None, "
688 "initial_values: typing.Union[numpy.typing.NDArray, typing.List[float], None] = None, "
689 "initial_indices: typing.Union[numpy.typing.NDArray, typing.List[int], None] = None, "
690 "num_channels: typing.Optional[int] = None, "
691 "dtype: typing.Optional[numpy.typing.DTypeLike] = None) -> int"),
692 R"(Create an attribute.
693
694:param name: Name of the attribute.
695:param element: Element type of the attribute. If None, derive from the shape of initial values.
696:param usage: Usage type of the attribute. If None, derive from the shape of initial values or the number of channels.
697:param initial_values: Initial values of the attribute.
698:param initial_indices: Initial indices of the attribute (Indexed attribute only).
699:param num_channels: Number of channels of the attribute.
700:param dtype: Data type of the attribute.
701
702:returns: The id of the created attribute.
703
704.. note::
705 If `element` is None, it will be derived based on the cardinality of the mesh elements.
706 If there is an ambiguity, an exception will be raised.
707 In addition, explicit `element` specification is required for value attributes.
708
709.. note::
710 If `usage` is None, it will be derived based on the shape of `initial_values` or `num_channels` if specified.)");
711
712 surface_mesh_class.def(
713 "create_attribute_from",
714 &MeshType::template create_attribute_from<Scalar, Index>,
715 "name"_a,
716 "source_mesh"_a,
717 "source_name"_a = "",
718 R"(Shallow copy an attribute from another mesh.
719
720:param name: Name of the attribute.
721:param source_mesh: Source mesh.
722:param source_name: Name of the attribute in the source mesh. If empty, use the same name as `name`.
723
724:returns: The id of the created attribute.)");
725
726 surface_mesh_class.def(
727 "wrap_as_attribute",
728 [](MeshType& self,
729 std::string_view name,
730 AttributeElement element,
731 AttributeUsage usage,
732 GenericTensor values) {
733 auto wrap_as_attribute = [&](auto tensor) {
734 using ValueType = typename std::decay_t<decltype(tensor)>::Scalar;
735 auto [data, shape, stride] = tensor_to_span(tensor);
736 la_runtime_assert(is_dense(shape, stride));
737 Index num_channels = shape.size() == 1 ? 1 : static_cast<Index>(shape[1]);
738 AttributeId id;
739 auto owner = std::make_shared<nb::object>(nb::find(values));
740 if constexpr (std::is_const_v<ValueType>) {
741 id = self.wrap_as_const_attribute(
742 name,
743 element,
744 usage,
745 num_channels,
746 make_shared_span(owner, data.data(), data.size()));
747 } else {
748 id = self.wrap_as_attribute(
749 name,
750 element,
751 usage,
752 num_channels,
753 make_shared_span(owner, data.data(), data.size()));
754 }
755 auto& attr = self.template ref_attribute<ValueType>(id);
756 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
757 return id;
758 };
759
760#define LA_X_wrap_as_attribute(_, ValueType) \
761 if (values.dtype() == nb::dtype<ValueType>()) { \
762 Tensor<ValueType> local_values(values.handle()); \
763 return wrap_as_attribute(local_values); \
764 }
765 LA_ATTRIBUTE_X(wrap_as_attribute, 0)
766#undef LA_X_wrap_as_attribute
767 throw nb::type_error("Unsupported value type!");
768 },
769 "name"_a,
770 "element"_a,
771 "usage"_a,
772 "values"_a,
773 R"(Wrap an existing numpy array as an attribute.
774
775:param name: Name of the attribute.
776:param element: Element type of the attribute.
777:param usage: Usage type of the attribute.
778:param values: Values of the attribute.
779
780:returns: The id of the created attribute.)");
781 surface_mesh_class.def(
782 "wrap_as_indexed_attribute",
783 [](MeshType& self,
784 std::string_view name,
785 AttributeUsage usage,
786 GenericTensor values,
787 Tensor<Index> indices) {
788 auto wrap_as_indexed_attribute = [&](auto value_tensor, auto index_tensor) {
789 using ValueType = typename std::decay_t<decltype(value_tensor)>::Scalar;
790 auto [value_data, value_shape, value_stride] = tensor_to_span(value_tensor);
791 auto [index_data, index_shape, index_stride] = tensor_to_span(index_tensor);
792 la_runtime_assert(is_dense(value_shape, value_stride));
793 la_runtime_assert(is_dense(index_shape, index_stride));
794 Index num_values = static_cast<Index>(value_shape[0]);
795 Index num_channels =
796 value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]);
797 AttributeId id;
798
799 auto value_owner = std::make_shared<nb::object>(nb::find(values));
800 auto index_owner = std::make_shared<nb::object>(nb::find(indices));
801
802 if constexpr (std::is_const_v<ValueType>) {
803 id = self.wrap_as_const_indexed_attribute(
804 name,
805 usage,
806 num_values,
807 num_channels,
808 make_shared_span(value_owner, value_data.data(), value_data.size()),
809 make_shared_span(index_owner, index_data.data(), index_data.size()));
810 } else {
811 id = self.wrap_as_indexed_attribute(
812 name,
813 usage,
814 num_values,
815 num_channels,
816 make_shared_span(value_owner, value_data.data(), value_data.size()),
817 make_shared_span(index_owner, index_data.data(), index_data.size()));
818 }
819 auto& attr = self.template ref_indexed_attribute<ValueType>(id);
820 attr.values().set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
821 attr.indices().set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
822 return id;
823 };
824
825#define LA_X_wrap_as_indexed_attribute(_, ValueType) \
826 if (values.dtype() == nb::dtype<ValueType>()) { \
827 Tensor<ValueType> local_values(values.handle()); \
828 return wrap_as_indexed_attribute(local_values, indices); \
829 }
830 LA_ATTRIBUTE_X(wrap_as_indexed_attribute, 0)
831#undef LA_X_wrap_as_indexed_attribute
832 throw nb::type_error("Unsupported value type!");
833 },
834 "name"_a,
835 "usage"_a,
836 "values"_a,
837 "indices"_a,
838 R"(Wrap an existing numpy array as an indexed attribute.
839
840:param name: Name of the attribute.
841:param usage: Usage type of the attribute.
842:param values: Values of the attribute.
843:param indices: Indices of the attribute.
844
845:returns: The id of the created attribute.)");
846 surface_mesh_class.def(
847 "duplicate_attribute",
848 &MeshType::duplicate_attribute,
849 "old_name"_a,
850 "new_name"_a,
851 R"(Duplicate an attribute with a new name.
852
853:param old_name: name of the attribute to duplicate
854:param new_name: name for the new attribute
855
856:returns: attribute ID of the duplicated attribute)");
857 surface_mesh_class.def(
858 "rename_attribute",
859 &MeshType::rename_attribute,
860 "old_name"_a,
861 "new_name"_a,
862 R"(Rename an attribute.
863
864:param old_name: current name of the attribute
865:param new_name: new name for the attribute)");
866
867 surface_mesh_class.def(
868 "delete_attribute",
869 [](MeshType& self, std::string_view name, AttributeDeletePolicy policy) {
870 self.delete_attribute(name, policy);
871 },
872 "name"_a,
874 R"(Delete an attribute by name.
875
876:param name: Name of the attribute.
877:param policy: Deletion policy for reserved attributes.)");
878 surface_mesh_class.def(
879 "delete_attribute",
880 [](MeshType& self, AttributeId id, AttributeDeletePolicy policy) {
881 self.delete_attribute(id, policy);
882 },
883 "id"_a,
885 R"(Delete an attribute by id.
886
887:param id: Id of the attribute.
888:param policy: Deletion policy for reserved attributes.)");
889 surface_mesh_class.def(
890 "has_attribute",
891 &MeshType::has_attribute,
892 "name"_a,
893 R"(Check if an attribute exists.
894
895:param name: attribute name
896
897:returns: True if the attribute exists, False otherwise)");
898 surface_mesh_class.def(
899 "is_attribute_indexed",
900 static_cast<bool (MeshType::*)(AttributeId) const>(&MeshType::is_attribute_indexed),
901 "id"_a,
902 R"(Check if an attribute is indexed.
903
904:param id: attribute ID
905
906:returns: True if the attribute is indexed, False otherwise)");
907 surface_mesh_class.def(
908 "is_attribute_indexed",
909 static_cast<bool (MeshType::*)(std::string_view) const>(&MeshType::is_attribute_indexed),
910 "name"_a,
911 R"(Check if an attribute is indexed.
912
913:param name: attribute name
914
915:returns: True if the attribute is indexed, False otherwise)");
916
917 // This is a helper method for trigger copy-on-write mechanism for a given attribute.
918 auto ensure_attribute_is_not_shared = [](MeshType& mesh, AttributeId id) {
919 auto& attr_base = mesh.get_attribute_base(id);
920#define LA_X_trigger_copy_on_write(_, ValueType) \
921 if (mesh.is_attribute_indexed(id)) { \
922 if (dynamic_cast<const IndexedAttribute<ValueType, Index>*>(&attr_base)) { \
923 [[maybe_unused]] auto& attr = mesh.template ref_indexed_attribute<ValueType>(id); \
924 } \
925 } else { \
926 if (dynamic_cast<const Attribute<ValueType>*>(&attr_base)) { \
927 [[maybe_unused]] auto& attr = mesh.template ref_attribute<ValueType>(id); \
928 } \
929 }
930 LA_ATTRIBUTE_X(trigger_copy_on_write, 0)
931#undef LA_X_trigger_copy_on_write
932 };
933
934 surface_mesh_class.def(
935 "attribute",
936 [&](MeshType& self, AttributeId id, bool sharing) {
938 !self.is_attribute_indexed(id),
939 fmt::format(
940 "Attribute {} is indexed! Please use `indexed_attribute` property "
941 "instead.",
942 id));
943 if (!sharing) ensure_attribute_is_not_shared(self, id);
944 return PyAttribute(self._ref_attribute_ptr(id));
945 },
946 "id"_a,
947 "sharing"_a = true,
948 R"(Get an attribute by id.
949
950:param id: Id of the attribute.
951:param sharing: Whether to allow sharing the attribute with other meshes.
952
953:returns: The attribute.)");
954 surface_mesh_class.def(
955 "attribute",
956 [&](MeshType& self, std::string_view name, bool sharing) {
958 !self.is_attribute_indexed(name),
959 fmt::format(
960 "Attribute \"{}\" is indexed! Please use `indexed_attribute` property "
961 "instead.",
962 name));
963 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
964 return PyAttribute(self._ref_attribute_ptr(name));
965 },
966 "name"_a,
967 "sharing"_a = true,
968 R"(Get an attribute by name.
969
970:param name: Name of the attribute.
971:param sharing: Whether to allow sharing the attribute with other meshes.
972
973:return: The attribute.)");
974 surface_mesh_class.def(
975 "indexed_attribute",
976 [&](MeshType& self, AttributeId id, bool sharing) {
978 self.is_attribute_indexed(id),
979 fmt::format(
980 "Attribute {} is not indexed! Please use `attribute` property instead.",
981 id));
982 if (!sharing) ensure_attribute_is_not_shared(self, id);
983 return PyIndexedAttribute(self._ref_attribute_ptr(id));
984 },
985 "id"_a,
986 "sharing"_a = true,
987 R"(Get an indexed attribute by id.
988
989:param id: Id of the attribute.
990:param sharing: Whether to allow sharing the attribute with other meshes.
991
992:returns: The indexed attribute.)");
993 surface_mesh_class.def(
994 "indexed_attribute",
995 [&](MeshType& self, std::string_view name, bool sharing) {
997 self.is_attribute_indexed(name),
998 fmt::format(
999 "Attribute \"{}\"is not indexed! Please use `attribute` property instead.",
1000 name));
1001 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
1002 return PyIndexedAttribute(self._ref_attribute_ptr(name));
1003 },
1004 "name"_a,
1005 "sharing"_a = true,
1006 R"(Get an indexed attribute by name.
1007
1008:param name: Name of the attribute.
1009:param sharing: Whether to allow sharing the attribute with other meshes.
1010
1011:returns: The indexed attribute.)");
1012 surface_mesh_class.def(
1013 "__attribute_ref_count__",
1014 [](MeshType& self, AttributeId id) {
1015 auto ptr = self._get_attribute_ptr(id);
1016 return ptr.use_count();
1017 },
1018 "id"_a,
1019 R"(Get the reference count of an attribute (for debugging purposes).
1020
1021:param id: attribute ID
1022
1023:returns: reference count of the attribute)");
1024 surface_mesh_class.def_prop_rw(
1025 "vertices",
1026 [](const MeshType& self) {
1027 const auto& attr = self.get_vertex_to_position();
1028 return attribute_to_tensor(attr, nb::find(&self));
1029 },
1030 [](MeshType& self, Tensor<Scalar> tensor) {
1031 auto [values, shape, stride] = tensor_to_span(tensor);
1032 la_runtime_assert(is_dense(shape, stride));
1033 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
1034
1035 size_t num_vertices = shape.size() == 1 ? 1 : shape[0];
1036 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1037 auto id = self.wrap_as_vertices(
1038 make_shared_span(owner, values.data(), values.size()),
1039 static_cast<Index>(num_vertices));
1040 auto& attr = self.template ref_attribute<Scalar>(id);
1041 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1042 },
1043 "Vertices of the mesh.");
1044 surface_mesh_class.def_prop_rw(
1045 "facets",
1046 [](const MeshType& self) {
1047 if (self.is_regular()) {
1048 const auto& attr = self.get_corner_to_vertex();
1049 const size_t shape[2] = {
1050 static_cast<size_t>(self.get_num_facets()),
1051 static_cast<size_t>(self.get_vertex_per_facet())};
1052 return attribute_to_tensor(attr, shape, nb::find(&self));
1053 } else {
1054 logger().warn("Mesh is not regular, returning the flattened facets.");
1055 const auto& attr = self.get_corner_to_vertex();
1056 return attribute_to_tensor(attr, nb::find(&self));
1057 }
1058 },
1059 [](MeshType& self, Tensor<Index> tensor) {
1060 auto [values, shape, stride] = tensor_to_span(tensor);
1061 la_runtime_assert(is_dense(shape, stride));
1062
1063 const size_t num_facets = shape.size() == 1 ? 1 : shape[0];
1064 const size_t vertex_per_facet = shape.size() == 1 ? shape[0] : shape[1];
1065 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1066 auto id = self.wrap_as_facets(
1067 make_shared_span(owner, values.data(), values.size()),
1068 static_cast<Index>(num_facets),
1069 static_cast<Index>(vertex_per_facet));
1070 auto& attr = self.template ref_attribute<Index>(id);
1071 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1072 },
1073 "Facets of the mesh.");
1074 surface_mesh_class.def_prop_ro(
1075 "edges",
1076 [](MeshType& self) {
1077 self.initialize_edges();
1078 auto num_edges = self.get_num_edges();
1079 std::vector<Index> data(num_edges * 2);
1080 tbb::parallel_for(Index{0}, num_edges, [&](Index i) {
1081 auto [v0, v1] = self.get_edge_vertices(i);
1082 data[i * 2] = v0;
1083 data[i * 2 + 1] = v1;
1084 });
1085 nb::ndarray<Index, nb::shape<-1, 2>, nb::numpy, nb::c_contig, nb::device::cpu> edges(
1086 data.data(),
1087 {static_cast<size_t>(num_edges), 2});
1088 return edges.cast();
1089 },
1090 "Edges of the mesh.");
1091 surface_mesh_class.def(
1092 "wrap_as_vertices",
1093 [](MeshType& self, Tensor<Scalar> tensor, Index num_vertices) {
1094 auto [values, shape, stride] = tensor_to_span(tensor);
1095 la_runtime_assert(is_dense(shape, stride));
1096 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
1097
1098 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1099 auto id = self.wrap_as_vertices(
1100 make_shared_span(owner, values.data(), values.size()),
1101 num_vertices);
1102 auto& attr = self.template ref_attribute<Scalar>(id);
1103 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1104 return id;
1105 },
1106 "tensor"_a,
1107 "num_vertices"_a,
1108 R"(Wrap a tensor as vertices.
1109
1110:param tensor: The tensor to wrap.
1111:param num_vertices: Number of vertices.
1112
1113:return: The id of the wrapped vertices attribute.)");
1114 surface_mesh_class.def(
1115 "wrap_as_facets",
1116 [](MeshType& self, Tensor<Index> tensor, Index num_facets, Index vertex_per_facet) {
1117 auto [values, shape, stride] = tensor_to_span(tensor);
1118 la_runtime_assert(is_dense(shape, stride));
1119
1120 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1121 auto id = self.wrap_as_facets(
1122 make_shared_span(owner, values.data(), values.size()),
1123 num_facets,
1124 vertex_per_facet);
1125 auto& attr = self.template ref_attribute<Index>(id);
1126 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1127 return id;
1128 },
1129 "tensor"_a,
1130 "num_facets"_a,
1131 "vertex_per_facet"_a,
1132 R"(Wrap a tensor as a list of regular facets.
1133
1134:param tensor: The tensor to wrap.
1135:param num_facets: Number of facets.
1136:param vertex_per_facet: Number of vertices per facet.
1137
1138:return: The id of the wrapped facet attribute.)");
1139 surface_mesh_class.def(
1140 "wrap_as_facets",
1141 [](MeshType& self,
1142 Tensor<Index> offsets,
1143 Index num_facets,
1144 Tensor<Index> facets,
1145 Index num_corners) {
1146 auto [offsets_data, offsets_shape, offsets_stride] = tensor_to_span(offsets);
1147 auto [facets_data, facets_shape, facets_stride] = tensor_to_span(facets);
1148 la_runtime_assert(is_dense(offsets_shape, offsets_stride));
1149 la_runtime_assert(is_dense(facets_shape, facets_stride));
1150
1151 auto offsets_owner = std::make_shared<nb::object>(nb::find(offsets));
1152 auto facets_owner = std::make_shared<nb::object>(nb::find(facets));
1153
1154 auto id = self.wrap_as_facets(
1155 make_shared_span(offsets_owner, offsets_data.data(), offsets_data.size()),
1156 num_facets,
1157 make_shared_span(facets_owner, facets_data.data(), facets_data.size()),
1158 num_corners);
1159 auto& attr = self.template ref_attribute<Index>(id);
1160 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1161 return id;
1162 },
1163 "offsets"_a,
1164 "num_facets"_a,
1165 "facets"_a,
1166 "num_corners"_a,
1167 R"(Wrap a tensor as a list of hybrid facets.
1168
1169:param offsets: The offset indices into the facets array.
1170:param num_facets: Number of facets.
1171:param facets: The indices of the vertices of the facets.
1172:param num_corners: Number of corners.
1173
1174:return: The id of the wrapped facet attribute.)");
1175 surface_mesh_class.def_static(
1176 "attr_name_is_reserved",
1177 &MeshType::attr_name_is_reserved,
1178 "name"_a,
1179 R"(Check if an attribute name is reserved.
1180
1181:param name: attribute name to check
1182
1183:returns: True if the name is reserved, False otherwise)");
1184 surface_mesh_class.def_prop_ro_static(
1185 "attr_name_vertex_to_position",
1186 [](nb::handle) { return MeshType::attr_name_vertex_to_position(); },
1187 "The name of the attribute that stores the vertex positions.");
1188 surface_mesh_class.def_prop_ro_static(
1189 "attr_name_corner_to_vertex",
1190 [](nb::handle) { return MeshType::attr_name_corner_to_vertex(); },
1191 "The name of the attribute that stores the corner to vertex mapping.");
1192 surface_mesh_class.def_prop_ro_static(
1193 "attr_name_facet_to_first_corner",
1194 [](nb::handle) { return MeshType::attr_name_facet_to_first_corner(); },
1195 "The name of the attribute that stores the facet to first corner mapping.");
1196 surface_mesh_class.def_prop_ro_static(
1197 "attr_name_corner_to_facet",
1198 [](nb::handle) { return MeshType::attr_name_corner_to_facet(); },
1199 "The name of the attribute that stores the corner to facet mapping.");
1200 surface_mesh_class.def_prop_ro_static(
1201 "attr_name_corner_to_edge",
1202 [](nb::handle) { return MeshType::attr_name_corner_to_edge(); },
1203 "The name of the attribute that stores the corner to edge mapping.");
1204 surface_mesh_class.def_prop_ro_static(
1205 "attr_name_edge_to_first_corner",
1206 [](nb::handle) { return MeshType::attr_name_edge_to_first_corner(); },
1207 "The name of the attribute that stores the edge to first corner mapping.");
1208 surface_mesh_class.def_prop_ro_static(
1209 "attr_name_next_corner_around_edge",
1210 [](nb::handle) { return MeshType::attr_name_next_corner_around_edge(); },
1211 "The name of the attribute that stores the next corner around edge mapping.");
1212 surface_mesh_class.def_prop_ro_static(
1213 "attr_name_vertex_to_first_corner",
1214 [](nb::handle) { return MeshType::attr_name_vertex_to_first_corner(); },
1215 "The name of the attribute that stores the vertex to first corner mapping.");
1216 surface_mesh_class.def_prop_ro_static(
1217 "attr_name_next_corner_around_vertex",
1218 [](nb::handle) { return MeshType::attr_name_next_corner_around_vertex(); },
1219 "The name of the attribute that stores the next corner around vertex mapping.");
1220 surface_mesh_class.def_prop_ro(
1221 "attr_id_vertex_to_position",
1222 &MeshType::attr_id_vertex_to_position);
1223 surface_mesh_class.def_prop_ro("attr_id_corner_to_vertex", &MeshType::attr_id_corner_to_vertex);
1224 surface_mesh_class.def_prop_ro(
1225 "attr_id_facet_to_first_corner",
1226 &MeshType::attr_id_facet_to_first_corner);
1227 surface_mesh_class.def_prop_ro("attr_id_corner_to_facet", &MeshType::attr_id_corner_to_facet);
1228 surface_mesh_class.def_prop_ro("attr_id_corner_to_edge", &MeshType::attr_id_corner_to_edge);
1229 surface_mesh_class.def_prop_ro(
1230 "attr_id_edge_to_first_corner",
1231 &MeshType::attr_id_edge_to_first_corner);
1232 surface_mesh_class.def_prop_ro(
1233 "attr_id_next_corner_around_edge",
1234 &MeshType::attr_id_next_corner_around_edge);
1235 surface_mesh_class.def_prop_ro(
1236 "attr_id_vertex_to_first_corner",
1237 &MeshType::attr_id_vertex_to_first_corner);
1238 surface_mesh_class.def_prop_ro(
1239 "attr_id_next_corner_around_vertex",
1240 &MeshType::attr_id_next_corner_around_vertex);
1241 surface_mesh_class.def(
1242 "initialize_edges",
1243 [](MeshType& self, std::optional<Tensor<Index>> tensor) {
1244 if (tensor.has_value()) {
1245 auto [edge_data, edge_shape, edge_stride] = tensor_to_span(tensor.value());
1246 la_runtime_assert(is_dense(edge_shape, edge_stride));
1248 edge_data.empty() || check_shape(edge_shape, invalid<size_t>(), 2),
1249 "Edge tensor mush be of the shape num_edges x 2");
1250 self.initialize_edges(edge_data);
1251 } else {
1252 self.initialize_edges();
1253 }
1254 },
1255 "edges"_a = nb::none(),
1256 R"(Initialize the edges.
1257
1258The `edges` tensor provides a predefined ordering of the edges.
1259If not provided, the edges are initialized in an arbitrary order.
1260
1261:param edges: M x 2 tensor of predefined edge vertex indices, where M is the number of edges.)");
1262 surface_mesh_class.def(
1263 "clear_edges",
1264 &MeshType::clear_edges,
1265 R"(Clear all edge connectivity information.)");
1266 surface_mesh_class.def_prop_ro("has_edges", &MeshType::has_edges);
1267 surface_mesh_class.def(
1268 "get_edge",
1270 "facet_id"_a,
1271 "lv"_a,
1272 R"(Get the edge index associated with a local vertex of a facet.
1273
1274:param facet_id: facet index
1275:param lv: local vertex index of the facet
1276
1277:returns: edge index)");
1278 surface_mesh_class.def(
1279 "get_corner_edge",
1280 &MeshType::get_corner_edge,
1281 "corner_id"_a,
1282 R"(Get the edge index associated with a corner.
1283
1284:param corner_id: corner index
1285
1286:returns: edge index)");
1287 surface_mesh_class.def(
1288 "get_edge_vertices",
1290 "edge_id"_a,
1291 R"(Get the two vertex indices of an edge.
1292
1293:param edge_id: edge index
1294
1295:returns: tuple of (vertex1_id, vertex2_id))");
1296 surface_mesh_class.def(
1297 "find_edge_from_vertices",
1299 "vertex1_id"_a,
1300 "vertex2_id"_a,
1301 R"(Find the edge connecting two vertices.
1302
1303:param vertex1_id: first vertex index
1304:param vertex2_id: second vertex index
1305
1306:returns: edge index, or invalid_index if no such edge exists)");
1307 surface_mesh_class.def(
1308 "get_first_corner_around_edge",
1309 &MeshType::get_first_corner_around_edge,
1310 "edge_id"_a,
1311 R"(Get the first corner around an edge.
1312
1313:param edge_id: edge index
1314
1315:returns: first corner index around the edge)");
1316 surface_mesh_class.def(
1317 "get_next_corner_around_edge",
1318 &MeshType::get_next_corner_around_edge,
1319 "corner_id"_a,
1320 R"(Get the next corner around the same edge.
1321
1322:param corner_id: current corner index
1323
1324:returns: next corner index around the same edge)");
1325 surface_mesh_class.def(
1326 "get_first_corner_around_vertex",
1327 &MeshType::get_first_corner_around_vertex,
1328 "vertex_id"_a,
1329 R"(Get the first corner around a vertex.
1330
1331:param vertex_id: vertex index
1332
1333:returns: first corner index around the vertex)");
1334 surface_mesh_class.def(
1335 "get_next_corner_around_vertex",
1336 &MeshType::get_next_corner_around_vertex,
1337 "corner_id"_a,
1338 R"(Get the next corner around the same vertex.
1339
1340:param corner_id: current corner index
1341
1342:returns: next corner index around the same vertex)");
1343 surface_mesh_class.def(
1344 "count_num_corners_around_edge",
1345 &MeshType::count_num_corners_around_edge,
1346 "edge_id"_a,
1347 R"(Count the number of corners around an edge.
1348
1349:param edge_id: edge index
1350
1351:returns: number of corners around the edge)");
1352 surface_mesh_class.def(
1353 "count_num_corners_around_vertex",
1354 &MeshType::count_num_corners_around_vertex,
1355 "vertex_id"_a,
1356 R"(Count the number of corners around a vertex.
1357
1358:param vertex_id: vertex index
1359
1360:returns: number of corners around the vertex)");
1361 surface_mesh_class.def(
1362 "get_counterclockwise_corner_around_vertex",
1363 &MeshType::get_counterclockwise_corner_around_vertex,
1364 "corner"_a,
1365 R"(Get the counterclockwise corner around the vertex associated with the input corner.
1366
1367.. note::
1368 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1369 corners based on edge-connectivity) will be visited.
1370
1371 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1372 is not well defined. It will return `invalid_index` in this case.
1373
1374:param corner: The input corner index.
1375
1376:returns: The counterclockwise corner index or `invalid_index` if none exists.)");
1377 surface_mesh_class.def(
1378 "get_clockwise_corner_around_vertex",
1379 &MeshType::get_clockwise_corner_around_vertex,
1380 "corner"_a,
1381 R"(Get the clockwise corner around the vertex associated with the input corner.
1382
1383.. note::
1384 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1385 corners based on edge-connectivity) will be visited.
1386
1387 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1388 is not well defined. It will return `invalid_index` in this case.
1389
1390:param corner: The input corner index.
1391
1392:returns: The clockwise corner index or `invalid_index` if none exists.)");
1393 surface_mesh_class.def(
1394 "get_one_facet_around_edge",
1396 "edge_id"_a,
1397 R"(Get one facet adjacent to an edge.
1398
1399:param edge_id: edge index
1400
1401:returns: facet index adjacent to the edge)");
1402 surface_mesh_class.def(
1403 "get_one_corner_around_edge",
1405 "edge_id"_a,
1406 R"(Get one corner around an edge.
1407
1408:param edge_id: edge index
1409
1410:returns: corner index around the edge)");
1411 surface_mesh_class.def(
1412 "get_one_corner_around_vertex",
1414 "vertex_id"_a,
1415 R"(Get one corner around a vertex.
1416
1417:param vertex_id: vertex index
1418
1419:returns: corner index around the vertex)");
1420 surface_mesh_class.def(
1421 "is_boundary_edge",
1423 "edge_id"_a,
1424 R"(Check if an edge is on the boundary.
1425
1426:param edge_id: edge index
1427
1428:returns: True if the edge is on the boundary, False otherwise)");
1429
1430 struct MetaData
1431 {
1432 MeshType* mesh = nullptr;
1433
1434 std::vector<AttributeId> get_metadata() const
1435 {
1436 la_runtime_assert(mesh != nullptr, "MetaData is not initialized.");
1437 lagrange::AttributeMatcher opts;
1438 opts.usages = AttributeUsage::String;
1439 opts.element_types = AttributeElement::Value;
1440 opts.num_channels = 1;
1441 return lagrange::find_matching_attributes(*mesh, opts);
1442 }
1443 };
1444 auto meta_data_class = nb::class_<MetaData>(m, "MetaData", "Metadata `dict` of the mesh");
1445 meta_data_class.def("__len__", [](const MetaData& self) {
1446 auto data = self.get_metadata();
1447 return data.size();
1448 });
1449 meta_data_class.def("__getitem__", [](const MetaData& self, std::string_view key) {
1450 return self.mesh->get_metadata(key);
1451 });
1452 meta_data_class.def(
1453 "__setitem__",
1454 [](MetaData& self, std::string_view key, std::string_view value) {
1455 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1456 if (self.mesh->has_attribute(key)) {
1457 self.mesh->set_metadata(key, value);
1458 } else {
1459 self.mesh->create_metadata(key, value);
1460 }
1461 });
1462 meta_data_class.def("__delitem__", [](MetaData& self, std::string_view key) {
1463 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1464 self.mesh->delete_attribute(key);
1465 });
1466 meta_data_class.def("__repr__", [](const MetaData& self) -> std::string {
1467 auto data = self.get_metadata();
1468 if (data.empty()) return "MetaData({})";
1469
1470 std::string r;
1471 for (auto id : data) {
1472 auto name = self.mesh->get_attribute_name(id);
1473 auto value = self.mesh->get_metadata(id);
1474 fmt::format_to(std::back_inserter(r), " {}: {},\n", name, value);
1475 }
1476 return fmt::format("MetaData(\n{})", r);
1477 });
1478
1479 surface_mesh_class.def_prop_ro(
1480 "metadata",
1481 [](MeshType& self) {
1482 MetaData meta_data;
1483 meta_data.mesh = &self;
1484 return meta_data;
1485 },
1486 "Metadata of the mesh.");
1487
1488 surface_mesh_class.def(
1489 "get_matching_attribute_ids",
1490 [](MeshType& self,
1491 std::optional<AttributeElement> element,
1492 std::optional<AttributeUsage> usage,
1493 Index num_channels) {
1494 AttributeMatcher opts;
1495 if (usage.has_value()) {
1496 opts.usages = usage.value();
1497 }
1498 if (element.has_value()) {
1499 opts.element_types = element.value();
1500 }
1501 opts.num_channels = num_channels;
1502 return find_matching_attributes(self, opts);
1503 },
1504 "element"_a = nb::none(),
1505 "usage"_a = nb::none(),
1506 "num_channels"_a = 0,
1507 R"(Get all matching attribute ids with the desired element type, usage and number of channels.
1508
1509:param element: The target element type. None matches all element types.
1510:param usage: The target usage type. None matches all usage types.
1511:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1512
1513:returns: A list of attribute ids matching the target element, usage and number of channels.
1514)");
1515
1516 surface_mesh_class.def(
1517 "get_matching_attribute_id",
1518 [](MeshType& self,
1519 std::optional<AttributeElement> element,
1520 std::optional<AttributeUsage> usage,
1521 Index num_channels) {
1522 std::optional<AttributeId> result;
1523 self.seq_foreach_attribute_id([&](AttributeId attr_id) {
1524 if (result.has_value()) {
1525 return;
1526 }
1527 const auto name = self.get_attribute_name(attr_id);
1528 if (self.attr_name_is_reserved(name)) return;
1529 const auto& attr = self.get_attribute_base(attr_id);
1530 if (element && attr.get_element_type() != *element) return;
1531 if (usage && attr.get_usage() != *usage) return;
1532 if (num_channels != 0 && attr.get_num_channels() != num_channels) return;
1533 result = attr_id;
1534 });
1535 return result;
1536 },
1537 "element"_a = nb::none(),
1538 "usage"_a = nb::none(),
1539 "num_channels"_a = 0,
1540 R"(Get one matching attribute id with the desired element type, usage and number of channels.
1541
1542:param element: The target element type. None matches all element types.
1543:param usage: The target usage type. None matches all usage types.
1544:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1545
1546:returns: An attribute id matching the target element, usage and number of channels, if found. None otherwise.
1547)");
1548
1549 surface_mesh_class.def(
1550 "__copy__",
1551 [](MeshType& self) -> MeshType {
1552 MeshType mesh = self;
1553 return mesh;
1554 },
1555 R"(Create a shallow copy of this mesh.)");
1556
1557 surface_mesh_class.def(
1558 "__deepcopy__",
1559 [](MeshType& self, [[maybe_unused]] std::optional<nb::dict> memo) -> MeshType {
1560 MeshType mesh = self;
1561 par_foreach_attribute_write(mesh, [](auto&& attr) {
1562 // For most of the attributes, just getting a writable reference will trigger a
1563 // copy of the buffer thanks to the copy-on-write mechanism and the default
1564 // CopyIfExternal copy policy.
1565
1566 using AttributeType = std::decay_t<decltype(attr)>;
1567 if constexpr (AttributeType::IsIndexed) {
1568 auto& value_attr = attr.values();
1569 if (value_attr.is_external()) {
1570 value_attr.create_internal_copy();
1571 }
1572 auto& index_attr = attr.indices();
1573 if (index_attr.is_external()) {
1574 index_attr.create_internal_copy();
1575 }
1576 } else {
1577 if (attr.is_external()) {
1578 attr.create_internal_copy();
1579 }
1580 }
1581 });
1582 return mesh;
1583 },
1584 "memo"_a = nb::none(),
1585 R"(Create a deep copy of this mesh.)");
1586
1587 surface_mesh_class.def(
1588 "clone",
1589 [](MeshType& self, bool strip) -> MeshType {
1590 if (strip) {
1591 return MeshType::stripped_copy(self);
1592 } else {
1593 auto py_self = nb::find(self);
1594 return nb::cast<MeshType>(py_self.attr("__deepcopy__")());
1595 }
1596 },
1597 "strip"_a = false,
1598 R"(Create a deep copy of this mesh.
1599
1600:param strip: If True, strip the mesh of all attributes except for the reserved attributes.)");
1601}
1602
1603} // namespace lagrange::python
Index find_edge_from_vertices(Index v0, Index v1) const
Definition Mesh.h:694
bool is_boundary_edge(Index e) const
Definition Mesh.h:821
Index get_one_corner_around_edge(Index e) const
Definition Mesh.h:795
Index get_edge(Index f, Index lv) const
Definition Mesh.h:664
std::array< Index, 2 > get_edge_vertices(Index e) const
Retrieve edge endpoints.
Definition Mesh.h:730
Index get_one_facet_around_edge(Index e) const
Definition Mesh.h:782
Index get_one_corner_around_vertex(Index v) const
Definition Mesh.h:808
Index get_num_edges() const
Definition Mesh.h:650
const AttributeBase & get_attribute_base(std::string_view name) const
Gets a read-only reference to the base class of attribute given its name.
Definition SurfaceMesh.cpp:1276
Definition PyAttribute.h:24
Definition PyIndexedAttribute.h:27
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
uint32_t AttributeId
Identified to be used to access an attribute.
Definition AttributeFwd.h:73
AttributeUsage
Usage tag indicating how the attribute should behave under mesh transformations.
Definition AttributeFwd.h:54
#define LA_ATTRIBUTE_INDEX_X(mode, data)
X Macro arguments for the Attribute<> class.
Definition AttributeTypes.h:80
AttributeElement
Type of element to which the attribute is attached.
Definition AttributeFwd.h:26
AttributeDeletePolicy
Policy for attribute deletion of reserved attribute names.
Definition AttributeFwd.h:172
#define LA_ATTRIBUTE_X(mode, data)
X Macro arguments for the Attribute<> class.
Definition AttributeTypes.h:48
@ ErrorIfReserved
Default deletion policy, throw an exception if attribute name is reserved.
Definition AttributeFwd.h:87
@ Position
Mesh attribute must have exactly dim channels.
Definition AttributeFwd.h:57
@ Tangent
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:59
@ Vector
Mesh attribute can have any number of channels (including 1 channel).
Definition AttributeFwd.h:55
@ CornerIndex
Single channel integer attribute indexing a mesh corner.
Definition AttributeFwd.h:65
@ VertexIndex
Single channel integer attribute indexing a mesh vertex.
Definition AttributeFwd.h:63
@ EdgeIndex
Single channel integer attribute indexing a mesh edge.
Definition AttributeFwd.h:66
@ Normal
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:58
@ FacetIndex
Single channel integer attribute indexing a mesh facet.
Definition AttributeFwd.h:64
@ Color
Mesh attribute can have 1, 2, 3 or 4 channels.
Definition AttributeFwd.h:61
@ UV
Mesh attribute must have exactly 2 channels.
Definition AttributeFwd.h:62
@ Bitangent
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:60
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
@ WarnAndCopy
Logs a warning and copy the buffer data if it grows beyond the buffer capacity.
Definition AttributeFwd.h:106
@ Value
Values that are not attached to a specific element.
Definition AttributeFwd.h:42
@ Edge
Per-edge mesh attributes.
Definition AttributeFwd.h:34
@ Indexed
Indexed mesh attributes.
Definition AttributeFwd.h:45
@ Facet
Per-facet mesh attributes.
Definition AttributeFwd.h:31
@ Corner
Per-corner mesh attributes.
Definition AttributeFwd.h:37
@ Vertex
Per-vertex mesh attributes.
Definition AttributeFwd.h:28
@ ErrorIfReserved
Default deletion policy, throw an exception if attribute name is reserved.
Definition AttributeFwd.h:173
void par_foreach_attribute_write(SurfaceMesh< Scalar, Index > &mesh, Visitor &&vis)
Applies a function in parallel to each attribute of a mesh.
Definition foreach_attribute.h:408
std::vector< AttributeId > find_matching_attributes(const SurfaceMesh< Scalar, Index > &mesh, const AttributeMatcher &options)
Finds all attributes with the specified usage/element type/number of channels.
Definition find_matching_attributes.cpp:80
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
#define la_debug_assert(...)
Debug assertion check.
Definition assert.h:194
SharedSpan< T > make_shared_span(const std::shared_ptr< Y > &r, T *element_ptr, size_t size)
Created a SharedSpan object around an internal buffer of a parent object.
Definition SharedSpan.h:101
::nonstd::span< T, Extent > span
A bounds-safe view for sequences of objects.
Definition span.h:27
constexpr T invalid()
You can use invalid<T>() to get a value that can represent "invalid" values, such as invalid indices ...
Definition invalid.h:40
BitField< AttributeElement > element_types
List of attribute element types to include. By default, all element types are included.
Definition find_matching_attributes.h:40
BitField< AttributeUsage > usages
List of attribute usages to include. By default, all usages are included.
Definition find_matching_attributes.h:37
size_t num_channels
Number of channels to match against. Default value is 0, which disables this test.
Definition find_matching_attributes.h:43