Lagrange
Loading...
Searching...
No Matches
bind_scene.h
1/*
2 * Copyright 2023 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 <lagrange/AttributeValueType.h>
15#include <lagrange/CameraTransforms.h>
16#include <lagrange/Logger.h>
17#include <lagrange/python/binding.h>
18#include <lagrange/python/tensor_utils.h>
19#include <lagrange/scene/Scene.h>
20#include <lagrange/scene/SimpleScene.h>
21#include <lagrange/scene/internal/scene_string_utils.h>
22#include <lagrange/scene/internal/shared_utils.h>
23#include <lagrange/scene/scene_convert.h>
24#include <lagrange/scene/scene_utils.h>
25#include <lagrange/utils/assert.h>
26
27#include "bind_value.h"
28
29namespace lagrange::python {
30
31namespace nb = nanobind;
32
33void bind_scene(nb::module_& m)
34{
35 using namespace lagrange::scene;
36 using Scalar = double;
37 using Index = uint32_t;
38 using SceneType = Scene<Scalar, Index>;
39
40 nb::bind_vector<SafeVector<ElementId>>(m, "ElementIdList");
41 nb::bind_safe_vector<SafeVector<Node>>(m, "NodeList");
42 nb::bind_safe_vector<SafeVector<SceneMeshInstance>>(m, "SceneMeshInstanceList");
43 nb::bind_safe_vector<SafeVector<SurfaceMesh<Scalar, Index>>>(m, "SurfaceMeshList");
44 nb::bind_safe_vector<SafeVector<ImageExperimental>>(m, "ImageList");
45 nb::bind_safe_vector<SafeVector<Texture>>(m, "TextureList");
46 nb::bind_safe_vector<SafeVector<MaterialExperimental>>(m, "MaterialList");
47 nb::bind_safe_vector<SafeVector<Light>>(m, "LightList");
48 nb::bind_safe_vector<SafeVector<Camera>>(m, "CameraList");
49 nb::bind_safe_vector<SafeVector<Skeleton>>(m, "SkeletonList");
50 nb::bind_safe_vector<SafeVector<Animation>>(m, "AnimationList");
51
52 nb::class_<lagrange::scene::Extensions>(m, "Extensions")
53 .def(
54 "__repr__",
55 [](const lagrange::scene::Extensions& self) {
56 return scene::internal::to_string(self);
57 })
58 .def_prop_ro("size", &Extensions::size)
59 .def_prop_ro("empty", &Extensions::empty)
60 .def_rw(
61 "data",
62 &Extensions::data,
63 nb::rv_policy::reference_internal,
64 "Raw data stored in this extension as a dict");
65
66 nb::class_<SceneMeshInstance>(
67 m,
68 "SceneMeshInstance",
69 "Pairs a mesh with its materials (zero, one, or more)")
70 .def(nb::init<>())
71 .def(
72 "__repr__",
73 [](const SceneMeshInstance& self) { return scene::internal::to_string(self); })
74 .def_prop_rw(
75 "mesh",
76 [](SceneMeshInstance& self) -> std::optional<ElementId> {
77 if (self.mesh != invalid_element)
78 return self.mesh;
79 else
80 return {};
81 },
82 [](SceneMeshInstance& self, ElementId mesh) { self.mesh = mesh; },
83 "Mesh index. Has to be a valid index in the scene.meshes vector (None if invalid)")
84 .def_rw(
85 "materials",
86 &SceneMeshInstance::materials,
87 "Material indices in the scene.materials vector. This is typically a single material "
88 "index. When a single mesh uses multiple materials, the AttributeName::material_id "
89 "facet attribute should be defined.",
90 LA_SAFE_VECTOR_SETTER("materials", "ElementIdList | collections.abc.Sequence[int]"));
91
92 nb::class_<Node>(m, "Node", "Represents a node in the scene hierarchy")
93 .def(nb::init<>())
94 .def("__repr__", [](const Node& self) { return scene::internal::to_string(self); })
95 .def_rw("name", &Node::name, "Node name. May not be unique and can be empty")
96 .def_prop_rw(
97 "transform",
98 [](Node& node) {
99 return nb::ndarray<nb::numpy, float, nb::f_contig, nb::shape<4, 4>>(
100 node.transform.data(),
101 {4, 4},
102 nb::find(node),
103 {1, 4});
104 },
105 [](Node& node, nb::ndarray<nb::numpy, const float, nb::shape<4, 4>> t) -> void {
106 auto view = t.view<float, nb::ndim<2>>();
107 // Explicit 2D indexing because the input ndarray can be either row or column major.
108 for (size_t i = 0; i < 4; i++) {
109 for (size_t j = 0; j < 4; j++) {
110 node.transform.data()[i + j * 4] = view(i, j);
111 }
112 }
113 },
114 "Transform of the node, relative to its parent",
115 nb::for_setter(nb::sig("def transform(self, arg: numpy.typing.ArrayLike, /) -> None")))
116 .def_prop_rw(
117 "parent",
118 [](Node& node) -> std::optional<ElementId> {
119 if (node.parent != invalid_element)
120 return node.parent;
121 else
122 return {};
123 },
124 [](Node& node, ElementId parent) { node.parent = parent; },
125 "Parent index. May be invalid if the node has no parent (e.g. the root)")
126 .def_rw("children", &Node::children, "Children indices. May be empty")
127 .def_rw(
128 "meshes",
129 &Node::meshes,
130 "List of meshes contained in this node",
131 LA_SAFE_VECTOR_SETTER("meshes", "collections.abc.Sequence[SceneMeshInstance]"))
132 .def_rw("cameras", &Node::cameras, "List of cameras contained in this node")
133 .def_rw("lights", &Node::lights, "List of lights contained in this node")
134 .def_rw("extensions", &Node::extensions);
135
136 nb::class_<ImageBufferExperimental> image_buffer(
137 m,
138 "ImageBuffer",
139 "Minimalistic image data structure that stores the raw image data");
140 image_buffer.def(nb::init<>())
141 .def(
142 "__repr__",
143 [](const ImageBufferExperimental& self) { return scene::internal::to_string(self); })
144 .def_ro("width", &ImageBufferExperimental::width, "Image width")
145 .def_ro("height", &ImageBufferExperimental::height, "Image height")
146 .def_ro(
147 "num_channels",
148 &ImageBufferExperimental::num_channels,
149 "Number of image channels (must be 1, 3, or 4)")
150 .def_prop_rw(
151 "data",
152 [](ImageBufferExperimental& self) {
153 size_t shape[3] = {self.height, self.width, self.num_channels};
154 switch (self.element_type) {
155 case AttributeValueType::e_int8_t:
156 return nb::cast(
157 nb::ndarray<int8_t, nb::numpy, nb::c_contig, nb::device::cpu>(
158 reinterpret_cast<int8_t*>(self.data.data()),
159 3,
160 shape,
161 nb::find(self)),
162 nb::rv_policy::reference_internal);
163 case AttributeValueType::e_uint8_t:
164 return nb::cast(
165 nb::ndarray<uint8_t, nb::numpy, nb::c_contig, nb::device::cpu>(
166 reinterpret_cast<uint8_t*>(self.data.data()),
167 3,
168 shape,
169 nb::find(self)),
170 nb::rv_policy::reference_internal);
171 case AttributeValueType::e_int16_t:
172 return nb::cast(
173 nb::ndarray<int16_t, nb::numpy, nb::c_contig, nb::device::cpu>(
174 reinterpret_cast<int16_t*>(self.data.data()),
175 3,
176 shape,
177 nb::find(self)),
178 nb::rv_policy::reference_internal);
179 case AttributeValueType::e_uint16_t:
180 return nb::cast(
181 nb::ndarray<uint16_t, nb::numpy, nb::c_contig, nb::device::cpu>(
182 reinterpret_cast<uint16_t*>(self.data.data()),
183 3,
184 shape,
185 nb::find(self)),
186 nb::rv_policy::reference_internal);
187 case AttributeValueType::e_int32_t:
188 return nb::cast(
189 nb::ndarray<int32_t, nb::numpy, nb::c_contig, nb::device::cpu>(
190 reinterpret_cast<int32_t*>(self.data.data()),
191 3,
192 shape,
193 nb::find(self)),
194 nb::rv_policy::reference_internal);
195 case AttributeValueType::e_uint32_t:
196 return nb::cast(
197 nb::ndarray<uint32_t, nb::numpy, nb::c_contig, nb::device::cpu>(
198 reinterpret_cast<uint32_t*>(self.data.data()),
199 3,
200 shape,
201 nb::find(self)),
202 nb::rv_policy::reference_internal);
203 case AttributeValueType::e_int64_t:
204 return nb::cast(
205 nb::ndarray<int64_t, nb::numpy, nb::c_contig, nb::device::cpu>(
206 reinterpret_cast<int64_t*>(self.data.data()),
207 3,
208 shape,
209 nb::find(self)),
210 nb::rv_policy::reference_internal);
211 case AttributeValueType::e_uint64_t:
212 return nb::cast(
213 nb::ndarray<uint64_t, nb::numpy, nb::c_contig, nb::device::cpu>(
214 reinterpret_cast<uint64_t*>(self.data.data()),
215 3,
216 shape,
217 nb::find(self)),
218 nb::rv_policy::reference_internal);
219 case AttributeValueType::e_float:
220 return nb::cast(
221 nb::ndarray<float, nb::numpy, nb::c_contig, nb::device::cpu>(
222 reinterpret_cast<float*>(self.data.data()),
223 3,
224 shape,
225 nb::find(self)),
226 nb::rv_policy::reference_internal);
227 case AttributeValueType::e_double:
228 return nb::cast(
229 nb::ndarray<double, nb::numpy, nb::c_contig, nb::device::cpu>(
230 reinterpret_cast<double*>(self.data.data()),
231 3,
232 shape,
233 nb::find(self)),
234 nb::rv_policy::reference_internal);
235 default: throw nb::type_error("Unsupported image buffer `dtype`!");
236 }
237 },
238 [](ImageBufferExperimental& self,
239 nb::ndarray<nb::numpy, nb::c_contig, nb::device::cpu> tensor) {
240 la_runtime_assert(tensor.ndim() == 3);
241 self.width = tensor.shape(1);
242 self.height = tensor.shape(0);
243 self.num_channels = tensor.shape(2);
244 auto dtype = tensor.dtype();
245 if (dtype == nb::dtype<int8_t>()) {
246 self.element_type = AttributeValueType::e_int8_t;
247 } else if (dtype == nb::dtype<uint8_t>()) {
248 self.element_type = AttributeValueType::e_uint8_t;
249 } else if (dtype == nb::dtype<int16_t>()) {
250 self.element_type = AttributeValueType::e_int16_t;
251 } else if (dtype == nb::dtype<uint16_t>()) {
252 self.element_type = AttributeValueType::e_uint16_t;
253 } else if (dtype == nb::dtype<int32_t>()) {
254 self.element_type = AttributeValueType::e_int32_t;
255 } else if (dtype == nb::dtype<uint32_t>()) {
256 self.element_type = AttributeValueType::e_uint32_t;
257 } else if (dtype == nb::dtype<int64_t>()) {
258 self.element_type = AttributeValueType::e_int64_t;
259 } else if (dtype == nb::dtype<uint64_t>()) {
260 self.element_type = AttributeValueType::e_uint64_t;
261 } else if (dtype == nb::dtype<float>()) {
262 self.element_type = AttributeValueType::e_float;
263 } else if (dtype == nb::dtype<double>()) {
264 self.element_type = AttributeValueType::e_double;
265 } else {
266 throw nb::type_error("Unsupported input tensor `dtype`!");
267 }
268 self.data.resize(tensor.nbytes());
269 std::copy(
270 reinterpret_cast<uint8_t*>(tensor.data()),
271 reinterpret_cast<uint8_t*>(tensor.data()) + tensor.nbytes(),
272 self.data.data());
273 },
274 "Raw buffer of size (width * height * num_channels * num_bits_per_element / 8) bytes "
275 "containing image data",
276 nb::for_getter(
277 nb::sig("def data(self) -> Annotated[NDArray, dict(order='C', device='cpu')]")))
278 .def_prop_ro(
279 "dtype",
280 [](ImageBufferExperimental& self) -> std::optional<nb::type_object> {
281 auto np = nb::module_::import_("numpy");
282 switch (self.element_type) {
283 case AttributeValueType::e_int8_t: return np.attr("int8");
284 case AttributeValueType::e_int16_t: return np.attr("int16");
285 case AttributeValueType::e_int32_t: return np.attr("int32");
286 case AttributeValueType::e_int64_t: return np.attr("int64");
287 case AttributeValueType::e_uint8_t: return np.attr("uint8");
288 case AttributeValueType::e_uint16_t: return np.attr("uint16");
289 case AttributeValueType::e_uint32_t: return np.attr("uint32");
290 case AttributeValueType::e_uint64_t: return np.attr("uint64");
291 case AttributeValueType::e_float: return np.attr("float32");
292 case AttributeValueType::e_double: return np.attr("float64");
293 default: logger().warn("Image buffer has an unknown dtype."); return std::nullopt;
294 }
295 },
296 "The scalar type of the elements in the buffer");
297
298 nb::class_<ImageExperimental> image(
299 m,
300 "Image",
301 "Image structure that can store either image data or reference to an image file");
302 image.def(nb::init<>())
303 .def(
304 "__repr__",
305 [](const ImageExperimental& self) { return scene::internal::to_string(self); })
306 .def_rw(
307 "name",
308 &ImageExperimental::name,
309 "Image name. Not guaranteed to be unique and can be empty")
310 .def_rw("image", &ImageExperimental::image, "Image data")
311 .def_prop_rw(
312 "uri",
313 [](const ImageExperimental& self) -> std::optional<std::string> {
314 if (self.uri.empty())
315 return {};
316 else
317 return self.uri.string();
318 },
319 [](ImageExperimental& self, std::optional<std::string> uri) {
320 if (uri.has_value())
321 self.uri = fs::path(uri.value());
322 else
323 self.uri = fs::path();
324 },
325 "Image file path. This path is relative to the file that contains the scene. It is "
326 "only valid if image data should be mapped to an external file")
327 .def_rw("extensions", &ImageExperimental::extensions, "Image extensions");
328
329 nb::class_<TextureInfo>(
330 m,
331 "TextureInfo",
332 "Pair of texture index (which texture to use) and texture coordinate index (which set of "
333 "UVs to use)")
334 .def(nb::init<>())
335 .def("__repr__", [](const TextureInfo& self) { return scene::internal::to_string(self); })
336 .def_prop_rw(
337 "index",
338 [](const TextureInfo& self) -> std::optional<ElementId> {
339 if (self.index != invalid_element)
340 return self.index;
341 else
342 return {};
343 },
344 [](TextureInfo& self, std::optional<ElementId> index) {
345 if (index.has_value())
346 self.index = index.value();
347 else
348 self.index = invalid_element;
349 },
350 "Texture index. Index in scene.textures vector. `None` if not set")
351 .def_rw(
352 "texcoord",
353 &TextureInfo::texcoord,
354 "Index of UV coordinates. Usually stored in the mesh as `texcoord_x` attribute where x "
355 "is this variable. This is typically 0");
356
357 nb::class_<MaterialExperimental> material(
358 m,
359 "Material",
360 "PBR material, based on the gltf specification. This is subject to change, to support more "
361 "material models");
362 material.def(nb::init<>())
363 .def(
364 "__repr__",
365 [](const MaterialExperimental& self) { return scene::internal::to_string(self); })
366 .def_rw(
367 "name",
368 &MaterialExperimental::name,
369 "Material name. May not be unique, and can be empty")
370 .def_rw("base_color_value", &MaterialExperimental::base_color_value, "Base color value")
371 .def_rw(
372 "base_color_texture",
373 &MaterialExperimental::base_color_texture,
374 "Base color texture")
375 .def_rw(
376 "alpha_mode",
377 &MaterialExperimental::alpha_mode,
378 "The alpha mode specifies how to interpret the alpha value of the base color")
379 .def_rw("alpha_cutoff", &MaterialExperimental::alpha_cutoff, "Alpha cutoff value")
380 .def_rw("emissive_value", &MaterialExperimental::emissive_value, "Emissive color value")
381 .def_rw("emissive_texture", &MaterialExperimental::emissive_texture, "Emissive texture")
382 .def_rw("metallic_value", &MaterialExperimental::metallic_value, "Metallic value")
383 .def_rw("roughness_value", &MaterialExperimental::roughness_value, "Roughness value")
384 .def_rw(
385 "metallic_roughness_texture",
386 &MaterialExperimental::metallic_roughness_texture,
387 "Metalness and roughness are packed together in a single texture. Green channel has "
388 "roughness, blue channel has metalness")
389 .def_rw("normal_texture", &MaterialExperimental::normal_texture, "Normal texture")
390 .def_rw(
391 "normal_scale",
392 &MaterialExperimental::normal_scale,
393 "Normal scaling factor. normal = normalize(<sampled tex value> * 2 - 1) * vec3(scale, "
394 "scale, 1)")
395 .def_rw("occlusion_texture", &MaterialExperimental::occlusion_texture, "Occlusion texture")
396 .def_rw(
397 "occlusion_strength",
398 &MaterialExperimental::occlusion_strength,
399 "Occlusion strength. color = lerp(color, color * <sampled tex value>, strength)")
400 .def_rw(
401 "double_sided",
402 &MaterialExperimental::double_sided,
403 "Whether the material is double-sided")
404 .def_rw("extensions", &MaterialExperimental::extensions, "Material extensions");
405
406 nb::enum_<MaterialExperimental::AlphaMode>(material, "AlphaMode", "Alpha mode")
407 .value(
408 "Opaque",
409 MaterialExperimental::AlphaMode::Opaque,
410 "Alpha is ignored, and rendered output is opaque")
411 .value(
412 "Mask",
413 MaterialExperimental::AlphaMode::Mask,
414 "Output is either opaque or transparent depending on the alpha value and the "
415 "alpha_cutoff value")
416 .value(
417 "Blend",
418 MaterialExperimental::AlphaMode::Blend,
419 "Alpha value is used to composite source and destination");
420
421
422 nb::class_<Texture> texture(m, "Texture", "Texture");
423 texture.def(nb::init<>())
424 .def("__repr__", [](const Texture& self) { return scene::internal::to_string(self); })
425 .def_rw("name", &Texture::name, "Texture name")
426 .def_prop_rw(
427 "image",
428 [](Texture& self) -> std::optional<ElementId> {
429 if (self.image != invalid_element)
430 return self.image;
431 else
432 return {};
433 },
434 [](Texture& self, ElementId img) { self.image = img; },
435 "Index of image in scene.images vector (None if invalid)")
436 .def_rw(
437 "mag_filter",
438 &Texture::mag_filter,
439 "Texture magnification filter, used when texture appears larger on screen than the "
440 "source image")
441 .def_rw(
442 "min_filter",
443 &Texture::min_filter,
444 "Texture minification filter, used when the texture appears smaller on screen than the "
445 "source image")
446 .def_rw("wrap_u", &Texture::wrap_u, "Texture wrap mode for U coordinate")
447 .def_rw("wrap_v", &Texture::wrap_v, "Texture wrap mode for V coordinate")
448 .def_rw("scale", &Texture::scale, "Texture scale")
449 .def_rw("offset", &Texture::offset, "Texture offset")
450 .def_rw("rotation", &Texture::rotation, "Texture rotation")
451 .def_rw("extensions", &Texture::extensions, "Texture extensions");
452
453 nb::enum_<Texture::WrapMode>(texture, "WrapMode", "Texture wrap mode")
454 .value("Wrap", Texture::WrapMode::Wrap, "u|v becomes u%1|v%1")
455 .value(
456 "Clamp",
457 Texture::WrapMode::Clamp,
458 "Coordinates outside [0, 1] are clamped to the nearest value")
459 .value(
460 "Decal",
461 Texture::WrapMode::Decal,
462 "If the texture coordinates for a pixel are outside [0, 1], the texture is not applied")
463 .value("Mirror", Texture::WrapMode::Mirror, "Mirror wrap mode");
464 nb::enum_<Texture::TextureFilter>(texture, "TextureFilter", "Texture filter mode")
465 .value("Undefined", Texture::TextureFilter::Undefined, "Undefined filter")
466 .value("Nearest", Texture::TextureFilter::Nearest, "Nearest neighbor filtering")
467 .value("Linear", Texture::TextureFilter::Linear, "Linear filtering")
468 .value(
469 "NearestMipmapNearest",
470 Texture::TextureFilter::NearestMipmapNearest,
471 "Nearest mipmap nearest filtering")
472 .value(
473 "LinearMipmapNearest",
474 Texture::TextureFilter::LinearMipmapNearest,
475 "Linear mipmap nearest filtering")
476 .value(
477 "NearestMipmapLinear",
478 Texture::TextureFilter::NearestMipmapLinear,
479 "Nearest mipmap linear filtering")
480 .value(
481 "LinearMipmapLinear",
482 Texture::TextureFilter::LinearMipmapLinear,
483 "Linear mipmap linear filtering");
484
485 nb::class_<Light> light(m, "Light", "Light");
486 light.def(nb::init<>())
487 .def("__repr__", [](const Light& self) { return scene::internal::to_string(self); })
488 .def_rw("name", &Light::name, "Light name")
489 .def_rw("type", &Light::type, "Light type")
490 .def_rw(
491 "position",
492 &Light::position,
493 "Light position. Note that the light is part of the scene graph, and has an associated "
494 "transform in its node. This value is relative to the coordinate system defined by the "
495 "node")
496 .def_rw("direction", &Light::direction, "Light direction")
497 .def_rw("up", &Light::up, "Light up vector")
498 .def_rw("intensity", &Light::intensity, "Light intensity")
499 .def_rw(
500 "attenuation_constant",
501 &Light::attenuation_constant,
502 "Attenuation constant. Intensity of light at a given distance 'd' is: intensity / "
503 "(attenuation_constant + attenuation_linear * d + attenuation_quadratic * d * d + "
504 "attenuation_cubic * d * d * d)")
505 .def_rw("attenuation_linear", &Light::attenuation_linear, "Linear attenuation factor")
506 .def_rw(
507 "attenuation_quadratic",
508 &Light::attenuation_quadratic,
509 "Quadratic attenuation factor")
510 .def_rw("attenuation_cubic", &Light::attenuation_cubic, "Cubic attenuation factor")
511 .def_rw(
512 "range",
513 &Light::range,
514 "Range is defined for point and spot lights. It defines a distance cutoff at which the "
515 "light intensity is to be considered zero. When the value is 0, range is assumed to be "
516 "infinite")
517 .def_rw("color_diffuse", &Light::color_diffuse, "Diffuse color")
518 .def_rw("color_specular", &Light::color_specular, "Specular color")
519 .def_rw("color_ambient", &Light::color_ambient, "Ambient color")
520 .def_rw(
521 "angle_inner_cone",
522 &Light::angle_inner_cone,
523 "Inner angle of a spot light's light cone. 2PI for point lights, undefined for "
524 "directional lights")
525 .def_rw(
526 "angle_outer_cone",
527 &Light::angle_outer_cone,
528 "Outer angle of a spot light's light cone. 2PI for point lights, undefined for "
529 "directional lights")
530 .def_rw("size", &Light::size, "Size of area light source")
531 .def_rw("extensions", &Light::extensions, "Light extensions");
532
533 nb::enum_<Light::Type>(light, "Type", "Light type")
534 .value("Undefined", Light::Type::Undefined, "Undefined light type")
535 .value("Directional", Light::Type::Directional, "Directional light")
536 .value("Point", Light::Type::Point, "Point light")
537 .value("Spot", Light::Type::Spot, "Spot light")
538 .value("Ambient", Light::Type::Ambient, "Ambient light")
539 .value("Area", Light::Type::Area, "Area light");
540
541 nb::class_<Camera> camera(m, "Camera", "Camera");
542 camera.def(nb::init<>())
543 .def("__repr__", [](const Camera& self) { return scene::internal::to_string(self); })
544 .def_rw("name", &Camera::name, "Camera name")
545 .def_rw(
546 "position",
547 &Camera::position,
548 "Camera position. Note that the camera is part of the scene graph, and has an "
549 "associated transform in its node. This value is relative to the coordinate system "
550 "defined by the node")
551 .def_rw("up", &Camera::up, "Camera up vector")
552 .def_rw("look_at", &Camera::look_at, "Camera look-at point")
553 .def_rw(
554 "near_plane",
555 &Camera::near_plane,
556 "Distance of the near clipping plane. This value cannot be 0")
557 .def_rw("far_plane", &Camera::far_plane, "Distance of the far clipping plane")
558 .def_rw("type", &Camera::type, "Camera type")
559 .def_rw(
560 "aspect_ratio",
561 &Camera::aspect_ratio,
562 "Screen aspect ratio. This is the value of width / height of the screen. aspect_ratio "
563 "= tan(horizontal_fov / 2) / tan(vertical_fov / 2)")
564 .def_rw(
565 "horizontal_fov",
566 &Camera::horizontal_fov,
567 "Horizontal field of view angle, in radians. This is the angle between the left and "
568 "right borders of the viewport. It should not be greater than Pi. fov is only defined "
569 "when the camera type is perspective, otherwise it should be 0")
570 .def_rw(
571 "orthographic_width",
572 &Camera::orthographic_width,
573 "Half width of the orthographic view box. Or horizontal magnification. This is only "
574 "defined when the camera type is orthographic, otherwise it should be 0")
575 .def_prop_ro(
576 "get_vertical_fov",
577 &Camera::get_vertical_fov,
578 "Get the vertical field of view. Make sure aspect_ratio is set before calling this")
579 .def(
580 "set_horizontal_fov_from_vertical_fov",
581 &Camera::set_horizontal_fov_from_vertical_fov,
582 "vfov"_a,
583 "Set horizontal fov from vertical fov. Make sure aspect_ratio is set before calling "
584 "this")
585 .def_rw("extensions", &Camera::extensions, "Camera extensions");
586
587 nb::enum_<Camera::Type>(camera, "Type", "Camera type")
588 .value("Perspective", Camera::Type::Perspective, "Perspective projection")
589 .value("Orthographic", Camera::Type::Orthographic, "Orthographic projection");
590
591 nb::class_<Animation>(m, "Animation", "Animation")
592 .def(nb::init<>())
593 .def("__repr__", [](const Animation& self) { return scene::internal::to_string(self); })
594 .def_rw("name", &Animation::name, "Animation name")
595 .def_rw("extensions", &Animation::extensions, "Animation extensions");
596
597
598 nb::class_<Skeleton>(m, "Skeleton", "Skeleton")
599 .def(nb::init<>())
600 .def("__repr__", [](const Skeleton& self) { return scene::internal::to_string(self); })
601 .def_rw(
602 "meshes",
603 &Skeleton::meshes,
604 "This skeleton is used to deform those meshes. This will typically contain one value, "
605 "but can have zero or multiple meshes. The value is the index in the scene meshes")
606 .def_rw("extensions", &Skeleton::extensions, "Skeleton extensions");
607
608
609 nb::class_<SceneType>(m, "Scene", "A 3D scene")
610 .def(nb::init<>())
611 .def("__repr__", [](const SceneType& self) { return scene::internal::to_string(self); })
612 .def_rw("name", &SceneType::name, "Name of the scene")
613 .def_rw(
614 "nodes",
615 &SceneType::nodes,
616 "Scene nodes. This is a list of nodes, the hierarchy information is contained by each "
617 "node having a list of children as indices to this vector")
618 .def_rw(
619 "root_nodes",
620 &SceneType::root_nodes,
621 "Root nodes. This is typically one. Must be at least one")
622 .def_rw(
623 "meshes",
624 &SceneType::meshes,
625 "Scene meshes",
626 LA_SAFE_VECTOR_SETTER("meshes", "collections.abc.Sequence[lagrange.core.SurfaceMesh]"))
627 .def_rw("images", &SceneType::images, "Images")
628 .def_rw("textures", &SceneType::textures, "Textures. They can reference images")
629 .def_rw("materials", &SceneType::materials, "Materials. They can reference textures")
630 .def_rw("lights", &SceneType::lights, "Lights in the scene")
631 .def_rw(
632 "cameras",
633 &SceneType::cameras,
634 "Cameras. The first camera (if any) is the default camera view")
635 .def_rw("skeletons", &SceneType::skeletons, "Scene skeletons")
636 .def_rw("animations", &SceneType::animations, "Animations (unused for now)")
637 .def_rw("extensions", &SceneType::extensions, "Scene extensions")
638 .def(
639 "add",
640 [](SceneType& self,
641 std::variant<
642 Node,
644 ImageExperimental,
645 Texture,
646 MaterialExperimental,
647 Light,
648 Camera,
649 Skeleton,
650 Animation> element) {
651 return std::visit(
652 [&](auto&& value) {
653 using T = std::decay_t<decltype(value)>;
654 return self.add(std::forward<T>(value));
655 },
656 element);
657 },
658 "element"_a,
659 R"(Add an element to the scene.
660
661:param element: The element to add to the scene. E.g. node, mesh, image, texture, material, light, camera, skeleton, or animation.
662
663:returns: The id of the added element.)")
664 .def(
665 "add_child",
666 &SceneType::add_child,
667 "parent_id"_a,
668 "child_id"_a,
669 R"(Add a child node to a parent node. The parent-child relationship will be updated for both nodes.
670
671:param parent_id: The parent node id.
672:param child_id: The child node id.
673
674:returns: The id of the added child node.)");
675
676 m.def(
677 "compute_global_node_transform",
678 [](const SceneType& scene, size_t node_idx) {
679 auto t = utils::compute_global_node_transform<Scalar, Index>(scene, node_idx);
680 return nb::ndarray<nb::numpy, float, nb::f_contig, nb::shape<4, 4>>(
681 t.data(),
682 {4, 4},
683 nb::handle(), // owner
684 {1, 4})
685 .cast();
686 },
687 "scene"_a,
688 "node_idx"_a,
689 R"(Compute the global transform associated with a node.
690
691:param scene: The input scene.
692:param node_idx: The index of the target node.
693
694:returns: The global transform of the target node, which is the combination of transforms from this node all the way to the root.
695 )");
696
697 m.def(
698 "camera_transforms_from_scene",
699 [](const SceneType& scene) {
700 return scene::internal::camera_transforms_from_scene<Scalar, Index>(scene);
701 },
702 "scene"_a,
703 R"(Extract view and projection transforms for every camera referenced by a node in the scene.
704
705:param scene: The input scene.
706
707:returns: A list of CameraTransforms, one per camera instance in the scene.)");
708
709 m.def(
710 "scene_to_mesh",
711 [](const SceneType& scene,
712 bool normalize_normals,
713 bool normalize_tangents_bitangents,
714 bool reorient,
715 bool preserve_attributes) {
716 TransformOptions transform_options;
717 transform_options.normalize_normals = normalize_normals;
718 transform_options.normalize_tangents_bitangents = normalize_tangents_bitangents;
719 transform_options.reorient = reorient;
720 return scene::scene_to_mesh(scene, transform_options, preserve_attributes);
721 },
722 "scene"_a,
723 nb::kw_only(),
724 "normalize_normals"_a = TransformOptions{}.normalize_normals,
725 "normalize_tangents_bitangents"_a = TransformOptions{}.normalize_tangents_bitangents,
726 "reorient"_a = TransformOptions{}.reorient,
727 "preserve_attributes"_a = true,
728 R"(Converts a scene into a concatenated mesh with all the transforms applied.
729
730:param scene: Scene to convert.
731:param normalize_normals: If enabled, normals are normalized after transformation.
732:param normalize_tangents_bitangents: If enabled, tangents and bitangents are normalized after transformation.
733:param reorient: If enabled, flip facets and reorient attributes for instances with a negative-determinant transform.
734:param preserve_attributes: Preserve shared attributes and map them to the output mesh.
735
736:return: Concatenated mesh.)");
737
738 m.def(
739 "scene_to_meshes",
740 [](const SceneType& scene,
741 bool normalize_normals,
742 bool normalize_tangents_bitangents,
743 bool reorient) {
744 TransformOptions transform_options;
745 transform_options.normalize_normals = normalize_normals;
746 transform_options.normalize_tangents_bitangents = normalize_tangents_bitangents;
747 transform_options.reorient = reorient;
748 return scene::scene_to_meshes(scene, transform_options);
749 },
750 "scene"_a,
751 nb::kw_only(),
752 "normalize_normals"_a = TransformOptions{}.normalize_normals,
753 "normalize_tangents_bitangents"_a = TransformOptions{}.normalize_tangents_bitangents,
754 "reorient"_a = TransformOptions{}.reorient,
755 R"(Converts a scene into a list of meshes with all the transforms applied.
756
757:param scene: Scene to convert.
758:param normalize_normals: If enabled, normals are normalized after transformation.
759:param normalize_tangents_bitangents: If enabled, tangents and bitangents are normalized after transformation.
760:param reorient: If enabled, flip facets and reorient attributes for instances with a negative-determinant transform.
761
762:return: List of transformed meshes.)");
763
764 m.def(
765 "scene_to_meshes_and_materials",
766 [](const SceneType& scene,
767 bool normalize_normals,
768 bool normalize_tangents_bitangents,
769 bool reorient)
770 -> std::pair<std::vector<SceneType::MeshType>, std::vector<std::vector<ElementId>>> {
771 TransformOptions transform_options;
772 transform_options.normalize_normals = normalize_normals;
773 transform_options.normalize_tangents_bitangents = normalize_tangents_bitangents;
774 transform_options.reorient = reorient;
775 auto [meshes, material_ids] =
776 scene::scene_to_meshes_and_materials(scene, transform_options);
777 return {std::move(meshes), std::move(material_ids)};
778 },
779 "scene"_a,
780 nb::kw_only(),
781 "normalize_normals"_a = TransformOptions{}.normalize_normals,
782 "normalize_tangents_bitangents"_a = TransformOptions{}.normalize_tangents_bitangents,
783 "reorient"_a = TransformOptions{}.reorient,
784 R"(Converts a scene into a list of meshes with all the transforms applied and a list of material IDs.
785
786:param scene: Scene to convert.
787:param normalize_normals: If enabled, normals are normalized after transformation.
788:param normalize_tangents_bitangents: If enabled, tangents and bitangents are normalized after transformation.
789:param reorient: If enabled, flip facets and reorient attributes for instances with a negative-determinant transform.
790
791:return: List of meshes with transforms applied and a list of material IDs.)");
792
793 m.def(
794 "mesh_to_scene",
795 [](const SceneType::MeshType& mesh) { return scene::mesh_to_scene(mesh); },
796 "mesh"_a,
797 R"(Converts a single mesh into a scene with a single identity instance of the input mesh.
798
799:param mesh: Input mesh to convert.
800
801:return: Scene containing the input mesh.)");
802
803 m.def(
804 "meshes_to_scene",
805 [](std::vector<SceneType::MeshType> meshes) {
806 return scene::meshes_to_scene(std::move(meshes));
807 },
808 "meshes"_a,
809 R"(Converts a list of meshes into a scene with a single identity instance of each input mesh.
810
811:param meshes: Input meshes to convert.
812
813:return: Scene containing the input meshes.)");
814
815 using SimpleScene3D = scene::SimpleScene<Scalar, Index, 3>;
816
817 m.def(
818 "scene_to_simple_scene",
819 [](const SceneType& scene) { return scene::scene_to_simple_scene(scene); },
820 "scene"_a,
821 R"(Converts a Scene into a SimpleScene.
822
823The Scene's node hierarchy is flattened: each mesh instance in the scene becomes a
824MeshInstance in the SimpleScene with the accumulated world transform. Meshes are copied
825by index. Materials and other scene metadata (images, textures, cameras, lights) are not
826preserved in the SimpleScene.
827
828:param scene: Input scene to convert.
829
830:return: SimpleScene containing all mesh instances from the scene.)");
831
832 m.def(
833 "simple_scene_to_scene",
834 [](const SimpleScene3D& simple_scene) {
835 return scene::simple_scene_to_scene(simple_scene);
836 },
837 "simple_scene"_a,
838 R"(Converts a SimpleScene into a Scene.
839
840Each mesh instance in the SimpleScene becomes a node in the Scene with the instance
841transform. All nodes are direct children of a single root node. Meshes are copied
842by index.
843
844:param simple_scene: Input simple scene to convert.
845
846:return: Scene containing the meshes and instances from the SimpleScene.)");
847}
848
849} // namespace lagrange::python
SurfaceMesh< Scalar, Index > MeshType
Definition SimpleScene.h:65
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
SurfaceMesh< ToScalar, ToIndex > cast(const SurfaceMesh< FromScalar, FromIndex > &source_mesh, const AttributeFilter &convertible_attributes={}, std::vector< std::string > *converted_attributes_names=nullptr)
Cast a mesh to a mesh of different scalar and/or index type.
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:177
bool reorient
If enabled, when applying a transform with negative determinant:
Definition TransformOptions.h:39
bool normalize_normals
If enabled, normals are normalized after transformation.
Definition TransformOptions.h:31
bool normalize_tangents_bitangents
If enabled, tangents and bitangents are normalized after transformation.
Definition TransformOptions.h:34
size_t height
Image height.
Definition Scene.h:95
size_t width
Image width.
Definition Scene.h:92
AttributeValueType element_type
The scalar type of the elements in the buffer.
Definition Scene.h:101
std::vector< unsigned char > data
Raw buffer of size (width * height * num_channels * num_bits_per_element / 8) bytes containing image ...
Definition Scene.h:104
size_t num_channels
Number of image channels (must be 1, 3, or 4).
Definition Scene.h:98
fs::path uri
Image file path.
Definition Scene.h:125
ElementId index
Texture index. Index in scene.textures vector.
Definition Scene.h:137