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 "flip_facets",
245 [](MeshType& self, Tensor<Index> b, AttributeReorientPolicy policy) {
246 auto [data, shape, stride] = tensor_to_span(b);
247 la_runtime_assert(is_dense(shape, stride));
248 la_runtime_assert(is_vector(shape));
249 self.flip_facets(data, policy);
250 },
251 "facets"_a,
253 R"(Flip the orientation of selected facets.
254
255:param facets: 1D tensor of facet indices to flip
256:param policy: Whether to reorient associated attributes like normals and bitangents.)");
257 surface_mesh_class.def(
258 "flip_facets",
259 [](MeshType& self, nb::list b, AttributeReorientPolicy policy) {
260 std::vector<Index> indices;
261 for (auto i : b) {
262 indices.push_back(nb::cast<Index>(i));
263 }
264 self.flip_facets(indices, policy);
265 },
266 "facets"_a,
268 R"(Flip the orientation of selected facets.
269
270:param facets: list of facet indices to flip
271:param policy: Whether to reorient associated attributes like normals and bitangents.)");
272 surface_mesh_class.def(
273 "clear_vertices",
274 &MeshType::clear_vertices,
275 R"(Remove all vertices from the mesh.)");
276 surface_mesh_class.def(
277 "clear_facets",
278 &MeshType::clear_facets,
279 R"(Remove all facets from the mesh.)");
280 surface_mesh_class.def(
281 "shrink_to_fit",
282 &MeshType::shrink_to_fit,
283 R"(Shrink the internal storage to fit the current mesh size.)");
284 surface_mesh_class.def(
285 "compress_if_regular",
286 &MeshType::compress_if_regular,
287 R"(Compress the mesh representation if it is regular (all facets have the same number of vertices).
288
289:returns: True if the mesh was compressed, False otherwise)");
290 surface_mesh_class.def_prop_ro("is_triangle_mesh", &MeshType::is_triangle_mesh);
291 surface_mesh_class.def_prop_ro("is_quad_mesh", &MeshType::is_quad_mesh);
292 surface_mesh_class.def_prop_ro("is_regular", &MeshType::is_regular);
293 surface_mesh_class.def_prop_ro("is_hybrid", &MeshType::is_hybrid);
294 surface_mesh_class.def_prop_ro("dimension", &MeshType::get_dimension);
295 surface_mesh_class.def_prop_ro("vertex_per_facet", &MeshType::get_vertex_per_facet);
296 surface_mesh_class.def_prop_ro("num_vertices", &MeshType::get_num_vertices);
297 surface_mesh_class.def_prop_ro("num_facets", &MeshType::get_num_facets);
298 surface_mesh_class.def_prop_ro("num_corners", &MeshType::get_num_corners);
299 surface_mesh_class.def_prop_ro("num_edges", &MeshType::get_num_edges);
300 surface_mesh_class.def(
301 "get_position",
302 [](MeshType& self, Index i) {
303 return span_to_tensor(self.get_position(i), nb::find(&self));
304 },
305 "vertex_id"_a,
306 R"(Get the position of a vertex.
307
308:param vertex_id: vertex index
309
310:returns: position coordinates as a tensor)");
311 surface_mesh_class.def(
312 "ref_position",
313 [](MeshType& self, Index i) {
314 return span_to_tensor(self.ref_position(i), nb::find(&self));
315 },
316 "vertex_id"_a,
317 R"(Get a mutable reference to the position of a vertex.
318
319:param vertex_id: vertex index
320
321:returns: mutable position coordinates as a tensor)");
322 surface_mesh_class.def(
323 "get_facet_size",
324 &MeshType::get_facet_size,
325 "facet_id"_a,
326 R"(Get the number of vertices in a facet.
327
328:param facet_id: facet index
329
330:returns: number of vertices in the facet)");
331 surface_mesh_class.def(
332 "get_facet_vertex",
333 &MeshType::get_facet_vertex,
334 "facet_id"_a,
335 "local_vertex_id"_a,
336 R"(Get a vertex index from a facet.
337
338:param facet_id: facet index
339:param local_vertex_id: local vertex index within the facet (0 to facet_size-1)
340
341:returns: global vertex index)");
342 surface_mesh_class.def(
343 "get_facet_corner_begin",
344 &MeshType::get_facet_corner_begin,
345 "facet_id"_a,
346 R"(Get the first corner index of a facet.
347
348:param facet_id: facet index
349
350:returns: first corner index of the facet)");
351 surface_mesh_class.def(
352 "get_facet_corner_end",
353 &MeshType::get_facet_corner_end,
354 "facet_id"_a,
355 R"(Get the end corner index of a facet (one past the last corner).
356
357:param facet_id: facet index
358
359:returns: end corner index of the facet)");
360 surface_mesh_class.def(
361 "get_corner_vertex",
362 &MeshType::get_corner_vertex,
363 "corner_id"_a,
364 R"(Get the vertex index associated with a corner.
365
366:param corner_id: corner index
367
368:returns: vertex index)");
369 surface_mesh_class.def(
370 "get_corner_facet",
371 &MeshType::get_corner_facet,
372 "corner_id"_a,
373 R"(Get the facet index associated with a corner.
374
375:param corner_id: corner index
376
377:returns: facet index)");
378 surface_mesh_class.def(
379 "get_facet_vertices",
380 [](MeshType& self, Index f) {
381 return span_to_tensor(self.get_facet_vertices(f), nb::find(&self));
382 },
383 "facet_id"_a,
384 R"(Get all vertex indices of a facet.
385
386:param facet_id: facet index
387
388:returns: vertex indices as a tensor)");
389 surface_mesh_class.def(
390 "ref_facet_vertices",
391 [](MeshType& self, Index f) {
392 return span_to_tensor(self.ref_facet_vertices(f), nb::find(&self));
393 },
394 "facet_id"_a,
395 R"(Get a mutable reference to all vertex indices of a facet.
396
397:param facet_id: facet index
398
399:returns: mutable vertex indices as a tensor)");
400 surface_mesh_class.def(
401 "get_attribute_id",
402 &MeshType::get_attribute_id,
403 "name"_a,
404 R"(Get the attribute ID by name.
405
406:param name: attribute name
407
408:returns: attribute ID)");
409 surface_mesh_class.def(
410 "get_attribute_name",
411 &MeshType::get_attribute_name,
412 "id"_a,
413 R"(Get the attribute name by ID.
414
415:param id: attribute ID
416
417:returns: attribute name)");
418 surface_mesh_class.def(
419 "create_attribute",
420 [](MeshType& self,
421 std::string_view name,
422 std::variant<std::monostate, AttributeElement, std::string_view> element,
423 std::variant<std::monostate, AttributeUsage, std::string_view> usage,
424 std::variant<std::monostate, GenericTensor, nb::list> initial_values,
425 std::variant<std::monostate, Tensor<Index>, GenericTensor, nb::list> initial_indices,
426 std::optional<Index> num_channels,
427 std::optional<nb::type_object> dtype) {
428 const bool with_initial_values = initial_values.index() != 0;
429 const bool with_initial_indices = initial_indices.index() != 0;
430
431 // Infer number of channels.
432 Index n = invalid<Index>();
433 if (num_channels.has_value()) {
434 n = num_channels.value();
435 } else if (with_initial_values) {
436 if (initial_values.index() == 1) {
437 const auto& values = std::get<GenericTensor>(initial_values);
439 values.ndim() == 1 || values.ndim() == 2,
440 "Only vector or matrix are accepted as initial values.");
441 n = values.ndim() == 1 ? 1 : static_cast<Index>(values.shape(1));
442 } else if (initial_values.index() == 2) {
443 n = 1;
444 }
445 } else {
446 throw nb::type_error("Either number of channels or initial values are required!");
447 }
448
449 // Infer element type.
450 AttributeElement elem_type;
451 std::visit(
452 [&](auto&& value) {
453 using T = std::decay_t<decltype(value)>;
454 if constexpr (std::is_same_v<T, AttributeElement>) {
455 elem_type = value;
456 } else if (with_initial_indices) {
457 elem_type = AttributeElement::Indexed;
458 } else if constexpr (std::is_same_v<T, std::string_view>) {
459 if (value == "Vertex") {
460 elem_type = AttributeElement::Vertex;
461 } else if (value == "Facet") {
462 elem_type = AttributeElement::Facet;
463 } else if (value == "Edge") {
464 elem_type = AttributeElement::Edge;
465 } else if (value == "Corner") {
466 elem_type = AttributeElement::Corner;
467 } else if (value == "Value") {
468 elem_type = AttributeElement::Value;
469 } else if (value == "Indexed") {
470 elem_type = AttributeElement::Indexed;
471 } else {
472 throw nb::type_error("Invalid element type!");
473 }
474 } else if (with_initial_values) {
475 // TODO guess element type based on the shape of initial values.
476 const Index num_vertices = self.get_num_vertices();
477 const Index num_facets = self.get_num_facets();
478 const Index num_corners = self.get_num_corners();
479 const Index num_edges =
480 self.has_edges() ? self.get_num_edges() : invalid<Index>();
481
482 Index num_rows = invalid<Index>();
483 if (initial_values.index() == 1) {
484 const auto& values = std::get<GenericTensor>(initial_values);
485 num_rows = static_cast<Index>(values.shape(0));
486 } else if (initial_values.index() == 2) {
487 const auto& values = std::get<nb::list>(initial_values);
488 num_rows = static_cast<Index>(nb::len(values));
489 }
490 la_debug_assert(num_rows != invalid<Index>());
491
492 if (num_rows == num_vertices) {
494 num_rows != num_facets,
495 "Cannot infer attribute element due to ambiguity: vertices vs "
496 "facets");
498 num_rows != num_edges,
499 "Cannot infer attribute element due to ambiguity: vertices vs "
500 "edges");
502 num_rows != num_corners,
503 "Cannot infer attribute element due to ambiguity: vertices vs "
504 "corners");
505 elem_type = AttributeElement::Vertex;
506 } else if (num_rows == num_facets) {
508 num_rows != num_edges,
509 "Cannot infer attribute element due to ambiguity: facets vs "
510 "edges");
512 num_rows != num_corners,
513 "Cannot infer attribute element due to ambiguity: facets vs "
514 "corners");
515 elem_type = AttributeElement::Facet;
516 } else if (num_rows == num_corners) {
518 num_rows != num_edges,
519 "Cannot infer attribute element due to ambiguity: corners vs "
520 "edges");
521 elem_type = AttributeElement::Corner;
522 } else if (num_rows == num_edges) {
523 elem_type = AttributeElement::Edge;
524 } else {
525 throw nb::type_error(
526 "Cannot infer attribute element type from initial_values!");
527 }
528 } else {
529 throw nb::type_error("Invalid element type!");
530 }
531 },
532 element);
533
534 // Infer usage.
535 AttributeUsage usage_type;
536 std::visit(
537 [&](auto&& value) {
538 using T = std::decay_t<decltype(value)>;
539 if constexpr (std::is_same_v<T, AttributeUsage>) {
540 usage_type = value;
541 } else if constexpr (std::is_same_v<T, std::string_view>) {
542 if (value == "Vector") {
543 usage_type = AttributeUsage::Vector;
544 } else if (value == "Scalar") {
545 usage_type = AttributeUsage::Scalar;
546 } else if (value == "Position") {
547 usage_type = AttributeUsage::Position;
548 } else if (value == "Normal") {
549 usage_type = AttributeUsage::Normal;
550 } else if (value == "Tangent") {
551 usage_type = AttributeUsage::Tangent;
552 } else if (value == "Bitangent") {
553 usage_type = AttributeUsage::Bitangent;
554 } else if (value == "Color") {
555 usage_type = AttributeUsage::Color;
556 } else if (value == "UV") {
557 usage_type = AttributeUsage::UV;
558 } else if (value == "VertexIndex") {
559 usage_type = AttributeUsage::VertexIndex;
560 } else if (value == "FacetIndex") {
561 usage_type = AttributeUsage::FacetIndex;
562 } else if (value == "CornerIndex") {
563 usage_type = AttributeUsage::CornerIndex;
564 } else if (value == "EdgeIndex") {
565 usage_type = AttributeUsage::EdgeIndex;
566 } else {
567 throw nb::type_error("Invalid usage type!");
568 }
569 } else {
570 if (n == 1) {
571 usage_type = AttributeUsage::Scalar;
572 } else {
573 usage_type = AttributeUsage::Vector;
574 }
575 }
576 },
577 usage);
578
579 auto create_attribute = [&](auto values) {
580 using ValueType = typename std::decay_t<decltype(values)>::element_type;
581
582 span<const ValueType> init_values;
583 span<const Index> init_indices;
584 std::vector<Index> index_storage;
585
586 // Extract initial values.
587 if (with_initial_values) {
588 init_values = values;
589 la_debug_assert(values.size() % n == 0);
590 } else {
592 num_channels.has_value(),
593 "Number of channels is required when initial values are not provided!");
595 dtype.has_value(),
596 "dtype is required when initial values are not provided!");
597 }
598
599 // Extract initial indices.
600 if (const Tensor<Index>* tensor_ptr =
601 std::get_if<Tensor<Index>>(&initial_indices)) {
602 la_debug_assert(with_initial_indices);
603 const auto& indices = *tensor_ptr;
604 auto [index_data, index_shape, index_stride] = tensor_to_span(indices);
605 la_runtime_assert(is_dense(index_shape, index_stride));
606 init_indices = index_data;
607 } else if (
608 GenericTensor* generic_tensor_ptr =
609 std::get_if<GenericTensor>(&initial_indices)) {
610 la_debug_assert(with_initial_indices);
611 auto& indices = *generic_tensor_ptr;
612 index_storage.resize(indices.size());
613
614#define LA_X_create_attribute_index(_, IndexType) \
615 if (indices.dtype() == nb::dtype<IndexType>()) { \
616 auto view = indices.template view<IndexType, nb::ndim<1>>(); \
617 std::copy(view.data(), view.data() + indices.size(), index_storage.begin()); \
618 }
619 LA_ATTRIBUTE_INDEX_X(create_attribute_index, 0)
620#undef LA_X_create_attribute_index
621 init_indices = span<Index>(index_storage.data(), index_storage.size());
622 } else if (const nb::list* list_ptr = std::get_if<nb::list>(&initial_indices)) {
623 la_debug_assert(with_initial_indices);
624 const nb::list& py_list = *list_ptr;
625 index_storage = nb::cast<std::vector<Index>>(py_list);
626 init_indices = span<Index>(index_storage.begin(), index_storage.size());
627 }
628
629 return self.template create_attribute<ValueType>(
630 name,
631 elem_type,
632 usage_type,
633 n,
634 init_values,
635 init_indices,
637 };
638
639 if (const GenericTensor* tensor_ptr = std::get_if<GenericTensor>(&initial_values)) {
640 const auto& values = *tensor_ptr;
641#define LA_X_create_attribute(_, ValueType) \
642 if (values.dtype() == nb::dtype<ValueType>()) { \
643 Tensor<ValueType> local_values(values.handle()); \
644 auto [value_data, value_shape, value_stride] = tensor_to_span(local_values); \
645 la_runtime_assert(is_dense(value_shape, value_stride)); \
646 if (num_channels.has_value()) { \
647 Index nn = value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]); \
648 la_runtime_assert(nn == n, "Number of channels does not match initial_values"); \
649 } \
650 return create_attribute(value_data); \
651 }
652 LA_ATTRIBUTE_X(create_attribute, 0)
653#undef LA_X_create_attribute
654 } else if (const nb::list* list_ptr = std::get_if<nb::list>(&initial_values)) {
655 auto values = nb::cast<std::vector<double>>(*list_ptr);
656 return create_attribute(span<double>(values.data(), values.size()));
657 } else if (dtype.has_value()) {
658 const auto& t = dtype.value();
659 auto np = nb::module_::import_("numpy");
660 if (t.is(&PyFloat_Type)) {
661 // Native python float is a C double.
662 span<double> local_values;
663 return create_attribute(local_values);
664 } else if (t.is(np.attr("float32"))) {
665 span<float> local_values;
666 return create_attribute(local_values);
667 } else if (t.is(np.attr("float64"))) {
668 span<double> local_values;
669 return create_attribute(local_values);
670 } else if (t.is(np.attr("int8"))) {
671 span<int8_t> local_values;
672 return create_attribute(local_values);
673 } else if (t.is(np.attr("int16"))) {
674 span<int16_t> local_values;
675 return create_attribute(local_values);
676 } else if (t.is(np.attr("int32"))) {
677 span<int32_t> local_values;
678 return create_attribute(local_values);
679 } else if (t.is(np.attr("int64"))) {
680 span<int64_t> local_values;
681 return create_attribute(local_values);
682 } else if (t.is(np.attr("uint8"))) {
683 span<uint8_t> local_values;
684 return create_attribute(local_values);
685 } else if (t.is(np.attr("uint16"))) {
686 span<uint16_t> local_values;
687 return create_attribute(local_values);
688 } else if (t.is(np.attr("uint32"))) {
689 span<uint32_t> local_values;
690 return create_attribute(local_values);
691 } else if (t.is(np.attr("uint64"))) {
692 span<uint64_t> local_values;
693 return create_attribute(local_values);
694 }
695 }
696 throw nb::type_error("`initial_values` and `dtype` cannot both be None!");
697 },
698 "name"_a,
699 "element"_a = nb::none(),
700 "usage"_a = nb::none(),
701 "initial_values"_a = nb::none(),
702 "initial_indices"_a = nb::none(),
703 "num_channels"_a = nb::none(),
704 "dtype"_a = nb::none(),
705 nb::sig(
706 "def create_attribute(self, "
707 "name: str, "
708 "element: typing.Union[AttributeElement, "
709 "typing.Literal["
710 "'Vertex', 'Facet', 'Edge', 'Corner', 'Value', 'Indexed'"
711 "], None] = None, "
712 "usage: typing.Union[AttributeUsage, "
713 "typing.Literal["
714 "'Vector', 'Scalar', 'Position', 'Normal', 'Tangent', 'Bitangent', 'Color', 'UV', "
715 "'VertexIndex', 'FacetIndex', 'CornerIndex', 'EdgeIndex'"
716 "], None] = None, "
717 "initial_values: typing.Union[numpy.typing.NDArray, typing.List[float], None] = None, "
718 "initial_indices: typing.Union[numpy.typing.NDArray, typing.List[int], None] = None, "
719 "num_channels: typing.Optional[int] = None, "
720 "dtype: typing.Optional[numpy.typing.DTypeLike] = None) -> int"),
721 R"(Create an attribute.
722
723:param name: Name of the attribute.
724:param element: Element type of the attribute. If None, derive from the shape of initial values.
725:param usage: Usage type of the attribute. If None, derive from the shape of initial values or the number of channels.
726:param initial_values: Initial values of the attribute.
727:param initial_indices: Initial indices of the attribute (Indexed attribute only).
728:param num_channels: Number of channels of the attribute.
729:param dtype: Data type of the attribute.
730
731:returns: The id of the created attribute.
732
733.. note::
734 If `element` is None, it will be derived based on the cardinality of the mesh elements.
735 If there is an ambiguity, an exception will be raised.
736 In addition, explicit `element` specification is required for value attributes.
737
738.. note::
739 If `usage` is None, it will be derived based on the shape of `initial_values` or `num_channels` if specified.)");
740
741 surface_mesh_class.def(
742 "create_attribute_from",
743 &MeshType::template create_attribute_from<Scalar, Index>,
744 "name"_a,
745 "source_mesh"_a,
746 "source_name"_a = "",
747 R"(Shallow copy an attribute from another mesh.
748
749:param name: Name of the attribute.
750:param source_mesh: Source mesh.
751:param source_name: Name of the attribute in the source mesh. If empty, use the same name as `name`.
752
753:returns: The id of the created attribute.)");
754
755 surface_mesh_class.def(
756 "wrap_as_attribute",
757 [](MeshType& self,
758 std::string_view name,
759 AttributeElement element,
760 AttributeUsage usage,
761 GenericTensor values) {
762 auto wrap_as_attribute = [&](auto tensor) {
763 using ValueType = typename std::decay_t<decltype(tensor)>::Scalar;
764 auto [data, shape, stride] = tensor_to_span(tensor);
765 la_runtime_assert(is_dense(shape, stride));
766 Index num_channels = shape.size() == 1 ? 1 : static_cast<Index>(shape[1]);
767 AttributeId id;
768 auto owner = std::make_shared<nb::object>(nb::find(values));
769 if constexpr (std::is_const_v<ValueType>) {
770 id = self.wrap_as_const_attribute(
771 name,
772 element,
773 usage,
774 num_channels,
775 make_shared_span(owner, data.data(), data.size()));
776 } else {
777 id = self.wrap_as_attribute(
778 name,
779 element,
780 usage,
781 num_channels,
782 make_shared_span(owner, data.data(), data.size()));
783 }
784 auto& attr = self.template ref_attribute<ValueType>(id);
785 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
786 return id;
787 };
788
789#define LA_X_wrap_as_attribute(_, ValueType) \
790 if (values.dtype() == nb::dtype<ValueType>()) { \
791 Tensor<ValueType> local_values(values.handle()); \
792 return wrap_as_attribute(local_values); \
793 }
794 LA_ATTRIBUTE_X(wrap_as_attribute, 0)
795#undef LA_X_wrap_as_attribute
796 throw nb::type_error("Unsupported value type!");
797 },
798 "name"_a,
799 "element"_a,
800 "usage"_a,
801 "values"_a,
802 R"(Wrap an existing numpy array as an attribute.
803
804:param name: Name of the attribute.
805:param element: Element type of the attribute.
806:param usage: Usage type of the attribute.
807:param values: Values of the attribute.
808
809:returns: The id of the created attribute.)");
810 surface_mesh_class.def(
811 "wrap_as_indexed_attribute",
812 [](MeshType& self,
813 std::string_view name,
814 AttributeUsage usage,
815 GenericTensor values,
816 Tensor<Index> indices) {
817 auto wrap_as_indexed_attribute = [&](auto value_tensor, auto index_tensor) {
818 using ValueType = typename std::decay_t<decltype(value_tensor)>::Scalar;
819 auto [value_data, value_shape, value_stride] = tensor_to_span(value_tensor);
820 auto [index_data, index_shape, index_stride] = tensor_to_span(index_tensor);
821 la_runtime_assert(is_dense(value_shape, value_stride));
822 la_runtime_assert(is_dense(index_shape, index_stride));
823 Index num_values = static_cast<Index>(value_shape[0]);
824 Index num_channels =
825 value_shape.size() == 1 ? 1 : static_cast<Index>(value_shape[1]);
826 AttributeId id;
827
828 auto value_owner = std::make_shared<nb::object>(nb::find(values));
829 auto index_owner = std::make_shared<nb::object>(nb::find(indices));
830
831 if constexpr (std::is_const_v<ValueType>) {
832 id = self.wrap_as_const_indexed_attribute(
833 name,
834 usage,
835 num_values,
836 num_channels,
837 make_shared_span(value_owner, value_data.data(), value_data.size()),
838 make_shared_span(index_owner, index_data.data(), index_data.size()));
839 } else {
840 id = self.wrap_as_indexed_attribute(
841 name,
842 usage,
843 num_values,
844 num_channels,
845 make_shared_span(value_owner, value_data.data(), value_data.size()),
846 make_shared_span(index_owner, index_data.data(), index_data.size()));
847 }
848 auto& attr = self.template ref_indexed_attribute<ValueType>(id);
849 attr.values().set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
850 attr.indices().set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
851 return id;
852 };
853
854#define LA_X_wrap_as_indexed_attribute(_, ValueType) \
855 if (values.dtype() == nb::dtype<ValueType>()) { \
856 Tensor<ValueType> local_values(values.handle()); \
857 return wrap_as_indexed_attribute(local_values, indices); \
858 }
859 LA_ATTRIBUTE_X(wrap_as_indexed_attribute, 0)
860#undef LA_X_wrap_as_indexed_attribute
861 throw nb::type_error("Unsupported value type!");
862 },
863 "name"_a,
864 "usage"_a,
865 "values"_a,
866 "indices"_a,
867 R"(Wrap an existing numpy array as an indexed attribute.
868
869:param name: Name of the attribute.
870:param usage: Usage type of the attribute.
871:param values: Values of the attribute.
872:param indices: Indices of the attribute.
873
874:returns: The id of the created attribute.)");
875 surface_mesh_class.def(
876 "duplicate_attribute",
877 &MeshType::duplicate_attribute,
878 "old_name"_a,
879 "new_name"_a,
880 R"(Duplicate an attribute with a new name.
881
882:param old_name: name of the attribute to duplicate
883:param new_name: name for the new attribute
884
885:returns: attribute ID of the duplicated attribute)");
886 surface_mesh_class.def(
887 "rename_attribute",
888 &MeshType::rename_attribute,
889 "old_name"_a,
890 "new_name"_a,
891 R"(Rename an attribute.
892
893:param old_name: current name of the attribute
894:param new_name: new name for the attribute)");
895
896 surface_mesh_class.def(
897 "delete_attribute",
898 [](MeshType& self, std::string_view name, AttributeDeletePolicy policy) {
899 self.delete_attribute(name, policy);
900 },
901 "name"_a,
903 R"(Delete an attribute by name.
904
905:param name: Name of the attribute.
906:param policy: Deletion policy for reserved attributes.)");
907 surface_mesh_class.def(
908 "delete_attribute",
909 [](MeshType& self, AttributeId id, AttributeDeletePolicy policy) {
910 self.delete_attribute(id, policy);
911 },
912 "id"_a,
914 R"(Delete an attribute by id.
915
916:param id: Id of the attribute.
917:param policy: Deletion policy for reserved attributes.)");
918 surface_mesh_class.def(
919 "has_attribute",
920 &MeshType::has_attribute,
921 "name"_a,
922 R"(Check if an attribute exists.
923
924:param name: attribute name
925
926:returns: True if the attribute exists, False otherwise)");
927 surface_mesh_class.def(
928 "is_attribute_indexed",
929 static_cast<bool (MeshType::*)(AttributeId) const>(&MeshType::is_attribute_indexed),
930 "id"_a,
931 R"(Check if an attribute is indexed.
932
933:param id: attribute ID
934
935:returns: True if the attribute is indexed, False otherwise)");
936 surface_mesh_class.def(
937 "is_attribute_indexed",
938 static_cast<bool (MeshType::*)(std::string_view) const>(&MeshType::is_attribute_indexed),
939 "name"_a,
940 R"(Check if an attribute is indexed.
941
942:param name: attribute name
943
944:returns: True if the attribute is indexed, False otherwise)");
945
946 // This is a helper method for trigger copy-on-write mechanism for a given attribute.
947 auto ensure_attribute_is_not_shared = [](MeshType& mesh, AttributeId id) {
948 auto& attr_base = mesh.get_attribute_base(id);
949#define LA_X_trigger_copy_on_write(_, ValueType) \
950 if (mesh.is_attribute_indexed(id)) { \
951 if (dynamic_cast<const IndexedAttribute<ValueType, Index>*>(&attr_base)) { \
952 [[maybe_unused]] auto& attr = mesh.template ref_indexed_attribute<ValueType>(id); \
953 } \
954 } else { \
955 if (dynamic_cast<const Attribute<ValueType>*>(&attr_base)) { \
956 [[maybe_unused]] auto& attr = mesh.template ref_attribute<ValueType>(id); \
957 } \
958 }
959 LA_ATTRIBUTE_X(trigger_copy_on_write, 0)
960#undef LA_X_trigger_copy_on_write
961 };
962
963 surface_mesh_class.def(
964 "attribute",
965 [&](MeshType& self, AttributeId id, bool sharing) {
967 !self.is_attribute_indexed(id),
968 fmt::format(
969 "Attribute {} is indexed! Please use `indexed_attribute` property "
970 "instead.",
971 id));
972 if (!sharing) ensure_attribute_is_not_shared(self, id);
973 return PyAttribute(self._ref_attribute_ptr(id));
974 },
975 "id"_a,
976 "sharing"_a = true,
977 R"(Get an attribute by id.
978
979:param id: Id of the attribute.
980:param sharing: Whether to allow sharing the attribute with other meshes.
981
982:returns: The attribute.)");
983 surface_mesh_class.def(
984 "attribute",
985 [&](MeshType& self, std::string_view name, bool sharing) {
987 !self.is_attribute_indexed(name),
988 fmt::format(
989 "Attribute \"{}\" is indexed! Please use `indexed_attribute` property "
990 "instead.",
991 name));
992 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
993 return PyAttribute(self._ref_attribute_ptr(name));
994 },
995 "name"_a,
996 "sharing"_a = true,
997 R"(Get an attribute by name.
998
999:param name: Name of the attribute.
1000:param sharing: Whether to allow sharing the attribute with other meshes.
1001
1002:return: The attribute.)");
1003 surface_mesh_class.def(
1004 "indexed_attribute",
1005 [&](MeshType& self, AttributeId id, bool sharing) {
1007 self.is_attribute_indexed(id),
1008 fmt::format(
1009 "Attribute {} is not indexed! Please use `attribute` property instead.",
1010 id));
1011 if (!sharing) ensure_attribute_is_not_shared(self, id);
1012 return PyIndexedAttribute(self._ref_attribute_ptr(id));
1013 },
1014 "id"_a,
1015 "sharing"_a = true,
1016 R"(Get an indexed attribute by id.
1017
1018:param id: Id of the attribute.
1019:param sharing: Whether to allow sharing the attribute with other meshes.
1020
1021:returns: The indexed attribute.)");
1022 surface_mesh_class.def(
1023 "indexed_attribute",
1024 [&](MeshType& self, std::string_view name, bool sharing) {
1026 self.is_attribute_indexed(name),
1027 fmt::format(
1028 "Attribute \"{}\" is not indexed! Please use `attribute` property instead.",
1029 name));
1030 if (!sharing) ensure_attribute_is_not_shared(self, self.get_attribute_id(name));
1031 return PyIndexedAttribute(self._ref_attribute_ptr(name));
1032 },
1033 "name"_a,
1034 "sharing"_a = true,
1035 R"(Get an indexed attribute by name.
1036
1037:param name: Name of the attribute.
1038:param sharing: Whether to allow sharing the attribute with other meshes.
1039
1040:returns: The indexed attribute.)");
1041 surface_mesh_class.def(
1042 "__attribute_ref_count__",
1043 [](MeshType& self, AttributeId id) {
1044 auto ptr = self._get_attribute_ptr(id);
1045 return ptr.use_count();
1046 },
1047 "id"_a,
1048 R"(Get the reference count of an attribute (for debugging purposes).
1049
1050:param id: attribute ID
1051
1052:returns: reference count of the attribute)");
1053 surface_mesh_class.def_prop_rw(
1054 "vertices",
1055 [](const MeshType& self) {
1056 const auto& attr = self.get_vertex_to_position();
1057 return attribute_to_tensor(attr, nb::find(&self));
1058 },
1059 [](MeshType& self, Tensor<Scalar> tensor) {
1060 auto [values, shape, stride] = tensor_to_span(tensor);
1061 la_runtime_assert(is_dense(shape, stride));
1062 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
1063
1064 size_t num_vertices = shape.size() == 1 ? 1 : shape[0];
1065 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1066 auto id = self.wrap_as_vertices(
1067 make_shared_span(owner, values.data(), values.size()),
1068 static_cast<Index>(num_vertices));
1069 auto& attr = self.template ref_attribute<Scalar>(id);
1070 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1071 },
1072 "Vertices of the mesh.");
1073 surface_mesh_class.def_prop_rw(
1074 "facets",
1075 [](const MeshType& self) {
1076 if (self.is_regular()) {
1077 const auto& attr = self.get_corner_to_vertex();
1078 const size_t shape[2] = {
1079 static_cast<size_t>(self.get_num_facets()),
1080 static_cast<size_t>(self.get_vertex_per_facet())};
1081 return attribute_to_tensor(attr, shape, nb::find(&self));
1082 } else {
1083 logger().warn("Mesh is not regular, returning the flattened facets.");
1084 const auto& attr = self.get_corner_to_vertex();
1085 return attribute_to_tensor(attr, nb::find(&self));
1086 }
1087 },
1088 [](MeshType& self, Tensor<Index> tensor) {
1089 auto [values, shape, stride] = tensor_to_span(tensor);
1090 la_runtime_assert(is_dense(shape, stride));
1091
1092 const size_t num_facets = shape.size() == 1 ? 1 : shape[0];
1093 const size_t vertex_per_facet = shape.size() == 1 ? shape[0] : shape[1];
1094 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1095 auto id = self.wrap_as_facets(
1096 make_shared_span(owner, values.data(), values.size()),
1097 static_cast<Index>(num_facets),
1098 static_cast<Index>(vertex_per_facet));
1099 auto& attr = self.template ref_attribute<Index>(id);
1100 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1101 },
1102 "Facets of the mesh.");
1103 surface_mesh_class.def_prop_ro(
1104 "edges",
1105 [](MeshType& self) {
1106 self.initialize_edges();
1107 auto num_edges = self.get_num_edges();
1108 std::vector<Index> data(num_edges * 2);
1109 tbb::parallel_for(Index{0}, num_edges, [&](Index i) {
1110 auto [v0, v1] = self.get_edge_vertices(i);
1111 data[i * 2] = v0;
1112 data[i * 2 + 1] = v1;
1113 });
1114 nb::ndarray<Index, nb::shape<-1, 2>, nb::numpy, nb::c_contig, nb::device::cpu> edges(
1115 data.data(),
1116 {static_cast<size_t>(num_edges), 2});
1117 return edges.cast();
1118 },
1119 "Edges of the mesh.");
1120 surface_mesh_class.def(
1121 "wrap_as_vertices",
1122 [](MeshType& self, Tensor<Scalar> tensor, Index num_vertices) {
1123 auto [values, shape, stride] = tensor_to_span(tensor);
1124 la_runtime_assert(is_dense(shape, stride));
1125 la_runtime_assert(check_shape(shape, invalid<size_t>(), self.get_dimension()));
1126
1127 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1128 auto id = self.wrap_as_vertices(
1129 make_shared_span(owner, values.data(), values.size()),
1130 num_vertices);
1131 auto& attr = self.template ref_attribute<Scalar>(id);
1132 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1133 return id;
1134 },
1135 "tensor"_a,
1136 "num_vertices"_a,
1137 R"(Wrap a tensor as vertices.
1138
1139:param tensor: The tensor to wrap.
1140:param num_vertices: Number of vertices.
1141
1142:return: The id of the wrapped vertices attribute.)");
1143 surface_mesh_class.def(
1144 "wrap_as_facets",
1145 [](MeshType& self, Tensor<Index> tensor, Index num_facets, Index vertex_per_facet) {
1146 auto [values, shape, stride] = tensor_to_span(tensor);
1147 la_runtime_assert(is_dense(shape, stride));
1148
1149 auto owner = std::make_shared<nb::object>(nb::find(tensor));
1150 auto id = self.wrap_as_facets(
1151 make_shared_span(owner, values.data(), values.size()),
1152 num_facets,
1153 vertex_per_facet);
1154 auto& attr = self.template ref_attribute<Index>(id);
1155 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1156 return id;
1157 },
1158 "tensor"_a,
1159 "num_facets"_a,
1160 "vertex_per_facet"_a,
1161 R"(Wrap a tensor as a list of regular facets.
1162
1163:param tensor: The tensor to wrap.
1164:param num_facets: Number of facets.
1165:param vertex_per_facet: Number of vertices per facet.
1166
1167:return: The id of the wrapped facet attribute.)");
1168 surface_mesh_class.def(
1169 "wrap_as_facets",
1170 [](MeshType& self,
1171 Tensor<Index> offsets,
1172 Index num_facets,
1173 Tensor<Index> facets,
1174 Index num_corners) {
1175 auto [offsets_data, offsets_shape, offsets_stride] = tensor_to_span(offsets);
1176 auto [facets_data, facets_shape, facets_stride] = tensor_to_span(facets);
1177 la_runtime_assert(is_dense(offsets_shape, offsets_stride));
1178 la_runtime_assert(is_dense(facets_shape, facets_stride));
1179
1180 auto offsets_owner = std::make_shared<nb::object>(nb::find(offsets));
1181 auto facets_owner = std::make_shared<nb::object>(nb::find(facets));
1182
1183 auto id = self.wrap_as_facets(
1184 make_shared_span(offsets_owner, offsets_data.data(), offsets_data.size()),
1185 num_facets,
1186 make_shared_span(facets_owner, facets_data.data(), facets_data.size()),
1187 num_corners);
1188 auto& attr = self.template ref_attribute<Index>(id);
1189 attr.set_growth_policy(AttributeGrowthPolicy::WarnAndCopy);
1190 return id;
1191 },
1192 "offsets"_a,
1193 "num_facets"_a,
1194 "facets"_a,
1195 "num_corners"_a,
1196 R"(Wrap a tensor as a list of hybrid facets.
1197
1198:param offsets: The offset indices into the facets array.
1199:param num_facets: Number of facets.
1200:param facets: The indices of the vertices of the facets.
1201:param num_corners: Number of corners.
1202
1203:return: The id of the wrapped facet attribute.)");
1204 surface_mesh_class.def_static(
1205 "attr_name_is_reserved",
1206 &MeshType::attr_name_is_reserved,
1207 "name"_a,
1208 R"(Check if an attribute name is reserved.
1209
1210:param name: attribute name to check
1211
1212:returns: True if the name is reserved, False otherwise)");
1213 surface_mesh_class.def_prop_ro_static(
1214 "attr_name_vertex_to_position",
1215 [](nb::handle) { return MeshType::attr_name_vertex_to_position(); },
1216 "The name of the attribute that stores the vertex positions.");
1217 surface_mesh_class.def_prop_ro_static(
1218 "attr_name_corner_to_vertex",
1219 [](nb::handle) { return MeshType::attr_name_corner_to_vertex(); },
1220 "The name of the attribute that stores the corner to vertex mapping.");
1221 surface_mesh_class.def_prop_ro_static(
1222 "attr_name_facet_to_first_corner",
1223 [](nb::handle) { return MeshType::attr_name_facet_to_first_corner(); },
1224 "The name of the attribute that stores the facet to first corner mapping.");
1225 surface_mesh_class.def_prop_ro_static(
1226 "attr_name_corner_to_facet",
1227 [](nb::handle) { return MeshType::attr_name_corner_to_facet(); },
1228 "The name of the attribute that stores the corner to facet mapping.");
1229 surface_mesh_class.def_prop_ro_static(
1230 "attr_name_corner_to_edge",
1231 [](nb::handle) { return MeshType::attr_name_corner_to_edge(); },
1232 "The name of the attribute that stores the corner to edge mapping.");
1233 surface_mesh_class.def_prop_ro_static(
1234 "attr_name_edge_to_first_corner",
1235 [](nb::handle) { return MeshType::attr_name_edge_to_first_corner(); },
1236 "The name of the attribute that stores the edge to first corner mapping.");
1237 surface_mesh_class.def_prop_ro_static(
1238 "attr_name_next_corner_around_edge",
1239 [](nb::handle) { return MeshType::attr_name_next_corner_around_edge(); },
1240 "The name of the attribute that stores the next corner around edge mapping.");
1241 surface_mesh_class.def_prop_ro_static(
1242 "attr_name_vertex_to_first_corner",
1243 [](nb::handle) { return MeshType::attr_name_vertex_to_first_corner(); },
1244 "The name of the attribute that stores the vertex to first corner mapping.");
1245 surface_mesh_class.def_prop_ro_static(
1246 "attr_name_next_corner_around_vertex",
1247 [](nb::handle) { return MeshType::attr_name_next_corner_around_vertex(); },
1248 "The name of the attribute that stores the next corner around vertex mapping.");
1249 surface_mesh_class.def_prop_ro(
1250 "attr_id_vertex_to_position",
1251 &MeshType::attr_id_vertex_to_position);
1252 surface_mesh_class.def_prop_ro("attr_id_corner_to_vertex", &MeshType::attr_id_corner_to_vertex);
1253 surface_mesh_class.def_prop_ro(
1254 "attr_id_facet_to_first_corner",
1255 &MeshType::attr_id_facet_to_first_corner);
1256 surface_mesh_class.def_prop_ro("attr_id_corner_to_facet", &MeshType::attr_id_corner_to_facet);
1257 surface_mesh_class.def_prop_ro("attr_id_corner_to_edge", &MeshType::attr_id_corner_to_edge);
1258 surface_mesh_class.def_prop_ro(
1259 "attr_id_edge_to_first_corner",
1260 &MeshType::attr_id_edge_to_first_corner);
1261 surface_mesh_class.def_prop_ro(
1262 "attr_id_next_corner_around_edge",
1263 &MeshType::attr_id_next_corner_around_edge);
1264 surface_mesh_class.def_prop_ro(
1265 "attr_id_vertex_to_first_corner",
1266 &MeshType::attr_id_vertex_to_first_corner);
1267 surface_mesh_class.def_prop_ro(
1268 "attr_id_next_corner_around_vertex",
1269 &MeshType::attr_id_next_corner_around_vertex);
1270 surface_mesh_class.def(
1271 "initialize_edges",
1272 [](MeshType& self, std::optional<Tensor<Index>> tensor) {
1273 if (tensor.has_value()) {
1274 auto [edge_data, edge_shape, edge_stride] = tensor_to_span(tensor.value());
1275 la_runtime_assert(is_dense(edge_shape, edge_stride));
1277 edge_data.empty() || check_shape(edge_shape, invalid<size_t>(), 2),
1278 "Edge tensor must be of the shape num_edges x 2");
1279 self.initialize_edges(edge_data);
1280 } else {
1281 self.initialize_edges();
1282 }
1283 },
1284 "edges"_a = nb::none(),
1285 R"(Initialize the edges.
1286
1287The `edges` tensor provides a predefined ordering of the edges.
1288If not provided, the edges are initialized in an arbitrary order.
1289
1290:param edges: M x 2 tensor of predefined edge vertex indices, where M is the number of edges.)");
1291 surface_mesh_class.def(
1292 "clear_edges",
1293 &MeshType::clear_edges,
1294 R"(Clear all edge connectivity information.)");
1295 surface_mesh_class.def_prop_ro("has_edges", &MeshType::has_edges);
1296 surface_mesh_class.def(
1297 "get_edge",
1299 "facet_id"_a,
1300 "lv"_a,
1301 R"(Get the edge index associated with a local vertex of a facet.
1302
1303:param facet_id: facet index
1304:param lv: local vertex index of the facet
1305
1306:returns: edge index)");
1307 surface_mesh_class.def(
1308 "get_corner_edge",
1309 &MeshType::get_corner_edge,
1310 "corner_id"_a,
1311 R"(Get the edge index associated with a corner.
1312
1313:param corner_id: corner index
1314
1315:returns: edge index)");
1316 surface_mesh_class.def(
1317 "get_edge_vertices",
1319 "edge_id"_a,
1320 R"(Get the two vertex indices of an edge.
1321
1322:param edge_id: edge index
1323
1324:returns: tuple of (vertex1_id, vertex2_id))");
1325 surface_mesh_class.def(
1326 "find_edge_from_vertices",
1328 "vertex1_id"_a,
1329 "vertex2_id"_a,
1330 R"(Find the edge connecting two vertices.
1331
1332:param vertex1_id: first vertex index
1333:param vertex2_id: second vertex index
1334
1335:returns: edge index, or invalid_index if no such edge exists)");
1336 surface_mesh_class.def(
1337 "get_first_corner_around_edge",
1338 &MeshType::get_first_corner_around_edge,
1339 "edge_id"_a,
1340 R"(Get the first corner around an edge.
1341
1342:param edge_id: edge index
1343
1344:returns: first corner index around the edge)");
1345 surface_mesh_class.def(
1346 "get_next_corner_around_edge",
1347 &MeshType::get_next_corner_around_edge,
1348 "corner_id"_a,
1349 R"(Get the next corner around the same edge.
1350
1351:param corner_id: current corner index
1352
1353:returns: next corner index around the same edge)");
1354 surface_mesh_class.def(
1355 "get_first_corner_around_vertex",
1356 &MeshType::get_first_corner_around_vertex,
1357 "vertex_id"_a,
1358 R"(Get the first corner around a vertex.
1359
1360:param vertex_id: vertex index
1361
1362:returns: first corner index around the vertex)");
1363 surface_mesh_class.def(
1364 "get_next_corner_around_vertex",
1365 &MeshType::get_next_corner_around_vertex,
1366 "corner_id"_a,
1367 R"(Get the next corner around the same vertex.
1368
1369:param corner_id: current corner index
1370
1371:returns: next corner index around the same vertex)");
1372 surface_mesh_class.def(
1373 "count_num_corners_around_edge",
1374 &MeshType::count_num_corners_around_edge,
1375 "edge_id"_a,
1376 R"(Count the number of corners around an edge.
1377
1378:param edge_id: edge index
1379
1380:returns: number of corners around the edge)");
1381 surface_mesh_class.def(
1382 "count_num_corners_around_vertex",
1383 &MeshType::count_num_corners_around_vertex,
1384 "vertex_id"_a,
1385 R"(Count the number of corners around a vertex.
1386
1387:param vertex_id: vertex index
1388
1389:returns: number of corners around the vertex)");
1390 surface_mesh_class.def(
1391 "get_counterclockwise_corner_around_vertex",
1392 &MeshType::get_counterclockwise_corner_around_vertex,
1393 "corner"_a,
1394 R"(Get the counterclockwise corner around the vertex associated with the input corner.
1395
1396.. note::
1397 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1398 corners based on edge-connectivity) will be visited.
1399
1400 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1401 is not well defined. It will return `invalid_index` in this case.
1402
1403:param corner: The input corner index.
1404
1405:returns: The counterclockwise corner index or `invalid_index` if none exists.)");
1406 surface_mesh_class.def(
1407 "get_clockwise_corner_around_vertex",
1408 &MeshType::get_clockwise_corner_around_vertex,
1409 "corner"_a,
1410 R"(Get the clockwise corner around the vertex associated with the input corner.
1411
1412.. note::
1413 If the vertex is a non-manifold vertex, only one "umbrella" (a set of connected
1414 corners based on edge-connectivity) will be visited.
1415
1416 If the traversal reaches a boundary or a non-manifold edge, the next adjacent corner
1417 is not well defined. It will return `invalid_index` in this case.
1418
1419:param corner: The input corner index.
1420
1421:returns: The clockwise corner index or `invalid_index` if none exists.)");
1422 surface_mesh_class.def(
1423 "get_one_facet_around_edge",
1425 "edge_id"_a,
1426 R"(Get one facet adjacent to an edge.
1427
1428:param edge_id: edge index
1429
1430:returns: facet index adjacent to the edge)");
1431 surface_mesh_class.def(
1432 "get_one_corner_around_edge",
1434 "edge_id"_a,
1435 R"(Get one corner around an edge.
1436
1437:param edge_id: edge index
1438
1439:returns: corner index around the edge)");
1440 surface_mesh_class.def(
1441 "get_one_corner_around_vertex",
1443 "vertex_id"_a,
1444 R"(Get one corner around a vertex.
1445
1446:param vertex_id: vertex index
1447
1448:returns: corner index around the vertex)");
1449 surface_mesh_class.def(
1450 "is_boundary_edge",
1452 "edge_id"_a,
1453 R"(Check if an edge is on the boundary.
1454
1455:param edge_id: edge index
1456
1457:returns: True if the edge is on the boundary, False otherwise)");
1458
1459 surface_mesh_class.def(
1460 "foreach_facet_around_edge",
1461 [](MeshType& self, Index edge_id, std::function<void(Index)>& func) {
1462 self.foreach_facet_around_edge(edge_id, func);
1463 },
1464 "edge_id"_a,
1465 "func"_a,
1466 R"(Iterate over all facets around an edge.
1467
1468:param edge_id: edge index
1469:param func: function to call for each facet index
1470
1471.. code-block:: python
1472
1473 mesh.foreach_facet_around_edge(eid, lambda fid: print(fid))
1474)");
1475
1476 surface_mesh_class.def(
1477 "foreach_facet_around_vertex",
1478 [](MeshType& self, Index vertex_id, std::function<void(Index)>& func) {
1479 self.foreach_facet_around_vertex(vertex_id, func);
1480 },
1481 "vertex_id"_a,
1482 "func"_a,
1483 R"(Iterate over all facets around a vertex.
1484
1485:param vertex_id: vertex index
1486:param func: function to call for each facet index
1487
1488.. code-block:: python
1489
1490 mesh.foreach_facet_around_vertex(vid, lambda fid: print(fid))
1491)");
1492
1493 surface_mesh_class.def(
1494 "foreach_facet_around_facet",
1495 [](MeshType& self, Index facet_id, std::function<void(Index)>& func) {
1496 self.foreach_facet_around_facet(facet_id, func);
1497 },
1498 "facet_id"_a,
1499 "func"_a,
1500 R"(Iterate over all adjacent facets around a facet.
1501
1502:param facet_id: facet index
1503:param func: function to call for each adjacent facet index
1504
1505.. code-block:: python
1506
1507 mesh.foreach_facet_around_facet(fid, lambda afid: print(afid))
1508)");
1509
1510 surface_mesh_class.def(
1511 "foreach_corner_around_edge",
1512 [](MeshType& self, Index edge_id, std::function<void(Index)>& func) {
1513 self.foreach_corner_around_edge(edge_id, func);
1514 },
1515 "edge_id"_a,
1516 "func"_a,
1517 R"(Iterate over all corners around an edge.
1518
1519:param edge_id: edge index
1520:param func: function to call for each corner index
1521
1522.. code-block:: python
1523
1524 mesh.foreach_corner_around_edge(eid, lambda cid: print(cid))
1525)");
1526
1527 surface_mesh_class.def(
1528 "foreach_corner_around_vertex",
1529 [](MeshType& self, Index vertex_id, std::function<void(Index)>& func) {
1530 self.foreach_corner_around_vertex(vertex_id, func);
1531 },
1532 "vertex_id"_a,
1533 "func"_a,
1534 R"(Iterate over all corners around a vertex.
1535
1536:param vertex_id: vertex index
1537:param func: function to call for each corner index
1538
1539.. code-block:: python
1540
1541 mesh.foreach_corner_around_vertex(vid, lambda cid: print(cid))
1542)");
1543
1544 surface_mesh_class.def(
1545 "foreach_edge_around_vertex",
1546 [](MeshType& self, Index vertex_id, std::function<void(Index)>& func) {
1547 self.foreach_edge_around_vertex_with_duplicates(vertex_id, func);
1548 },
1549 "vertex_id"_a,
1550 "func"_a,
1551 R"(Iterate over all edges around a vertex.
1552
1553.. note::
1554 Each incident edge will be visited once for each incident facet.
1555 Thus, manifold edge will be visited exactly twice,
1556 boundary edge will be visited exactly once,
1557 non-manifold edges will be visited more than twice.
1558
1559:param vertex_id: vertex index
1560:param func: function to call for each edge index
1561
1562.. code-block:: python
1563
1564 mesh.foreach_edge_around_vertex(vid, lambda eid: print(eid))
1565)");
1566
1567 // Pythonic versions that return lists/generators
1568 surface_mesh_class.def(
1569 "facets_around_facet",
1570 [](MeshType& self, Index facet_id) {
1571 std::vector<Index> result;
1572 self.foreach_facet_around_facet(facet_id, [&](Index fid) { result.push_back(fid); });
1573 return result;
1574 },
1575 "facet_id"_a,
1576 R"(Get all adjacent facets around a facet.
1577
1578:param facet_id: facet index
1579
1580:returns: list of adjacent facet indices
1581
1582.. code-block:: python
1583
1584 facets = mesh.facets_around_facet(fid)
1585 for afid in facets:
1586 print(afid)
1587)");
1588
1589 surface_mesh_class.def(
1590 "facets_around_vertex",
1591 [](MeshType& self, Index vertex_id) {
1592 std::vector<Index> result;
1593 self.foreach_facet_around_vertex(vertex_id, [&](Index fid) { result.push_back(fid); });
1594 return result;
1595 },
1596 "vertex_id"_a,
1597 R"(Get all facets around a vertex.
1598
1599:param vertex_id: vertex index
1600
1601:returns: list of facet indices
1602
1603.. code-block:: python
1604
1605 facets = mesh.facets_around_vertex(vid)
1606 for fid in facets:
1607 print(fid)
1608)");
1609
1610 surface_mesh_class.def(
1611 "facets_around_edge",
1612 [](MeshType& self, Index edge_id) {
1613 std::vector<Index> result;
1614 self.foreach_facet_around_edge(edge_id, [&](Index fid) { result.push_back(fid); });
1615 return result;
1616 },
1617 "edge_id"_a,
1618 R"(Get all facets around an edge.
1619
1620:param edge_id: edge index
1621
1622:returns: list of facet indices
1623
1624.. code-block:: python
1625
1626 facets = mesh.facets_around_edge(eid)
1627 for fid in facets:
1628 print(fid)
1629)");
1630
1631 surface_mesh_class.def(
1632 "corners_around_edge",
1633 [](MeshType& self, Index edge_id) {
1634 std::vector<Index> result;
1635 self.foreach_corner_around_edge(edge_id, [&](Index cid) { result.push_back(cid); });
1636 return result;
1637 },
1638 "edge_id"_a,
1639 R"(Get all corners around an edge.
1640
1641:param edge_id: edge index
1642
1643:returns: list of corner indices
1644
1645.. code-block:: python
1646
1647 corners = mesh.corners_around_edge(eid)
1648 for cid in corners:
1649 print(cid)
1650)");
1651
1652 surface_mesh_class.def(
1653 "corners_around_vertex",
1654 [](MeshType& self, Index vertex_id) {
1655 std::vector<Index> result;
1656 self.foreach_corner_around_vertex(vertex_id, [&](Index cid) { result.push_back(cid); });
1657 return result;
1658 },
1659 "vertex_id"_a,
1660 R"(Get all corners around a vertex.
1661
1662:param vertex_id: vertex index
1663
1664:returns: list of corner indices
1665
1666.. code-block:: python
1667
1668 corners = mesh.corners_around_vertex(vid)
1669 for cid in corners:
1670 print(cid)
1671)");
1672
1673 surface_mesh_class.def(
1674 "edges_around_vertex",
1675 [](MeshType& self, Index vertex_id) {
1676 std::vector<Index> result;
1677 self.foreach_edge_around_vertex_with_duplicates(vertex_id, [&](Index eid) {
1678 result.push_back(eid);
1679 });
1680 return result;
1681 },
1682 "vertex_id"_a,
1683 R"(Get all edges around a vertex.
1684
1685.. note::
1686 Each incident edge will be visited once for each incident facet.
1687 Thus, manifold edge will be visited exactly twice,
1688 boundary edge will be visited exactly once,
1689 non-manifold edges will be visited more than twice.
1690
1691:param vertex_id: vertex index
1692
1693:returns: list of edge indices (with duplicates)
1694
1695.. code-block:: python
1696
1697 edges = mesh.edges_around_vertex(vid)
1698 for eid in edges:
1699 print(eid)
1700)");
1701
1702 struct MetaData
1703 {
1704 MeshType* mesh = nullptr;
1705
1706 std::vector<AttributeId> get_metadata() const
1707 {
1708 la_runtime_assert(mesh != nullptr, "MetaData is not initialized.");
1709 lagrange::AttributeMatcher opts;
1710 opts.usages = AttributeUsage::String;
1711 opts.element_types = AttributeElement::Value;
1712 opts.num_channels = 1;
1713 return lagrange::find_matching_attributes(*mesh, opts);
1714 }
1715 };
1716 auto meta_data_class = nb::class_<MetaData>(m, "MetaData", "Metadata `dict` of the mesh");
1717 meta_data_class.def("__len__", [](const MetaData& self) {
1718 auto data = self.get_metadata();
1719 return data.size();
1720 });
1721 meta_data_class.def("__getitem__", [](const MetaData& self, std::string_view key) {
1722 return self.mesh->get_metadata(key);
1723 });
1724 meta_data_class.def(
1725 "__setitem__",
1726 [](MetaData& self, std::string_view key, std::string_view value) {
1727 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1728 if (self.mesh->has_attribute(key)) {
1729 self.mesh->set_metadata(key, value);
1730 } else {
1731 self.mesh->create_metadata(key, value);
1732 }
1733 });
1734 meta_data_class.def("__delitem__", [](MetaData& self, std::string_view key) {
1735 la_runtime_assert(self.mesh != nullptr, "MetaData is not initialized.");
1736 self.mesh->delete_attribute(key);
1737 });
1738 meta_data_class.def("__repr__", [](const MetaData& self) -> std::string {
1739 auto data = self.get_metadata();
1740 if (data.empty()) return "MetaData({})";
1741
1742 std::string r;
1743 for (auto id : data) {
1744 auto name = self.mesh->get_attribute_name(id);
1745 auto value = self.mesh->get_metadata(id);
1746 fmt::format_to(std::back_inserter(r), " {}: {},\n", name, value);
1747 }
1748 return fmt::format("MetaData(\n{})", r);
1749 });
1750
1751 surface_mesh_class.def_prop_ro(
1752 "metadata",
1753 [](MeshType& self) {
1754 MetaData meta_data;
1755 meta_data.mesh = &self;
1756 return meta_data;
1757 },
1758 "Metadata of the mesh.");
1759
1760 surface_mesh_class.def(
1761 "get_matching_attribute_ids",
1762 [](MeshType& self,
1763 std::optional<AttributeElement> element,
1764 std::optional<AttributeUsage> usage,
1765 Index num_channels) {
1766 AttributeMatcher opts;
1767 if (usage.has_value()) {
1768 opts.usages = usage.value();
1769 }
1770 if (element.has_value()) {
1771 opts.element_types = element.value();
1772 }
1773 opts.num_channels = num_channels;
1774 return find_matching_attributes(self, opts);
1775 },
1776 "element"_a = nb::none(),
1777 "usage"_a = nb::none(),
1778 "num_channels"_a = 0,
1779 R"(Get all matching attribute ids with the desired element type, usage and number of channels.
1780
1781:param element: The target element type. None matches all element types.
1782:param usage: The target usage type. None matches all usage types.
1783:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1784
1785:returns: A list of attribute ids matching the target element, usage and number of channels.
1786)");
1787
1788 surface_mesh_class.def(
1789 "get_matching_attribute_id",
1790 [](MeshType& self,
1791 std::optional<AttributeElement> element,
1792 std::optional<AttributeUsage> usage,
1793 Index num_channels) {
1794 std::optional<AttributeId> result;
1795 self.seq_foreach_attribute_id([&](AttributeId attr_id) {
1796 if (result.has_value()) {
1797 return;
1798 }
1799 const auto name = self.get_attribute_name(attr_id);
1800 if (self.attr_name_is_reserved(name)) return;
1801 const auto& attr = self.get_attribute_base(attr_id);
1802 if (element && attr.get_element_type() != *element) return;
1803 if (usage && attr.get_usage() != *usage) return;
1804 if (num_channels != 0 && attr.get_num_channels() != num_channels) return;
1805 result = attr_id;
1806 });
1807 return result;
1808 },
1809 "element"_a = nb::none(),
1810 "usage"_a = nb::none(),
1811 "num_channels"_a = 0,
1812 R"(Get one matching attribute id with the desired element type, usage and number of channels.
1813
1814:param element: The target element type. None matches all element types.
1815:param usage: The target usage type. None matches all usage types.
1816:param num_channels: The target number of channels. 0 matches arbitrary number of channels.
1817
1818:returns: An attribute id matching the target element, usage and number of channels, if found. None otherwise.
1819)");
1820
1821 surface_mesh_class.def(
1822 "__copy__",
1823 [](MeshType& self) -> MeshType {
1824 MeshType mesh = self;
1825 return mesh;
1826 },
1827 R"(Create a shallow copy of this mesh.)");
1828
1829 surface_mesh_class.def(
1830 "__deepcopy__",
1831 [](MeshType& self, [[maybe_unused]] std::optional<nb::dict> memo) -> MeshType {
1832 MeshType mesh = self;
1833 par_foreach_attribute_write(mesh, [](auto&& attr) {
1834 // For most of the attributes, just getting a writable reference will trigger a
1835 // copy of the buffer thanks to the copy-on-write mechanism and the default
1836 // CopyIfExternal copy policy.
1837
1838 using AttributeType = std::decay_t<decltype(attr)>;
1839 if constexpr (AttributeType::IsIndexed) {
1840 auto& value_attr = attr.values();
1841 if (value_attr.is_external()) {
1842 value_attr.create_internal_copy();
1843 }
1844 auto& index_attr = attr.indices();
1845 if (index_attr.is_external()) {
1846 index_attr.create_internal_copy();
1847 }
1848 } else {
1849 if (attr.is_external()) {
1850 attr.create_internal_copy();
1851 }
1852 }
1853 });
1854 return mesh;
1855 },
1856 "memo"_a = nb::none(),
1857 R"(Create a deep copy of this mesh.)");
1858
1859 surface_mesh_class.def(
1860 "clone",
1861 [](MeshType& self, bool strip) -> MeshType {
1862 if (strip) {
1863 return MeshType::stripped_copy(self);
1864 } else {
1865 auto py_self = nb::find(self);
1866 return nb::cast<MeshType>(py_self.attr("__deepcopy__")());
1867 }
1868 },
1869 "strip"_a = false,
1870 R"(Create a deep copy of this mesh.
1871
1872:param strip: If True, strip the mesh of all attributes except for the reserved attributes.)");
1873}
1874
1875} // 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:1277
Definition PyAttribute.h:24
Definition PyIndexedAttribute.h:27
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
AttributeReorientPolicy
Policy for updating attribute values when reorienting mesh facets.
Definition AttributeFwd.h:192
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
@ None
Do not reorient attribute values when flipping mesh facets.
Definition AttributeFwd.h:193
@ 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