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 = values.shape(0);
457 } else if (initial_values.index() == 2) {
458 const auto& values = std::get<nb::list>(initial_values);
459 num_rows = nb::len(values);
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 surface_mesh_class.def(
867 "delete_attribute",
868 &MeshType::delete_attribute,
869 "name"_a,
870 "policy"_a /*= AttributeDeletePolicy::ErrorIfReserved*/);
871 surface_mesh_class.def(
872 "delete_attribute",
873 [](MeshType& self, std::string_view name) { self.delete_attribute(name); },
874 "name"_a,
875 R"(Delete an attribute by name.
876
877:param name: Name of the attribute.)");
878 surface_mesh_class.def(
879 "delete_attribute",
880 [](MeshType& self, AttributeId id) { self.delete_attribute(self.get_attribute_name(id)); },
881 "id"_a,
882 R"(Delete an attribute by id.
883
884:param id: Id of the attribute.)");
885 surface_mesh_class.def(
886 "has_attribute",
887 &MeshType::has_attribute,
888 "name"_a,
889 R"(Check if an attribute exists.
890
891:param name: attribute name
892
893:returns: True if the attribute exists, False otherwise)");
894 surface_mesh_class.def(
895 "is_attribute_indexed",
896 static_cast<bool (MeshType::*)(AttributeId) const>(&MeshType::is_attribute_indexed),
897 "id"_a,
898 R"(Check if an attribute is indexed.
899
900:param id: attribute ID
901
902:returns: True if the attribute is indexed, False otherwise)");
903 surface_mesh_class.def(
904 "is_attribute_indexed",
905 static_cast<bool (MeshType::*)(std::string_view) const>(&MeshType::is_attribute_indexed),
906 "name"_a,
907 R"(Check if an attribute is indexed.
908
909:param name: attribute name
910
911:returns: True if the attribute is indexed, False otherwise)");
912
913 // This is a helper method for trigger copy-on-write mechanism for a given attribute.
914 auto ensure_attribute_is_not_shared = [](MeshType& mesh, AttributeId id) {
915 auto& attr_base = mesh.get_attribute_base(id);
916#define LA_X_trigger_copy_on_write(_, ValueType) \
917 if (mesh.is_attribute_indexed(id)) { \
918 if (dynamic_cast<const IndexedAttribute<ValueType, Index>*>(&attr_base)) { \
919 [[maybe_unused]] auto& attr = mesh.template ref_indexed_attribute<ValueType>(id); \
920 } \
921 } else { \
922 if (dynamic_cast<const Attribute<ValueType>*>(&attr_base)) { \
923 [[maybe_unused]] auto& attr = mesh.template ref_attribute<ValueType>(id); \
924 } \
925 }
926 LA_ATTRIBUTE_X(trigger_copy_on_write, 0)
927#undef LA_X_trigger_copy_on_write
928 };
929
930 surface_mesh_class.def(
931 "attribute",
932 [&](MeshType& self, AttributeId id, bool sharing) {
934 !self.is_attribute_indexed(id),
935 fmt::format(
936 "Attribute {} is indexed! Please use `indexed_attribute` property "
937 "instead.",
938 id));
939 if (!sharing) ensure_attribute_is_not_shared(self, id);
940 return PyAttribute(self._ref_attribute_ptr(id));
941 },
942 "id"_a,
943 "sharing"_a = true,
944 R"(Get an attribute by id.
945
946:param id: Id of the attribute.
947:param sharing: Whether to allow sharing the attribute with other meshes.
948
949:returns: The attribute.)");
950 surface_mesh_class.def(
951 "attribute",
952 [&](MeshType& self, std::string_view name, bool sharing) {
954 !self.is_attribute_indexed(name),
955 fmt::format(
956 "Attribute \"{}\" is indexed! Please use `indexed_attribute` property "
957 "instead.",
958 name));
959 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
960 return PyAttribute(self._ref_attribute_ptr(name));
961 },
962 "name"_a,
963 "sharing"_a = true,
964 R"(Get an attribute by name.
965
966:param name: Name of the attribute.
967:param sharing: Whether to allow sharing the attribute with other meshes.
968
969:return: The attribute.)");
970 surface_mesh_class.def(
971 "indexed_attribute",
972 [&](MeshType& self, AttributeId id, bool sharing) {
974 self.is_attribute_indexed(id),
975 fmt::format(
976 "Attribute {} is not indexed! Please use `attribute` property instead.",
977 id));
978 if (!sharing) ensure_attribute_is_not_shared(self, id);
979 return PyIndexedAttribute(self._ref_attribute_ptr(id));
980 },
981 "id"_a,
982 "sharing"_a = true,
983 R"(Get an indexed attribute by id.
984
985:param id: Id of the attribute.
986:param sharing: Whether to allow sharing the attribute with other meshes.
987
988:returns: The indexed attribute.)");
989 surface_mesh_class.def(
990 "indexed_attribute",
991 [&](MeshType& self, std::string_view name, bool sharing) {
993 self.is_attribute_indexed(name),
994 fmt::format(
995 "Attribute \"{}\"is not indexed! Please use `attribute` property instead.",
996 name));
997 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
998 return PyIndexedAttribute(self._ref_attribute_ptr(name));
999 },
1000 "name"_a,
1001 "sharing"_a = true,
1002 R"(Get an indexed attribute by name.
1003
1004:param name: Name of the attribute.
1005:param sharing: Whether to allow sharing the attribute with other meshes.
1006
1007:returns: The indexed attribute.)");
1008 surface_mesh_class.def(
1009 "__attribute_ref_count__",
1010 [](MeshType& self, AttributeId id) {
1011 auto ptr = self._get_attribute_ptr(id);
1012 return ptr.use_count();
1013 },
1014 "id"_a,
1015 R"(Get the reference count of an attribute (for debugging purposes).
1016
1017:param id: attribute ID
1018
1019:returns: reference count of the attribute)");
1020 surface_mesh_class.def_prop_rw(
1021 "vertices",
1022 [](const MeshType& self) {
1023 const auto& attr = self.get_vertex_to_position();
1024 return attribute_to_tensor(attr, nb::find(&self));
1025 },
1026 [](MeshType& self, Tensor<Scalar> tensor) {
1027 auto [values, shape, stride] = tensor_to_span(tensor);
1028 la_runtime_assert(is_dense(shape, stride));
1029 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
1030
1031 size_t num_vertices = shape.size() == 1 ? 1 : shape[0];
1032 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1033 auto id = self.wrap_as_vertices(
1034 make_shared_span(owner, values.data(), values.size()),
1035 static_cast<Index>(num_vertices));
1036 auto& attr = self.template ref_attribute<Scalar>(id);
1037 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1038 },
1039 "Vertices of the mesh.");
1040 surface_mesh_class.def_prop_rw(
1041 "facets",
1042 [](const MeshType& self) {
1043 if (self.is_regular()) {
1044 const auto& attr = self.get_corner_to_vertex();
1045 const size_t shape[2] = {
1046 static_cast<size_t>(self.get_num_facets()),
1047 static_cast<size_t>(self.get_vertex_per_facet())};
1048 return attribute_to_tensor(attr, shape, nb::find(&self));
1049 } else {
1050 logger().warn("Mesh is not regular, returning the flattened facets.");
1051 const auto& attr = self.get_corner_to_vertex();
1052 return attribute_to_tensor(attr, nb::find(&self));
1053 }
1054 },
1055 [](MeshType& self, Tensor<Index> tensor) {
1056 auto [values, shape, stride] = tensor_to_span(tensor);
1057 la_runtime_assert(is_dense(shape, stride));
1058
1059 const size_t num_facets = shape.size() == 1 ? 1 : shape[0];
1060 const size_t vertex_per_facet = shape.size() == 1 ? shape[0] : shape[1];
1061 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1062 auto id = self.wrap_as_facets(
1063 make_shared_span(owner, values.data(), values.size()),
1064 static_cast<Index>(num_facets),
1065 static_cast<Index>(vertex_per_facet));
1066 auto& attr = self.template ref_attribute<Index>(id);
1067 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1068 },
1069 "Facets of the mesh.");
1070 surface_mesh_class.def_prop_ro(
1071 "edges",
1072 [](MeshType& self) {
1073 self.initialize_edges();
1074 auto num_edges = self.get_num_edges();
1075 std::vector<Index> data(num_edges * 2);
1076 tbb::parallel_for(Index{0}, num_edges, [&](Index i) {
1077 auto [v0, v1] = self.get_edge_vertices(i);
1078 data[i * 2] = v0;
1079 data[i * 2 + 1] = v1;
1080 });
1081 nb::ndarray<Index, nb::shape<-1, 2>, nb::numpy, nb::c_contig, nb::device::cpu> edges(
1082 data.data(),
1083 {static_cast<size_t>(num_edges), 2});
1084 return edges.cast();
1085 },
1086 "Edges of the mesh.");
1087 surface_mesh_class.def(
1088 "wrap_as_vertices",
1089 [](MeshType& self, Tensor<Scalar> tensor, Index num_vertices) {
1090 auto [values, shape, stride] = tensor_to_span(tensor);
1091 la_runtime_assert(is_dense(shape, stride));
1092 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
1093
1094 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1095 auto id = self.wrap_as_vertices(
1096 make_shared_span(owner, values.data(), values.size()),
1097 num_vertices);
1098 auto& attr = self.template ref_attribute<Scalar>(id);
1099 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1100 return id;
1101 },
1102 "tensor"_a,
1103 "num_vertices"_a,
1104 R"(Wrap a tensor as vertices.
1105
1106:param tensor: The tensor to wrap.
1107:param num_vertices: Number of vertices.
1108
1109:return: The id of the wrapped vertices attribute.)");
1110 surface_mesh_class.def(
1111 "wrap_as_facets",
1112 [](MeshType& self, Tensor<Index> tensor, Index num_facets, Index vertex_per_facet) {
1113 auto [values, shape, stride] = tensor_to_span(tensor);
1114 la_runtime_assert(is_dense(shape, stride));
1115
1116 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1117 auto id = self.wrap_as_facets(
1118 make_shared_span(owner, values.data(), values.size()),
1119 num_facets,
1120 vertex_per_facet);
1121 auto& attr = self.template ref_attribute<Index>(id);
1122 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1123 return id;
1124 },
1125 "tensor"_a,
1126 "num_facets"_a,
1127 "vertex_per_facet"_a,
1128 R"(Wrap a tensor as a list of regular facets.
1129
1130:param tensor: The tensor to wrap.
1131:param num_facets: Number of facets.
1132:param vertex_per_facet: Number of vertices per facet.
1133
1134:return: The id of the wrapped facet attribute.)");
1135 surface_mesh_class.def(
1136 "wrap_as_facets",
1137 [](MeshType& self,
1138 Tensor<Index> offsets,
1139 Index num_facets,
1140 Tensor<Index> facets,
1141 Index num_corners) {
1142 auto [offsets_data, offsets_shape, offsets_stride] = tensor_to_span(offsets);
1143 auto [facets_data, facets_shape, facets_stride] = tensor_to_span(facets);
1144 la_runtime_assert(is_dense(offsets_shape, offsets_stride));
1145 la_runtime_assert(is_dense(facets_shape, facets_stride));
1146
1147 auto offsets_owner = std::make_shared<nb::object>(nb::find(offsets));
1148 auto facets_owner = std::make_shared<nb::object>(nb::find(facets));
1149
1150 auto id = self.wrap_as_facets(
1151 make_shared_span(offsets_owner, offsets_data.data(), offsets_data.size()),
1152 num_facets,
1153 make_shared_span(facets_owner, facets_data.data(), facets_data.size()),
1154 num_corners);
1155 auto& attr = self.template ref_attribute<Index>(id);
1156 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1157 return id;
1158 },
1159 "offsets"_a,
1160 "num_facets"_a,
1161 "facets"_a,
1162 "num_corners"_a,
1163 R"(Wrap a tensor as a list of hybrid facets.
1164
1165:param offsets: The offset indices into the facets array.
1166:param num_facets: Number of facets.
1167:param facets: The indices of the vertices of the facets.
1168:param num_corners: Number of corners.
1169
1170:return: The id of the wrapped facet attribute.)");
1171 surface_mesh_class.def_static(
1172 "attr_name_is_reserved",
1173 &MeshType::attr_name_is_reserved,
1174 "name"_a,
1175 R"(Check if an attribute name is reserved.
1176
1177:param name: attribute name to check
1178
1179:returns: True if the name is reserved, False otherwise)");
1180 surface_mesh_class.def_prop_ro_static(
1181 "attr_name_vertex_to_position",
1182 [](nb::handle) { return MeshType::attr_name_vertex_to_position(); },
1183 "The name of the attribute that stores the vertex positions.");
1184 surface_mesh_class.def_prop_ro_static(
1185 "attr_name_corner_to_vertex",
1186 [](nb::handle) { return MeshType::attr_name_corner_to_vertex(); },
1187 "The name of the attribute that stores the corner to vertex mapping.");
1188 surface_mesh_class.def_prop_ro_static(
1189 "attr_name_facet_to_first_corner",
1190 [](nb::handle) { return MeshType::attr_name_facet_to_first_corner(); },
1191 "The name of the attribute that stores the facet to first corner mapping.");
1192 surface_mesh_class.def_prop_ro_static(
1193 "attr_name_corner_to_facet",
1194 [](nb::handle) { return MeshType::attr_name_corner_to_facet(); },
1195 "The name of the attribute that stores the corner to facet mapping.");
1196 surface_mesh_class.def_prop_ro_static(
1197 "attr_name_corner_to_edge",
1198 [](nb::handle) { return MeshType::attr_name_corner_to_edge(); },
1199 "The name of the attribute that stores the corner to edge mapping.");
1200 surface_mesh_class.def_prop_ro_static(
1201 "attr_name_edge_to_first_corner",
1202 [](nb::handle) { return MeshType::attr_name_edge_to_first_corner(); },
1203 "The name of the attribute that stores the edge to first corner mapping.");
1204 surface_mesh_class.def_prop_ro_static(
1205 "attr_name_next_corner_around_edge",
1206 [](nb::handle) { return MeshType::attr_name_next_corner_around_edge(); },
1207 "The name of the attribute that stores the next corner around edge mapping.");
1208 surface_mesh_class.def_prop_ro_static(
1209 "attr_name_vertex_to_first_corner",
1210 [](nb::handle) { return MeshType::attr_name_vertex_to_first_corner(); },
1211 "The name of the attribute that stores the vertex to first corner mapping.");
1212 surface_mesh_class.def_prop_ro_static(
1213 "attr_name_next_corner_around_vertex",
1214 [](nb::handle) { return MeshType::attr_name_next_corner_around_vertex(); },
1215 "The name of the attribute that stores the next corner around vertex mapping.");
1216 surface_mesh_class.def_prop_ro(
1217 "attr_id_vertex_to_position",
1218 &MeshType::attr_id_vertex_to_position);
1219 surface_mesh_class.def_prop_ro("attr_id_corner_to_vertex", &MeshType::attr_id_corner_to_vertex);
1220 surface_mesh_class.def_prop_ro(
1221 "attr_id_facet_to_first_corner",
1222 &MeshType::attr_id_facet_to_first_corner);
1223 surface_mesh_class.def_prop_ro("attr_id_corner_to_facet", &MeshType::attr_id_corner_to_facet);
1224 surface_mesh_class.def_prop_ro("attr_id_corner_to_edge", &MeshType::attr_id_corner_to_edge);
1225 surface_mesh_class.def_prop_ro(
1226 "attr_id_edge_to_first_corner",
1227 &MeshType::attr_id_edge_to_first_corner);
1228 surface_mesh_class.def_prop_ro(
1229 "attr_id_next_corner_around_edge",
1230 &MeshType::attr_id_next_corner_around_edge);
1231 surface_mesh_class.def_prop_ro(
1232 "attr_id_vertex_to_first_corner",
1233 &MeshType::attr_id_vertex_to_first_corner);
1234 surface_mesh_class.def_prop_ro(
1235 "attr_id_next_corner_around_vertex",
1236 &MeshType::attr_id_next_corner_around_vertex);
1237 surface_mesh_class.def(
1238 "initialize_edges",
1239 [](MeshType& self, std::optional<Tensor<Index>> tensor) {
1240 if (tensor.has_value()) {
1241 auto [edge_data, edge_shape, edge_stride] = tensor_to_span(tensor.value());
1242 la_runtime_assert(is_dense(edge_shape, edge_stride));
1244 edge_data.empty() || check_shape(edge_shape, invalid<size_t>(), 2),
1245 "Edge tensor mush be of the shape num_edges x 2");
1246 self.initialize_edges(edge_data);
1247 } else {
1248 self.initialize_edges();
1249 }
1250 },
1251 "edges"_a = nb::none(),
1252 R"(Initialize the edges.
1253
1254The `edges` tensor provides a predefined ordering of the edges.
1255If not provided, the edges are initialized in an arbitrary order.
1256
1257:param edges: M x 2 tensor of predefined edge vertex indices, where M is the number of edges.)");
1258 surface_mesh_class.def(
1259 "clear_edges",
1260 &MeshType::clear_edges,
1261 R"(Clear all edge connectivity information.)");
1262 surface_mesh_class.def_prop_ro("has_edges", &MeshType::has_edges);
1263 surface_mesh_class.def(
1264 "get_edge",
1266 "facet_id"_a,
1267 "lv"_a,
1268 R"(Get the edge index associated with a local vertex of a facet.
1269
1270:param facet_id: facet index
1271:param lv: local vertex index of the facet
1272
1273:returns: edge index)");
1274 surface_mesh_class.def(
1275 "get_corner_edge",
1276 &MeshType::get_corner_edge,
1277 "corner_id"_a,
1278 R"(Get the edge index associated with a corner.
1279
1280:param corner_id: corner index
1281
1282:returns: edge index)");
1283 surface_mesh_class.def(
1284 "get_edge_vertices",
1286 "edge_id"_a,
1287 R"(Get the two vertex indices of an edge.
1288
1289:param edge_id: edge index
1290
1291:returns: tuple of (vertex1_id, vertex2_id))");
1292 surface_mesh_class.def(
1293 "find_edge_from_vertices",
1295 "vertex1_id"_a,
1296 "vertex2_id"_a,
1297 R"(Find the edge connecting two vertices.
1298
1299:param vertex1_id: first vertex index
1300:param vertex2_id: second vertex index
1301
1302:returns: edge index, or invalid_index if no such edge exists)");
1303 surface_mesh_class.def(
1304 "get_first_corner_around_edge",
1305 &MeshType::get_first_corner_around_edge,
1306 "edge_id"_a,
1307 R"(Get the first corner around an edge.
1308
1309:param edge_id: edge index
1310
1311:returns: first corner index around the edge)");
1312 surface_mesh_class.def(
1313 "get_next_corner_around_edge",
1314 &MeshType::get_next_corner_around_edge,
1315 "corner_id"_a,
1316 R"(Get the next corner around the same edge.
1317
1318:param corner_id: current corner index
1319
1320:returns: next corner index around the same edge)");
1321 surface_mesh_class.def(
1322 "get_first_corner_around_vertex",
1323 &MeshType::get_first_corner_around_vertex,
1324 "vertex_id"_a,
1325 R"(Get the first corner around a vertex.
1326
1327:param vertex_id: vertex index
1328
1329:returns: first corner index around the vertex)");
1330 surface_mesh_class.def(
1331 "get_next_corner_around_vertex",
1332 &MeshType::get_next_corner_around_vertex,
1333 "corner_id"_a,
1334 R"(Get the next corner around the same vertex.
1335
1336:param corner_id: current corner index
1337
1338:returns: next corner index around the same vertex)");
1339 surface_mesh_class.def(
1340 "count_num_corners_around_edge",
1341 &MeshType::count_num_corners_around_edge,
1342 "edge_id"_a,
1343 R"(Count the number of corners around an edge.
1344
1345:param edge_id: edge index
1346
1347:returns: number of corners around the edge)");
1348 surface_mesh_class.def(
1349 "count_num_corners_around_vertex",
1350 &MeshType::count_num_corners_around_vertex,
1351 "vertex_id"_a,
1352 R"(Count the number of corners around a vertex.
1353
1354:param vertex_id: vertex index
1355
1356:returns: number of corners around the vertex)");
1357 surface_mesh_class.def(
1358 "get_counterclockwise_corner_around_vertex",
1359 &MeshType::get_counterclockwise_corner_around_vertex,
1360 "corner"_a,
1361 R"(Get the counterclockwise corner around the vertex associated with the input corner.
1362
1363.. note::
1364 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1365 corners based on edge-connectivity) will be visited.
1366
1367 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1368 is not well defined. It will return `invalid_index` in this case.
1369
1370:param corner: The input corner index.
1371
1372:returns: The counterclockwise corner index or `invalid_index` if none exists.)");
1373 surface_mesh_class.def(
1374 "get_clockwise_corner_around_vertex",
1375 &MeshType::get_clockwise_corner_around_vertex,
1376 "corner"_a,
1377 R"(Get the clockwise corner around the vertex associated with the input corner.
1378
1379.. note::
1380 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1381 corners based on edge-connectivity) will be visited.
1382
1383 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1384 is not well defined. It will return `invalid_index` in this case.
1385
1386:param corner: The input corner index.
1387
1388:returns: The clockwise corner index or `invalid_index` if none exists.)");
1389 surface_mesh_class.def(
1390 "get_one_facet_around_edge",
1392 "edge_id"_a,
1393 R"(Get one facet adjacent to an edge.
1394
1395:param edge_id: edge index
1396
1397:returns: facet index adjacent to the edge)");
1398 surface_mesh_class.def(
1399 "get_one_corner_around_edge",
1401 "edge_id"_a,
1402 R"(Get one corner around an edge.
1403
1404:param edge_id: edge index
1405
1406:returns: corner index around the edge)");
1407 surface_mesh_class.def(
1408 "get_one_corner_around_vertex",
1410 "vertex_id"_a,
1411 R"(Get one corner around a vertex.
1412
1413:param vertex_id: vertex index
1414
1415:returns: corner index around the vertex)");
1416 surface_mesh_class.def(
1417 "is_boundary_edge",
1419 "edge_id"_a,
1420 R"(Check if an edge is on the boundary.
1421
1422:param edge_id: edge index
1423
1424:returns: True if the edge is on the boundary, False otherwise)");
1425
1426 struct MetaData
1427 {
1428 MeshType* mesh = nullptr;
1429
1430 std::vector<AttributeId> get_metadata() const
1431 {
1432 la_runtime_assert(mesh != nullptr, "MetaData is not initialized.");
1433 lagrange::AttributeMatcher opts;
1434 opts.usages = AttributeUsage::String;
1435 opts.element_types = AttributeElement::Value;
1436 opts.num_channels = 1;
1437 return lagrange::find_matching_attributes(*mesh, opts);
1438 }
1439 };
1440 auto meta_data_class = nb::class_<MetaData>(m, "MetaData", "Metadata `dict` of the mesh");
1441 meta_data_class.def("__len__", [](const MetaData& self) {
1442 auto data = self.get_metadata();
1443 return data.size();
1444 });
1445 meta_data_class.def("__getitem__", [](const MetaData& self, std::string_view key) {
1446 return self.mesh->get_metadata(key);
1447 });
1448 meta_data_class.def(
1449 "__setitem__",
1450 [](MetaData& self, std::string_view key, std::string_view value) {
1451 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1452 if (self.mesh->has_attribute(key)) {
1453 self.mesh->set_metadata(key, value);
1454 } else {
1455 self.mesh->create_metadata(key, value);
1456 }
1457 });
1458 meta_data_class.def("__delitem__", [](MetaData& self, std::string_view key) {
1459 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1460 self.mesh->delete_attribute(key);
1461 });
1462 meta_data_class.def("__repr__", [](const MetaData& self) -> std::string {
1463 auto data = self.get_metadata();
1464 if (data.empty()) return "MetaData({})";
1465
1466 std::string r;
1467 for (auto id : data) {
1468 auto name = self.mesh->get_attribute_name(id);
1469 auto value = self.mesh->get_metadata(id);
1470 fmt::format_to(std::back_inserter(r), " {}: {},\n", name, value);
1471 }
1472 return fmt::format("MetaData(\n{})", r);
1473 });
1474
1475 surface_mesh_class.def_prop_ro(
1476 "metadata",
1477 [](MeshType& self) {
1478 MetaData meta_data;
1479 meta_data.mesh = &self;
1480 return meta_data;
1481 },
1482 "Metadata of the mesh.");
1483
1484 surface_mesh_class.def(
1485 "get_matching_attribute_ids",
1486 [](MeshType& self,
1487 std::optional<AttributeElement> element,
1488 std::optional<AttributeUsage> usage,
1489 Index num_channels) {
1490 AttributeMatcher opts;
1491 if (usage.has_value()) {
1492 opts.usages = usage.value();
1493 }
1494 if (element.has_value()) {
1495 opts.element_types = element.value();
1496 }
1497 opts.num_channels = num_channels;
1498 return find_matching_attributes(self, opts);
1499 },
1500 "element"_a = nb::none(),
1501 "usage"_a = nb::none(),
1502 "num_channels"_a = 0,
1503 R"(Get all matching attribute ids with the desired element type, usage and number of channels.
1504
1505:param element: The target element type. None matches all element types.
1506:param usage: The target usage type. None matches all usage types.
1507:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1508
1509:returns: A list of attribute ids matching the target element, usage and number of channels.
1510)");
1511
1512 surface_mesh_class.def(
1513 "get_matching_attribute_id",
1514 [](MeshType& self,
1515 std::optional<AttributeElement> element,
1516 std::optional<AttributeUsage> usage,
1517 Index num_channels) {
1518 std::optional<AttributeId> result;
1519 self.seq_foreach_attribute_id([&](AttributeId attr_id) {
1520 if (result.has_value()) {
1521 return;
1522 }
1523 const auto name = self.get_attribute_name(attr_id);
1524 if (self.attr_name_is_reserved(name)) return;
1525 const auto& attr = self.get_attribute_base(attr_id);
1526 if (element && attr.get_element_type() != *element) return;
1527 if (usage && attr.get_usage() != *usage) return;
1528 if (num_channels != 0 && attr.get_num_channels() != num_channels) return;
1529 result = attr_id;
1530 });
1531 return result;
1532 },
1533 "element"_a = nb::none(),
1534 "usage"_a = nb::none(),
1535 "num_channels"_a = 0,
1536 R"(Get one matching attribute id with the desired element type, usage and number of channels.
1537
1538:param element: The target element type. None matches all element types.
1539:param usage: The target usage type. None matches all usage types.
1540:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1541
1542:returns: An attribute id matching the target element, usage and number of channels, if found. None otherwise.
1543)");
1544
1545 surface_mesh_class.def(
1546 "__copy__",
1547 [](MeshType& self) -> MeshType {
1548 MeshType mesh = self;
1549 return mesh;
1550 },
1551 R"(Create a shallow copy of this mesh.)");
1552
1553 surface_mesh_class.def(
1554 "__deepcopy__",
1555 [](MeshType& self, [[maybe_unused]] std::optional<nb::dict> memo) -> MeshType {
1556 MeshType mesh = self;
1557 par_foreach_attribute_write(mesh, [](auto&& attr) {
1558 // For most of the attributes, just getting a writable reference will trigger a
1559 // copy of the buffer thanks to the copy-on-write mechanism and the default
1560 // CopyIfExternal copy policy.
1561
1562 using AttributeType = std::decay_t<decltype(attr)>;
1563 if constexpr (AttributeType::IsIndexed) {
1564 auto& value_attr = attr.values();
1565 if (value_attr.is_external()) {
1566 value_attr.create_internal_copy();
1567 }
1568 auto& index_attr = attr.indices();
1569 if (index_attr.is_external()) {
1570 index_attr.create_internal_copy();
1571 }
1572 } else {
1573 if (attr.is_external()) {
1574 attr.create_internal_copy();
1575 }
1576 }
1577 });
1578 return mesh;
1579 },
1580 "memo"_a = nb::none(),
1581 R"(Create a deep copy of this mesh.)");
1582
1583 surface_mesh_class.def(
1584 "clone",
1585 [](MeshType& self, bool strip) -> MeshType {
1586 if (strip) {
1587 return MeshType::stripped_copy(self);
1588 } else {
1589 auto py_self = nb::find(self);
1590 return nb::cast<MeshType>(py_self.attr("__deepcopy__")());
1591 }
1592 },
1593 "strip"_a = false,
1594 R"(Create a deep copy of this mesh.
1595
1596:param strip: If True, strip the mesh of all attributes except for the reserved attributes.)");
1597}
1598
1599} // 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:1270
Definition PyAttribute.h:24
Definition PyIndexedAttribute.h:27
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
uint32_t AttributeId
Identified to be used to access an attribute.
Definition AttributeFwd.h:73
AttributeUsage
Usage tag indicating how the attribute should behave under mesh transformations.
Definition AttributeFwd.h:54
#define LA_ATTRIBUTE_INDEX_X(mode, data)
X Macro arguments for the Attribute<> class.
Definition AttributeTypes.h:80
AttributeElement
Type of element to which the attribute is attached.
Definition AttributeFwd.h:26
#define LA_ATTRIBUTE_X(mode, data)
X Macro arguments for the Attribute<> class.
Definition AttributeTypes.h:48
@ ErrorIfReserved
Default deletion policy, throw an exception if attribute name is reserved.
Definition AttributeFwd.h:87
@ Position
Mesh attribute must have exactly dim channels.
Definition AttributeFwd.h:57
@ Tangent
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:59
@ Vector
Mesh attribute can have any number of channels (including 1 channel).
Definition AttributeFwd.h:55
@ CornerIndex
Single channel integer attribute indexing a mesh corner.
Definition AttributeFwd.h:65
@ VertexIndex
Single channel integer attribute indexing a mesh vertex.
Definition AttributeFwd.h:63
@ EdgeIndex
Single channel integer attribute indexing a mesh edge.
Definition AttributeFwd.h:66
@ Normal
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:58
@ FacetIndex
Single channel integer attribute indexing a mesh facet.
Definition AttributeFwd.h:64
@ Color
Mesh attribute can have 1, 2, 3 or 4 channels.
Definition AttributeFwd.h:61
@ UV
Mesh attribute must have exactly 2 channels.
Definition AttributeFwd.h:62
@ Bitangent
Mesh attribute can have dim or dim + 1 channels.
Definition AttributeFwd.h:60
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
@ WarnAndCopy
Logs a warning and copy the buffer data if it grows beyond the buffer capacity.
Definition AttributeFwd.h:106
@ Value
Values that are not attached to a specific element.
Definition AttributeFwd.h:42
@ Edge
Per-edge mesh attributes.
Definition AttributeFwd.h:34
@ Indexed
Indexed mesh attributes.
Definition AttributeFwd.h:45
@ Facet
Per-facet mesh attributes.
Definition AttributeFwd.h:31
@ Corner
Per-corner mesh attributes.
Definition AttributeFwd.h:37
@ Vertex
Per-vertex mesh attributes.
Definition AttributeFwd.h:28
void par_foreach_attribute_write(SurfaceMesh< Scalar, Index > &mesh, Visitor &&vis)
Applies a function in parallel to each attribute of a mesh.
Definition foreach_attribute.h:408
std::vector< AttributeId > find_matching_attributes(const SurfaceMesh< Scalar, Index > &mesh, const AttributeMatcher &options)
Finds all attributes with the specified usage/element type/number of channels.
Definition find_matching_attributes.cpp:80
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
#define la_debug_assert(...)
Debug assertion check.
Definition assert.h:194
SharedSpan< T > make_shared_span(const std::shared_ptr< Y > &r, T *element_ptr, size_t size)
Created a SharedSpan object around an internal buffer of a parent object.
Definition SharedSpan.h:101
::nonstd::span< T, Extent > span
A bounds-safe view for sequences of objects.
Definition span.h:27
constexpr T invalid()
You can use invalid<T>() to get a value that can represent "invalid" values, such as invalid indices ...
Definition invalid.h:40
BitField< AttributeElement > element_types
List of attribute element types to include. By default, all element types are included.
Definition find_matching_attributes.h:40
BitField< AttributeUsage > usages
List of attribute usages to include. By default, all usages are included.
Definition find_matching_attributes.h:37
size_t num_channels
Number of channels to match against. Default value is 0, which disables this test.
Definition find_matching_attributes.h:43