14#include <lagrange/AttributeValueType.h>
15#include <lagrange/Logger.h>
16#include <lagrange/python/tensor_utils.h>
17#include <lagrange/scene/Scene.h>
18#include <lagrange/scene/internal/scene_string_utils.h>
19#include <lagrange/scene/scene_utils.h>
20#include <lagrange/utils/assert.h>
22#include "bind_value.h"
25#include <lagrange/utils/warnoff.h>
27#include <nanobind/eigen/dense.h>
28#include <nanobind/eval.h>
29#include <nanobind/nanobind.h>
30#include <nanobind/stl/array.h>
31#include <nanobind/stl/bind_map.h>
32#include <nanobind/stl/bind_vector.h>
33#include <nanobind/stl/filesystem.h>
34#include <nanobind/stl/optional.h>
35#include <nanobind/stl/string.h>
36#include <nanobind/stl/variant.h>
37#include <nanobind/trampoline.h>
38#include <lagrange/utils/warnon.h>
42namespace lagrange::python {
44namespace nb = nanobind;
46void bind_scene(nb::module_& m)
48 using namespace lagrange::scene;
49 using Scalar = double;
50 using Index = uint32_t;
53 nb::bind_vector<std::vector<Node>>(m,
"NodeList");
54 nb::bind_vector<std::vector<ElementId>>(m,
"ElementIdList");
55 nb::bind_vector<std::vector<SceneMeshInstance>>(m,
"SceneMeshInstanceList");
56 nb::bind_vector<std::vector<SurfaceMesh<Scalar, Index>>>(m,
"SurfaceMeshList");
57 nb::bind_vector<std::vector<ImageExperimental>>(m,
"ImageList");
58 nb::bind_vector<std::vector<Texture>>(m,
"TextureList");
59 nb::bind_vector<std::vector<MaterialExperimental>>(m,
"MaterialList");
60 nb::bind_vector<std::vector<Light>>(m,
"LightList");
61 nb::bind_vector<std::vector<Camera>>(m,
"CameraList");
62 nb::bind_vector<std::vector<Skeleton>>(m,
"SkeletonList");
63 nb::bind_vector<std::vector<Animation>>(m,
"AnimationList");
66 nb::bind_vector<std::vector<lagrange::scene::Value>>(m,
"ValueList");
67 nb::bind_vector<std::vector<unsigned char>>(m,
"BufferList");
68 nb::bind_map<std::unordered_map<std::string, lagrange::scene::Value>>(m,
"ValueUnorderedMap");
69 nb::bind_map<std::map<std::string, lagrange::scene::Value>>(m,
"ValueMap");
72 nb::class_<lagrange::scene::Extensions>(m,
"Extensions")
74 .def_prop_ro(
"size", &Extensions::size)
75 .def_prop_ro(
"empty", &Extensions::empty)
76 .def_rw(
"data", &Extensions::data);
78 nb::class_<SceneMeshInstance>(m,
"SceneMeshInstance",
"Mesh and material index of a node")
86 if (self.mesh != invalid_element)
92 "Mesh index in the scene.meshes vector (None if invalid)")
93 .def_rw(
"materials", &SceneMeshInstance::materials);
95 nb::class_<Node>(m,
"Node")
97 .def(
"__repr__", [](
const Node& self) {
return scene::internal::to_string(self); })
98 .def_rw(
"name", &Node::name)
102 return nb::ndarray<nb::numpy, float, nb::f_contig, nb::shape<4, 4>>(
103 node.transform.data(),
108 [](
Node& node, nb::ndarray<nb::numpy, const float, nb::shape<4, 4>> t) ->
void {
109 auto view = t.view<float, nb::ndim<2>>();
111 for (
size_t i = 0; i < 4; i++) {
112 for (
size_t j = 0; j < 4; j++) {
113 node.transform.data()[i + j * 4] = view(i, j);
117 "The affine transform associated with this node")
120 [](
Node& node) -> std::optional<ElementId> {
121 if (node.parent != invalid_element)
126 [](
Node& node, ElementId parent) { node.parent = parent; },
127 "Parent node index (None if no parent)")
128 .def_rw(
"children", &Node::children)
129 .def_rw(
"meshes", &Node::meshes)
130 .def_rw(
"cameras", &Node::cameras)
131 .def_rw(
"lights", &Node::lights)
132 .def_rw(
"extensions", &Node::extensions);
134 nb::class_<ImageBufferExperimental> image_buffer(m,
"ImageBuffer");
135 image_buffer.def(nb::init<>())
139 .def_ro(
"width", &ImageBufferExperimental::width,
"Image width")
140 .def_ro(
"height", &ImageBufferExperimental::height,
"Image height")
143 &ImageBufferExperimental::num_channels,
144 "Number of channels in each pixel")
150 case AttributeValueType::e_int8_t:
152 nb::ndarray<int8_t, nb::numpy, nb::c_contig, nb::device::cpu>(
153 reinterpret_cast<int8_t*
>(self.
data.data()),
157 nb::rv_policy::reference_internal);
158 case AttributeValueType::e_uint8_t:
160 nb::ndarray<uint8_t, nb::numpy, nb::c_contig, nb::device::cpu>(
161 reinterpret_cast<uint8_t*
>(self.
data.data()),
165 nb::rv_policy::reference_internal);
166 case AttributeValueType::e_int16_t:
168 nb::ndarray<int16_t, nb::numpy, nb::c_contig, nb::device::cpu>(
169 reinterpret_cast<int16_t*
>(self.
data.data()),
173 nb::rv_policy::reference_internal);
174 case AttributeValueType::e_uint16_t:
176 nb::ndarray<uint16_t, nb::numpy, nb::c_contig, nb::device::cpu>(
177 reinterpret_cast<uint16_t*
>(self.
data.data()),
181 nb::rv_policy::reference_internal);
182 case AttributeValueType::e_int32_t:
184 nb::ndarray<int32_t, nb::numpy, nb::c_contig, nb::device::cpu>(
185 reinterpret_cast<int32_t*
>(self.
data.data()),
189 nb::rv_policy::reference_internal);
190 case AttributeValueType::e_uint32_t:
192 nb::ndarray<uint32_t, nb::numpy, nb::c_contig, nb::device::cpu>(
193 reinterpret_cast<uint32_t*
>(self.
data.data()),
197 nb::rv_policy::reference_internal);
198 case AttributeValueType::e_int64_t:
200 nb::ndarray<int64_t, nb::numpy, nb::c_contig, nb::device::cpu>(
201 reinterpret_cast<int64_t*
>(self.
data.data()),
205 nb::rv_policy::reference_internal);
206 case AttributeValueType::e_uint64_t:
208 nb::ndarray<uint64_t, nb::numpy, nb::c_contig, nb::device::cpu>(
209 reinterpret_cast<uint64_t*
>(self.
data.data()),
213 nb::rv_policy::reference_internal);
214 case AttributeValueType::e_float:
216 nb::ndarray<float, nb::numpy, nb::c_contig, nb::device::cpu>(
217 reinterpret_cast<float*
>(self.
data.data()),
221 nb::rv_policy::reference_internal);
222 case AttributeValueType::e_double:
224 nb::ndarray<double, nb::numpy, nb::c_contig, nb::device::cpu>(
225 reinterpret_cast<double*
>(self.
data.data()),
229 nb::rv_policy::reference_internal);
230 default:
throw nb::type_error(
"Unsupported image buffer `dtype`!");
234 nb::ndarray<nb::numpy, nb::c_contig, nb::device::cpu> tensor) {
236 self.
width = tensor.shape(1);
237 self.
height = tensor.shape(0);
239 auto dtype = tensor.dtype();
240 if (dtype == nb::dtype<int8_t>()) {
242 }
else if (dtype == nb::dtype<uint8_t>()) {
244 }
else if (dtype == nb::dtype<int16_t>()) {
246 }
else if (dtype == nb::dtype<uint16_t>()) {
248 }
else if (dtype == nb::dtype<int32_t>()) {
250 }
else if (dtype == nb::dtype<uint32_t>()) {
252 }
else if (dtype == nb::dtype<int64_t>()) {
254 }
else if (dtype == nb::dtype<uint64_t>()) {
256 }
else if (dtype == nb::dtype<float>()) {
258 }
else if (dtype == nb::dtype<double>()) {
261 throw nb::type_error(
"Unsupported input tensor `dtype`!");
263 self.
data.resize(tensor.nbytes());
265 reinterpret_cast<uint8_t*
>(tensor.data()),
266 reinterpret_cast<uint8_t*
>(tensor.data()) + tensor.nbytes(),
273 auto np = nb::module_::import_(
"numpy");
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;
288 "The element data type of the image buffer.");
290 nb::class_<ImageExperimental> image(m,
"Image");
291 image.def(nb::init<>())
295 .def_rw(
"name", &ImageExperimental::name,
"Name of the image object")
296 .def_rw(
"image", &ImageExperimental::image,
"Image buffer")
300 if (self.
uri.empty())
303 return self.
uri.string();
307 self.
uri = fs::path(uri.value());
309 self.
uri = fs::path();
311 "URI of the image file")
314 &ImageExperimental::extensions,
315 "Additional data associated with the image");
317 nb::class_<TextureInfo>(m,
"TextureInfo")
319 .def(
"__repr__", [](
const TextureInfo& self) {
return scene::internal::to_string(self); })
322 [](
const TextureInfo& self) -> std::optional<ElementId> {
323 if (self.
index != invalid_element)
328 [](
TextureInfo& self, std::optional<ElementId> index) {
329 if (index.has_value())
330 self.
index = index.value();
332 self.
index = invalid_element;
334 "Texture index in scene.textures vector. `None` if not set.")
335 .def_rw(
"texcoord", &TextureInfo::texcoord);
337 nb::class_<MaterialExperimental> material(m,
"Material");
338 material.def(nb::init<>())
342 .def_rw(
"name", &MaterialExperimental::name)
343 .def_rw(
"base_color_value", &MaterialExperimental::base_color_value)
344 .def_rw(
"base_color_texture", &MaterialExperimental::base_color_texture)
345 .def_rw(
"alpha_mode", &MaterialExperimental::alpha_mode)
346 .def_rw(
"alpha_cutoff", &MaterialExperimental::alpha_cutoff)
347 .def_rw(
"emissive_value", &MaterialExperimental::emissive_value)
348 .def_rw(
"emissive_texture", &MaterialExperimental::emissive_texture)
349 .def_rw(
"metallic_value", &MaterialExperimental::metallic_value)
350 .def_rw(
"roughness_value", &MaterialExperimental::roughness_value)
351 .def_rw(
"metallic_roughness_texture", &MaterialExperimental::metallic_roughness_texture)
352 .def_rw(
"normal_texture", &MaterialExperimental::normal_texture)
353 .def_rw(
"normal_scale", &MaterialExperimental::normal_scale)
354 .def_rw(
"occlusion_texture", &MaterialExperimental::occlusion_texture)
355 .def_rw(
"occlusion_strength", &MaterialExperimental::occlusion_strength)
356 .def_rw(
"double_sided", &MaterialExperimental::double_sided)
357 .def_rw(
"extensions", &MaterialExperimental::extensions);
359 nb::enum_<MaterialExperimental::AlphaMode>(material,
"AlphaMode",
"Alpha mode")
360 .value(
"Opaque", MaterialExperimental::AlphaMode::Opaque)
361 .value(
"Mask", MaterialExperimental::AlphaMode::Mask)
362 .value(
"Blend", MaterialExperimental::AlphaMode::Blend);
365 nb::class_<Texture> texture(m,
"Texture",
"Texture");
366 texture.def(nb::init<>())
367 .def(
"__repr__", [](
const Texture& self) {
return scene::internal::to_string(self); })
368 .def_rw(
"name", &Texture::name)
371 [](
Texture& self) -> std::optional<ElementId> {
372 if (self.image != invalid_element)
377 [](
Texture& self, ElementId img) { self.image = img; },
378 "Texture image index in scene.images vector (None if invalid)")
379 .def_rw(
"mag_filter", &Texture::mag_filter)
380 .def_rw(
"min_filter", &Texture::min_filter)
381 .def_rw(
"wrap_u", &Texture::wrap_u)
382 .def_rw(
"wrap_v", &Texture::wrap_v)
383 .def_rw(
"scale", &Texture::scale)
384 .def_rw(
"offset", &Texture::offset)
385 .def_rw(
"rotation", &Texture::rotation)
386 .def_rw(
"extensions", &Texture::extensions);
388 nb::enum_<Texture::WrapMode>(texture,
"WrapMode",
"Texture wrap mode")
389 .value(
"Wrap", Texture::WrapMode::Wrap)
390 .value(
"Clamp", Texture::WrapMode::Clamp)
391 .value(
"Decal", Texture::WrapMode::Decal)
392 .value(
"Mirror", Texture::WrapMode::Mirror);
393 nb::enum_<Texture::TextureFilter>(texture,
"TextureFilter",
"Texture filter mode")
394 .value(
"Undefined", Texture::TextureFilter::Undefined)
395 .value(
"Nearest", Texture::TextureFilter::Nearest)
396 .value(
"Linear", Texture::TextureFilter::Linear)
397 .value(
"NearestMipmapNearest", Texture::TextureFilter::NearestMipmapNearest)
398 .value(
"LinearMipmapNearest", Texture::TextureFilter::LinearMipmapNearest)
399 .value(
"NearestMipmapLinear", Texture::TextureFilter::NearestMipmapLinear)
400 .value(
"LinearMipmapLinear", Texture::TextureFilter::LinearMipmapLinear);
402 nb::class_<Light> light(m,
"Light",
"Light");
403 light.def(nb::init<>())
404 .def(
"__repr__", [](
const Light& self) {
return scene::internal::to_string(self); })
405 .def_rw(
"name", &Light::name)
406 .def_rw(
"type", &Light::type)
407 .def_rw(
"position", &Light::position)
408 .def_rw(
"direction", &Light::direction)
409 .def_rw(
"up", &Light::up)
410 .def_rw(
"intensity", &Light::intensity)
411 .def_rw(
"attenuation_constant", &Light::attenuation_constant)
412 .def_rw(
"attenuation_linear", &Light::attenuation_linear)
413 .def_rw(
"attenuation_quadratic", &Light::attenuation_quadratic)
414 .def_rw(
"attenuation_cubic", &Light::attenuation_cubic)
416 .def_rw(
"color_diffuse", &Light::color_diffuse)
417 .def_rw(
"color_specular", &Light::color_specular)
418 .def_rw(
"color_ambient", &Light::color_ambient)
419 .def_rw(
"angle_inner_cone", &Light::angle_inner_cone)
420 .def_rw(
"angle_outer_cone", &Light::angle_outer_cone)
421 .def_rw(
"size", &Light::size)
422 .def_rw(
"extensions", &Light::extensions);
424 nb::enum_<Light::Type>(light,
"Type",
"Light type")
425 .value(
"Undefined", Light::Type::Undefined)
426 .value(
"Directional", Light::Type::Directional)
427 .value(
"Point", Light::Type::Point)
428 .value(
"Spot", Light::Type::Spot)
429 .value(
"Ambient", Light::Type::Ambient)
430 .value(
"Area", Light::Type::Area);
432 nb::class_<Camera> camera(m,
"Camera",
"Camera");
433 camera.def(nb::init<>())
434 .def(
"__repr__", [](
const Camera& self) {
return scene::internal::to_string(self); })
435 .def_rw(
"name", &Camera::name)
436 .def_rw(
"position", &Camera::position)
437 .def_rw(
"up", &Camera::up)
438 .def_rw(
"look_at", &Camera::look_at)
439 .def_rw(
"near_plane", &Camera::near_plane)
440 .def_rw(
"far_plane", &Camera::far_plane)
441 .def_rw(
"type", &Camera::type)
442 .def_rw(
"aspect_ratio", &Camera::aspect_ratio)
443 .def_rw(
"horizontal_fov", &Camera::horizontal_fov)
444 .def_rw(
"orthographic_width", &Camera::orthographic_width)
445 .def_prop_ro(
"get_vertical_fov", &Camera::get_vertical_fov)
447 "set_horizontal_fov_from_vertical_fov",
448 &Camera::set_horizontal_fov_from_vertical_fov,
450 .def_rw(
"extensions", &Camera::extensions);
452 nb::enum_<Camera::Type>(camera,
"Type",
"Camera type")
453 .value(
"Perspective", Camera::Type::Perspective)
454 .value(
"Orthographic", Camera::Type::Orthographic);
456 nb::class_<Animation>(m,
"Animation",
"")
458 .def(
"__repr__", [](
const Animation& self) {
return scene::internal::to_string(self); })
459 .def_rw(
"name", &Animation::name)
460 .def_rw(
"extensions", &Animation::extensions);
463 nb::class_<Skeleton>(m,
"Skeleton",
"")
465 .def(
"__repr__", [](
const Skeleton& self) {
return scene::internal::to_string(self); })
466 .def_rw(
"meshes", &Skeleton::meshes)
467 .def_rw(
"extensions", &Skeleton::extensions);
470 nb::class_<SceneType>(m,
"Scene",
"A 3D scene")
474 [](
const SceneType& self) {
return scene::internal::to_string(self); })
475 .def_rw(
"name", &SceneType::name)
476 .def_rw(
"nodes", &SceneType::nodes)
477 .def_rw(
"root_nodes", &SceneType::root_nodes)
478 .def_rw(
"meshes", &SceneType::meshes)
479 .def_rw(
"images", &SceneType::images)
480 .def_rw(
"textures", &SceneType::textures)
481 .def_rw(
"materials", &SceneType::materials)
482 .def_rw(
"lights", &SceneType::lights)
483 .def_rw(
"cameras", &SceneType::cameras)
484 .def_rw(
"skeletons", &SceneType::skeletons)
485 .def_rw(
"animations", &SceneType::animations)
486 .def_rw(
"extensions", &SceneType::extensions)
502 using T = std::decay_t<
decltype(value)>;
503 return self.add(std::forward<T>(value));
508 R
"(Add an element to the scene.
510:param element: The element to add to the scene. E.g. node, mesh, image, texture, material, light, camera, skeleton, or animation.
512:returns: The id of the added element.)")
515 &SceneType::add_child,
518 R
"(Add a child node to a parent node.
520:param parent_id: The parent node id.
521:param child_id: The child node id.
523:returns: The id of the added child node.)");
526 "compute_global_node_transform",
527 [](
const SceneType& scene,
size_t node_idx) {
528 auto t = utils::compute_global_node_transform<Scalar, Index>(scene, node_idx);
529 return nb::ndarray<nb::numpy, float, nb::f_contig, nb::shape<4, 4>>(
538 R
"(Compute the global transform associated with a node.
540:param scene: The input node.
541:param node_idx: The index of the target node.
543:returns: The global transform of the target node, which is the combination of transforms from this node all the way to the root.
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition: Logger.cpp:40
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:169
internal::Range< Index > range(Index end)
Returns an iterable object representing the range [0, end).
Definition: range.h:176
Definition: SceneExtension.h:192
Minimalistic image data structure that stores the raw image data.
Definition: Scene.h:87
size_t height
Image height.
Definition: Scene.h:92
size_t width
Image width.
Definition: Scene.h:89
AttributeValueType element_type
The scalar type of the elements in the buffer.
Definition: Scene.h:98
std::vector< unsigned char > data
Raw buffer of size (width * height * num_channels * num_bits_per_element / 8) bytes containing image ...
Definition: Scene.h:101
size_t num_channels
Number of image channels (must be 1, 3, or 4).
Definition: Scene.h:95
Image structure that can store either image data or reference to an image file.
Definition: Scene.h:113
fs::path uri
Image file path.
Definition: Scene.h:122
ElementId index
Texture index. Index in scene.textures vector.
Definition: Scene.h:134