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