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