Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tsd/scripts/examples/render_scene.lua
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ for frame = 0, numFrames - 1 do
local angle = tsd.radians(frame * 360.0 / numFrames)
local rot = tsd.rotation(tsd.float3(0, 1, 0), angle)
rootXfm:setAsTransform(rot)
scene:signalLayerChange(layer)
scene:signalLayerTransformChanged(layer)

local filename = string.format("%s_%04d.png", outPrefix, frame)
tsd.render.renderToFile(pipeline, samples, filename, width, height)
Expand Down
10 changes: 5 additions & 5 deletions tsd/scripts/test_lua_api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1594,12 +1594,12 @@ section("ForEach (Extended)")

test("scene:forEachCamera", function()
local s = tsd.createScene()
local before = 0
s:forEachCamera(function(_) before = before + 1 end)
s:createCamera("perspective")
local count = 0
s:forEachCamera(function(c)
count = count + 1
end)
assert(count == 1, "should iterate over 1 camera")
local after = 0
s:forEachCamera(function(_) after = after + 1 end)
assert(after == before + 1, "forEachCamera should yield one more camera after create")
end)

test("scene:forEachVolume", function()
Expand Down
23 changes: 23 additions & 0 deletions tsd/src/tsd/scripting/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,36 @@ project_sources(
PRIVATE
LuaContext.cpp
LuaBindings.cpp
bindings/ArrayHelpers.cpp
bindings/ObjectMethodImpls.cpp
bindings/ObjectUsertype.cpp
bindings/AnimationBindings.cpp
bindings/CoreBindings.cpp
bindings/scene/Accessors.cpp
bindings/scene/Creators.cpp
bindings/scene/Iteration.cpp
bindings/scene/Layers.cpp
bindings/scene/Nodes.cpp
bindings/MathBindings.cpp
bindings/math/Float2.cpp
bindings/math/Float3.cpp
bindings/math/Float4.cpp
bindings/math/Mat3.cpp
bindings/math/Mat4.cpp
bindings/ObjectBindings.cpp
bindings/LayerBindings.cpp
bindings/IOBindings.cpp
bindings/RenderBindings.cpp
bindings/ParameterHelpers.cpp
bindings/refs/ArrayRef.cpp
bindings/refs/CameraRef.cpp
bindings/refs/GeometryRef.cpp
bindings/refs/LightRef.cpp
bindings/refs/MaterialRef.cpp
bindings/refs/SamplerRef.cpp
bindings/refs/SpatialFieldRef.cpp
bindings/refs/SurfaceRef.cpp
bindings/refs/VolumeRef.cpp
)

project_include_directories(
Expand Down
12 changes: 12 additions & 0 deletions tsd/src/tsd/scripting/LuaContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ LuaContext::LuaContext() : m_impl(std::make_unique<Impl>())
sol::lib::io,
sol::lib::utf8);

// Translate C++ exceptions thrown from bindings into Lua errors. Without
// this, sol2's default handler pushes a string and lets lua_error longjmp
// over live C++ destructors — undefined behavior that segfaults in practice.
m_impl->lua.set_exception_handler(
[](lua_State *L,
sol::optional<const std::exception &> maybe,
sol::string_view desc) {
const std::string msg =
maybe ? maybe->what() : std::string(desc);
return luaL_error(L, "%s", msg.c_str());
});

m_impl->lua.set_function("print", [this](sol::variadic_args va) {
fmt::memory_buffer buf;
bool first = true;
Expand Down
251 changes: 251 additions & 0 deletions tsd/src/tsd/scripting/bindings/AnimationBindings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// Copyright 2026 NVIDIA Corporation
// SPDX-License-Identifier: Apache-2.0

// Animation and AnimationManager sol2 usertypes. Lives in its own TU
// because each `new_usertype<>()` call is a heavy sol2 instantiation,
// and CoreBindings.cpp would otherwise be the build's critical path.

#include "AnimationBindings.hpp"
#include "ArrayHelpers.hpp"
#include "tsd/animation/Animation.hpp"
#include "tsd/animation/AnimationManager.hpp"
#include "tsd/core/Token.hpp"
#include "tsd/scene/Object.hpp"
#include "tsd/scripting/LuaBindings.hpp"

#include <sol/sol.hpp>

#include <algorithm>
#include <stdexcept>
#include <vector>

namespace tsd::scripting {

namespace {

std::vector<float> tableToFloats(sol::table t)
{
std::vector<float> v(t.size());
for (size_t i = 0; i < v.size(); i++)
v[i] = t[i + 1].get<float>();
return v;
}

template <typename Vec, size_t N>
std::vector<Vec> tableToVecs(sol::table t, const char *typeName)
{
std::vector<Vec> v(t.size());
for (size_t i = 0; i < v.size(); i++) {
sol::object o = t[i + 1];
if (o.is<Vec>()) {
v[i] = o.as<Vec>();
} else if (o.is<sol::table>()) {
sol::table sub = o.as<sol::table>();
if (sub.size() != N)
throw std::runtime_error(
std::string("expected ") + typeName + " or table of "
+ std::to_string(N) + " numbers");
if constexpr (N == 2)
v[i] = Vec(sub[1].get<float>(), sub[2].get<float>());
else if constexpr (N == 3)
v[i] = Vec(
sub[1].get<float>(), sub[2].get<float>(), sub[3].get<float>());
else if constexpr (N == 4)
v[i] = Vec(sub[1].get<float>(),
sub[2].get<float>(),
sub[3].get<float>(),
sub[4].get<float>());
} else {
throw std::runtime_error(
std::string("expected ") + typeName + " or table of "
+ std::to_string(N) + " numbers");
}
}
return v;
}

} // namespace

void registerAnimationBindings(sol::table &tsd)
{
tsd.new_usertype<tsd::animation::Animation>(
"Animation",
sol::no_constructor,
"name",
sol::property(
[](const tsd::animation::Animation &a) { return a.name(); },
[](tsd::animation::Animation &a, const std::string &n) {
a.editableName() = n;
}),
"addObjectParameterBinding",
[](tsd::animation::Animation &a,
sol::object target,
const std::string &param,
const std::string &typeStr,
sol::table dataTable,
sol::table timeBaseTable,
sol::optional<std::string> interpStr) {
auto *obj = extractObjectPtr(target);
if (!obj)
throw std::runtime_error(
"addObjectParameterBinding: invalid target object");

auto interp = tsd::animation::InterpolationRule::LINEAR;
if (interpStr && *interpStr == "step")
interp = tsd::animation::InterpolationRule::STEP;
else if (interpStr && *interpStr == "slerp")
interp = tsd::animation::InterpolationRule::SLERP;

auto dataType = arrayTypeFromString(typeStr);
auto tb = tableToFloats(timeBaseTable);
size_t count = std::min<size_t>(dataTable.size(), tb.size());

if (anari::isObject(dataType)) {
std::vector<scene::Object *> ptrs(count);
for (size_t i = 0; i < count; i++) {
ptrs[i] = extractObjectPtr(dataTable[i + 1]);
if (!ptrs[i])
throw std::runtime_error(
"addObjectParameterBinding: invalid object at index "
+ std::to_string(i));
}
a.addObjectParameterBinding(
obj, core::Token(param), dataType, ptrs.data(), tb.data(),
count, interp);
} else {
// Value types — decode into a typed vector, then pass as void*
auto addValues = [&](auto *typed, auto vec) {
(void)typed;
a.addObjectParameterBinding(
obj, core::Token(param), dataType, vec.data(), tb.data(),
count, interp);
};

switch (dataType) {
case ANARI_FLOAT32:
addValues((float *)nullptr, tableToFloats(dataTable));
break;
case ANARI_FLOAT32_VEC2:
addValues((math::float2 *)nullptr,
tableToVecs<math::float2, 2>(dataTable, "float2"));
break;
case ANARI_FLOAT32_VEC3:
addValues((math::float3 *)nullptr,
tableToVecs<math::float3, 3>(dataTable, "float3"));
break;
case ANARI_FLOAT32_VEC4:
addValues((math::float4 *)nullptr,
tableToVecs<math::float4, 4>(dataTable, "float4"));
break;
case ANARI_FLOAT32_MAT4: {
std::vector<math::mat4> v(count);
for (size_t i = 0; i < count; i++) {
sol::object o = dataTable[i + 1];
if (o.is<math::mat4>())
v[i] = o.as<math::mat4>();
else
throw std::runtime_error(
"addObjectParameterBinding: expected mat4 at index "
+ std::to_string(i));
}
a.addObjectParameterBinding(
obj, core::Token(param), dataType, v.data(), tb.data(),
count, interp);
break;
}
case ANARI_INT32: {
std::vector<int32_t> v(count);
for (size_t i = 0; i < count; i++)
v[i] = dataTable[i + 1].get<int32_t>();
a.addObjectParameterBinding(
obj, core::Token(param), dataType, v.data(), tb.data(),
count, interp);
break;
}
case ANARI_UINT32: {
std::vector<uint32_t> v(count);
for (size_t i = 0; i < count; i++)
v[i] = dataTable[i + 1].get<uint32_t>();
a.addObjectParameterBinding(
obj, core::Token(param), dataType, v.data(), tb.data(),
count, interp);
break;
}
default:
throw std::runtime_error(
"addObjectParameterBinding: unsupported data type '"
+ typeStr + "'");
}
}
},
"addTransformBinding",
[](tsd::animation::Animation &a,
scene::LayerNodeRef node,
sol::table timeBaseTable,
sol::table rotTable,
sol::table transTable,
sol::table scaleTable) {
if (!node.valid())
throw std::runtime_error("addTransformBinding: node must be valid");
auto tb = tableToFloats(timeBaseTable);
auto rot = tableToVecs<math::float4, 4>(rotTable, "float4");
auto trans = tableToVecs<math::float3, 3>(transTable, "float3");
auto scale = tableToVecs<math::float3, 3>(scaleTable, "float3");
a.addTransformBinding(
node, tb.data(), rot.data(), trans.data(), scale.data(), tb.size());
});
}

void registerAnimationManagerBindings(sol::state &lua)
{
using SA = tsd::animation::AnimationManager;
sol::table tsd = lua["tsd"];

tsd.new_usertype<SA>(
"AnimationManager",
sol::no_constructor,
"addAnimation",
sol::overload(
[](SA &sa) -> tsd::animation::Animation & {
return sa.addAnimation();
},
[](SA &sa, const std::string &name) -> tsd::animation::Animation & {
return sa.addAnimation(name);
}),
"animations",
[](SA &sa) -> std::vector<tsd::animation::Animation> & {
return sa.animations();
},
"numberOfAnimations",
[](SA &sa) -> size_t { return sa.animations().size(); },
"removeAnimation",
&SA::removeAnimation,
"removeAllAnimations",
&SA::removeAllAnimations,
"setAnimationTime",
&SA::setAnimationTime,
"getAnimationTime",
&SA::getAnimationTime,
"setAnimationIncrement",
&SA::setAnimationIncrement,
"getAnimationIncrement",
&SA::getAnimationIncrement,
"incrementAnimationTime",
&SA::incrementAnimationTime,
"getAnimationTotalFrames",
&SA::getAnimationTotalFrames,
"setAnimationTotalFrames",
&SA::setAnimationTotalFrames,
"getAnimationFPS",
&SA::getAnimationFPS,
"setAnimationFPS",
&SA::setAnimationFPS,
"getAnimationFrame",
&SA::getAnimationFrame,
"setAnimationFrame",
&SA::setAnimationFrame,
"incrementAnimationFrame",
&SA::incrementAnimationFrame);
}

} // namespace tsd::scripting
12 changes: 12 additions & 0 deletions tsd/src/tsd/scripting/bindings/AnimationBindings.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2026 NVIDIA Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <sol/sol.hpp>

namespace tsd::scripting {

void registerAnimationBindings(sol::table &tsd);

} // namespace tsd::scripting
Loading
Loading