From 8940d72e33af96c40e9c6e94c0e330537ec3d165 Mon Sep 17 00:00:00 2001 From: Yuting Ye Date: Sat, 15 Nov 2025 14:49:08 -0800 Subject: [PATCH 1/3] Add parameterized constructor for FileSaveOptions Python binding (#825) Summary: This change adds a parameterized constructor to the FileSaveOptions Python binding, enabling users to initialize all member variables directly when creating the object instead of having to set them one by one after construction. This improves the API ergonomics by allowing cleaner initialization patterns: ```python options = FileSaveOptions(mesh=False, locators=True, blend_shapes=False) ``` Reviewed By: cstollmeta, jeongseok-meta Differential Revision: D86904749 --- pymomentum/geometry/geometry_pybind.cpp | 61 +++++++++++++++++---- pymomentum/geometry/gltf_builder_pybind.cpp | 7 ++- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/pymomentum/geometry/geometry_pybind.cpp b/pymomentum/geometry/geometry_pybind.cpp index 39a938e469..89c6d36a95 100644 --- a/pymomentum/geometry/geometry_pybind.cpp +++ b/pymomentum/geometry/geometry_pybind.cpp @@ -204,7 +204,7 @@ PYBIND11_MODULE(geometry, m) { py::arg("collisions") = true, py::arg("locators") = true, py::arg("mesh") = true, - py::arg("blendShapes") = true) + py::arg("blend_shapes") = true) .def( "__repr__", [](const mm::GltfOptions& self) { @@ -493,19 +493,19 @@ The resulting tensors are as follows: const momentum::FbxCoordSystem coordSystem) { return momentum::FbxCoordSystemInfo{upVector, frontVector, coordSystem}; }), - py::arg("upVector"), - py::arg("frontVector"), - py::arg("coordSystem")) + py::arg("up_vector"), + py::arg("front_vector"), + py::arg("coord_system")) .def_property_readonly( - "upVector", + "up_vector", [](const mm::FbxCoordSystemInfo& coordSystemInfo) { return coordSystemInfo.upVector; }, "Returns the up vector.") .def_property_readonly( - "frontVector", + "front_vector", [](const mm::FbxCoordSystemInfo& coordSystemInfo) { return coordSystemInfo.frontVector; }, "Returns the front vector.") .def_property_readonly( - "coordSystem", + "coord_system", [](const mm::FbxCoordSystemInfo& coordSystemInfo) { return coordSystemInfo.coordSystem; }, "Returns the coordinate system.") .def("__repr__", [](const mm::FbxCoordSystemInfo& info) { @@ -565,7 +565,48 @@ The resulting tensors are as follows: // - GLTF-specific: extensions, gltfFileFormat // ===================================================== - fileSaveOptionsClass.def(py::init<>()) + fileSaveOptionsClass + .def( + py::init([](bool mesh, + bool locators, + bool collisions, + bool blendShapes, + bool permissive, + mm::FbxCoordSystemInfo coordSystemInfo, + std::string_view fbxNamespace, + bool extensions, + mm::GltfFileFormat gltfFileFormat) { + mm::FileSaveOptions options; + options.mesh = mesh; + options.locators = locators; + options.collisions = collisions; + options.blendShapes = blendShapes; + options.permissive = permissive; + options.coordSystemInfo = coordSystemInfo; + options.fbxNamespace = fbxNamespace; + options.extensions = extensions; + options.gltfFileFormat = gltfFileFormat; + return options; + }), + py::arg("mesh") = true, + py::arg("locators") = true, + py::arg("collisions") = true, + py::arg("blend_shapes") = true, + py::arg("permissive") = false, + py::arg("coord_system_info") = mm::FbxCoordSystemInfo{}, + py::arg("fbx_namespace") = "", + py::arg("extensions") = true, + py::arg("gltf_file_format") = mm::GltfFileFormat::Auto, + "Create FileSaveOptions with custom settings.\n\n" + ":param mesh: Include mesh geometry in the output (default: True)\n" + ":param locators: Include locators in the output (default: True)\n" + ":param collisions: Include collision geometry in the output (default: True)\n" + ":param blend_shapes: Include blend shapes in the output (default: True)\n" + ":param permissive: Permissive mode - allow saving mesh-only characters without skin weights (default: False)\n" + ":param coord_system_info: FBX coordinate system configuration (default: Maya Y-up)\n" + ":param fbx_namespace: Optional namespace prefix for FBX node names (default: empty)\n" + ":param extensions: Enable GLTF extensions (default: True)\n" + ":param gltf_file_format: GLTF file format selection (default: Auto)\n") .def_readwrite( "mesh", &mm::FileSaveOptions::mesh, "Include mesh geometry in the output (default: true)") .def_readwrite( @@ -667,8 +708,8 @@ The resulting tensors are as follows: coordSystemStr); return fmt::format( - "FileSaveOptions(mesh={}, locators={}, collisions={}, blendShapes={}, permissive={}, " - "coord_system_info={}, fbx_namespace='{}', extensions={}, gltfFileFormat={})", + "FileSaveOptions(mesh={}, locators={}, collisions={}, blend_shapes={}, permissive={}, " + "coord_system_info={}, fbx_namespace='{}', extensions={}, gltf_file_format={})", opts.mesh, opts.locators, opts.collisions, diff --git a/pymomentum/geometry/gltf_builder_pybind.cpp b/pymomentum/geometry/gltf_builder_pybind.cpp index 20fc8ceca5..24c6897884 100644 --- a/pymomentum/geometry/gltf_builder_pybind.cpp +++ b/pymomentum/geometry/gltf_builder_pybind.cpp @@ -82,11 +82,12 @@ Setting this value will affect subsequently added motions and animations. const mm::Character& character, const std::optional& positionOffset, const std::optional& rotationOffset, - const mm::GltfOptions& options) { + const std::optional& options) { // Use defaults if not provided Eigen::Vector3f actualPositionOffset = positionOffset.value_or(Eigen::Vector3f::Zero()); Eigen::Vector4f actualRotationOffset = rotationOffset.value_or(Eigen::Vector4f(0.0f, 0.0f, 0.0f, 1.0f)); + mm::GltfOptions actualOptions = options.value_or(mm::GltfOptions{}); // Convert Vector4f (x,y,z,w) to Quaternionf (w,x,y,z) mm::Quaternionf quaternionOffset( @@ -95,7 +96,7 @@ Setting this value will affect subsequently added motions and animations. actualRotationOffset[1], // y actualRotationOffset[2]); // z - builder.addCharacter(character, actualPositionOffset, quaternionOffset, options); + builder.addCharacter(character, actualPositionOffset, quaternionOffset, actualOptions); }, R"(Add a character to the GLTF scene. @@ -113,7 +114,7 @@ can be provided as an initial transform for the character. py::arg("character"), py::arg("position_offset") = std::nullopt, py::arg("rotation_offset") = std::nullopt, - py::arg("options") = mm::GltfOptions{}) + py::arg("options") = std::nullopt) .def( "add_mesh", &mm::GltfBuilder::addMesh, From 77c5b775fa9ee5ae62d7fb77f6b3ccb583fcef60 Mon Sep 17 00:00:00 2001 From: Yuting Ye Date: Sat, 15 Nov 2025 14:49:08 -0800 Subject: [PATCH 2/3] Replace GltfOptions with FileSaveOptions in saveGltfCharacter APIs (#826) Summary: This diff replaces the `GltfOptions` parameter with `FileSaveOptions` in the `saveGltfCharacter` function signatures to provide a unified interface for file saving options across different formats (GLTF and FBX). Reviewed By: cstollmeta, jeongseok-meta Differential Revision: D86882964 --- momentum/io/character_io.cpp | 28 ++----------------- momentum/io/file_save_options.h | 16 +---------- momentum/io/gltf/gltf_builder.cpp | 2 +- momentum/io/gltf/gltf_builder.h | 2 +- momentum/io/gltf/gltf_io.cpp | 30 +++++++++------------ momentum/io/gltf/gltf_io.h | 14 +++++----- pymomentum/geometry/character_pybind.cpp | 6 +++-- pymomentum/geometry/geometry_pybind.cpp | 27 ------------------- pymomentum/geometry/gltf_builder_pybind.cpp | 6 ++--- pymomentum/geometry/momentum_io.cpp | 22 +++++++-------- pymomentum/geometry/momentum_io.h | 8 +++--- 11 files changed, 47 insertions(+), 114 deletions(-) diff --git a/momentum/io/character_io.cpp b/momentum/io/character_io.cpp index 80ec093efb..f30cf00d4e 100644 --- a/momentum/io/character_io.cpp +++ b/momentum/io/character_io.cpp @@ -155,14 +155,6 @@ void saveCharacter( filename.string()); if (format == CharacterFormat::Gltf) { - // Convert FileSaveOptions to GltfOptions - GltfOptions gltfOptions; - gltfOptions.extensions = options.extensions; - gltfOptions.collisions = options.collisions; - gltfOptions.locators = options.locators; - gltfOptions.mesh = options.mesh; - gltfOptions.blendShapes = options.blendShapes; - saveGltfCharacter( filename, character, @@ -170,8 +162,7 @@ void saveCharacter( {character.parameterTransform.name, motion}, {}, markerSequence, - options.gltfFileFormat, - gltfOptions); + options); } else if (format == CharacterFormat::Fbx) { // Save as FBX saveFbx( @@ -206,22 +197,7 @@ void saveCharacter( filename.string()); if (format == CharacterFormat::Gltf) { - // Convert FileSaveOptions to GltfOptions - GltfOptions gltfOptions; - gltfOptions.extensions = options.extensions; - gltfOptions.collisions = options.collisions; - gltfOptions.locators = options.locators; - gltfOptions.mesh = options.mesh; - gltfOptions.blendShapes = options.blendShapes; - - saveGltfCharacter( - filename, - character, - fps, - skeletonStates, - markerSequence, - options.gltfFileFormat, - gltfOptions); + saveGltfCharacter(filename, character, fps, skeletonStates, markerSequence, options); } else if (format == CharacterFormat::Fbx) { // Save as FBX saveFbxWithSkeletonStates( diff --git a/momentum/io/file_save_options.h b/momentum/io/file_save_options.h index 13c7ff26f1..075cec453b 100644 --- a/momentum/io/file_save_options.h +++ b/momentum/io/file_save_options.h @@ -22,23 +22,9 @@ enum class GltfFileFormat { Ascii = 2, // ASCII format (generally .gltf) }; -/// Options for GLTF file export -struct GltfOptions { - /// Include GLTF extensions in the output. - bool extensions = true; - /// Include collision geometry in the output. - bool collisions = true; - /// Include locators in the output. - bool locators = true; - /// Include mesh geometry in the output. - bool mesh = true; - /// Include blend shapes in the output. - bool blendShapes = true; -}; - // ============================================================================ // FBX Coordinate System Options -// ===================================================================== +// ============================================================================ /// Specifies which canonical axis represents up in the system (typically Y or Z). /// Maps to fbxsdk::FbxAxisSystem::EUpVector diff --git a/momentum/io/gltf/gltf_builder.cpp b/momentum/io/gltf/gltf_builder.cpp index cd7213e6e9..f71c1d33aa 100644 --- a/momentum/io/gltf/gltf_builder.cpp +++ b/momentum/io/gltf/gltf_builder.cpp @@ -855,7 +855,7 @@ void GltfBuilder::addCharacter( const Character& character, const Vector3f& positionOffset /*= Vector3f::Zero()*/, const Quaternionf& rotationOffset /*= Quaternionf::Identity()*/, - const GltfOptions& options) { + const FileSaveOptions& options) { if (impl_->characterData.find(character.name) != impl_->characterData.end()) { // Character already exist. Doesn't allow character with the same name to be saved. // #TODO: proper warning diff --git a/momentum/io/gltf/gltf_builder.h b/momentum/io/gltf/gltf_builder.h index 016915bb79..ba42512cda 100644 --- a/momentum/io/gltf/gltf_builder.h +++ b/momentum/io/gltf/gltf_builder.h @@ -50,7 +50,7 @@ class GltfBuilder final { const Character& character, const Vector3f& positionOffset = Vector3f::Zero(), const Quaternionf& rotationOffset = Quaternionf::Identity(), - const GltfOptions& options = GltfOptions()); + const FileSaveOptions& options = FileSaveOptions()); /// Add a static mesh, such as an environment or a target scan void addMesh(const Mesh& mesh, const std::string& name, bool addColor = false); diff --git a/momentum/io/gltf/gltf_io.cpp b/momentum/io/gltf/gltf_io.cpp index 5aab4578bc..848d666435 100644 --- a/momentum/io/gltf/gltf_io.cpp +++ b/momentum/io/gltf/gltf_io.cpp @@ -438,7 +438,7 @@ addMesh(const fx::gltf::Document& model, const fx::gltf::Primitive& primitive, M auto fidxDense = copyAccessorBuffer(model, extension.at("texfaces")); MT_CHECK(fidxDense.size() % 3 == 0, "{} % 3 = {}", fidxDense.size(), fidxDense.size() % 3); texfaces.resize(fidxDense.size() / 3); - if (fidxDense.size() > 0) { + if (!fidxDense.empty()) { std::copy_n(fidxDense.data(), fidxDense.size(), &texfaces[0][0]); } } else { @@ -991,7 +991,7 @@ fx::gltf::Document makeCharacterDocument( std::span skeletonStates, std::span> markerSequence, bool embedResource, - const GltfOptions& options) { + const FileSaveOptions& options) { GltfBuilder fileBuilder; const auto kCharacterIsEmpty = character.skeleton.joints.empty() && character.mesh == nullptr; @@ -1226,7 +1226,7 @@ fx::gltf::Document makeCharacterDocument( const IdentityParameters& offsets, std::span> markerSequence, bool embedResource, - const GltfOptions& options) { + const FileSaveOptions& options) { GltfBuilder fileBuilder; const auto kCharacterIsEmpty = character.skeleton.joints.empty() && character.mesh == nullptr; if (!kCharacterIsEmpty) { @@ -1293,14 +1293,14 @@ MarkerSequence loadMarkerSequence(const filesystem::path& filename) { positions.size()); // resize the output array if necessary - const size_t length = static_cast(timestamps.back() * fps + 0.5f) + 1; + const size_t length = static_cast(std::lround(timestamps.back() * fps)) + 1; if (length > result.frames.size()) { result.frames.resize(length); } // go over all data and enter into the output array for (size_t i = 0; i < timestamps.size(); i++) { - const size_t index = static_cast(timestamps[i] * fps + 0.5f); + const size_t index = static_cast(std::lround(timestamps[i] * fps)); result.frames[index].emplace_back(); auto& marker = result.frames[index].back(); @@ -1321,14 +1321,12 @@ void saveGltfCharacter( const MotionParameters& motion, const IdentityParameters& offsets, std::span> markerSequence, - const GltfFileFormat fileFormat, - const GltfOptions& options) { - constexpr auto kEmbedResources = false; // Don't embed resource for saving glb - // create new model + const FileSaveOptions& options) { + constexpr auto kEmbedResources = false; fx::gltf::Document model = makeCharacterDocument( character, fps, motion, offsets, markerSequence, kEmbedResources, options); - GltfBuilder::save(model, filename, fileFormat, kEmbedResources); + GltfBuilder::save(model, filename, options.gltfFileFormat, kEmbedResources); } void saveGltfCharacter( @@ -1337,14 +1335,12 @@ void saveGltfCharacter( const float fps, std::span skeletonStates, std::span> markerSequence, - const GltfFileFormat fileFormat, - const GltfOptions& options) { - constexpr auto kEmbedResources = false; // Don't embed resource for saving glb - // create new model + const FileSaveOptions& options) { + constexpr auto kEmbedResources = false; fx::gltf::Document model = ::makeCharacterDocument( character, fps, skeletonStates, markerSequence, kEmbedResources, options); - GltfBuilder::save(model, filename, fileFormat, kEmbedResources); + GltfBuilder::save(model, filename, options.gltfFileFormat, kEmbedResources); } std::vector saveCharacterToBytes( @@ -1353,8 +1349,8 @@ std::vector saveCharacterToBytes( const MotionParameters& motion, const IdentityParameters& offsets, std::span> markerSequence, - const GltfOptions& options) { - constexpr auto kEmbedResources = false; // Don't embed resource for saving glb + const FileSaveOptions& options) { + constexpr auto kEmbedResources = false; fx::gltf::Document model = makeCharacterDocument( character, fps, motion, offsets, markerSequence, kEmbedResources, options); diff --git a/momentum/io/gltf/gltf_io.h b/momentum/io/gltf/gltf_io.h index a6e18417bb..60a362a85f 100644 --- a/momentum/io/gltf/gltf_io.h +++ b/momentum/io/gltf/gltf_io.h @@ -98,7 +98,7 @@ fx::gltf::Document makeCharacterDocument( const IdentityParameters& offsets = {}, std::span> markerSequence = {}, bool embedResource = true, - const GltfOptions& options = GltfOptions()); + const FileSaveOptions& options = FileSaveOptions()); /// Saves character motion to a glb file. /// @@ -106,6 +106,8 @@ fx::gltf::Document makeCharacterDocument( /// numFrames) /// @param[in] offsets Offset values per joint capturing the skeleton bone lengths using translation /// and scale offset (7*numJoints, 1) +/// @param[in] options Optional file save options for controlling output (default: +/// FileSaveOptions{}). void saveGltfCharacter( const filesystem::path& filename, const Character& character, @@ -113,21 +115,21 @@ void saveGltfCharacter( const MotionParameters& motion = {}, const IdentityParameters& offsets = {}, std::span> markerSequence = {}, - GltfFileFormat fileFormat = GltfFileFormat::Auto, - const GltfOptions& options = GltfOptions()); + const FileSaveOptions& options = FileSaveOptions()); /// Saves character skeleton states to a glb file. /// /// @param[in] skeletonStates The skeleton states for each frame of the motion sequence (numFrames, /// numJoints, 8) +/// @param[in] options Optional file save options for controlling output (default: +/// FileSaveOptions{}). void saveGltfCharacter( const filesystem::path& filename, const Character& character, float fps, std::span skeletonStates, std::span> markerSequence = {}, - GltfFileFormat fileFormat = GltfFileFormat::Auto, - const GltfOptions& options = GltfOptions()); + const FileSaveOptions& options = FileSaveOptions()); std::vector saveCharacterToBytes( const Character& character, @@ -135,6 +137,6 @@ std::vector saveCharacterToBytes( const MotionParameters& motion = {}, const IdentityParameters& offsets = {}, std::span> markerSequence = {}, - const GltfOptions& options = GltfOptions()); + const FileSaveOptions& options = FileSaveOptions()); } // namespace momentum diff --git a/pymomentum/geometry/character_pybind.cpp b/pymomentum/geometry/character_pybind.cpp index b7e9bba202..d6b6d9dc9c 100644 --- a/pymomentum/geometry/character_pybind.cpp +++ b/pymomentum/geometry/character_pybind.cpp @@ -714,6 +714,7 @@ support the proprietary momentum motion format for storing model parameters in G :param motion: Pose array in [n_frames x n_parameters] :param offsets: Offset array in [n_joints x n_parameters_per_joint] :param markers: Additional marker (3d positions) data in [n_frames][n_markers] +:param options: FileSaveOptions for controlling output (mesh, locators, collisions, etc.) )", py::arg("path"), py::arg("character"), @@ -721,7 +722,7 @@ support the proprietary momentum motion format for storing model parameters in G py::arg("motion") = std::optional{}, py::arg("offsets") = std::optional{}, py::arg("markers") = std::optional>>{}, - py::arg("options") = momentum::GltfOptions{}) + py::arg("options") = std::optional{}) .def_static( "save_gltf_from_skel_states", &saveGLTFCharacterToFileFromSkelStates, @@ -733,13 +734,14 @@ support the proprietary momentum motion format for storing model parameters in G :param fps: Frequency in frames per second :param skel_states: Skeleton states [n_frames x n_joints x n_parameters_per_joint] :param markers: Additional marker (3d positions) data in [n_frames][n_markers] +:param options: FileSaveOptions for controlling output (mesh, locators, collisions, etc.) )", py::arg("path"), py::arg("character"), py::arg("fps"), py::arg("skel_states"), py::arg("markers") = std::optional>>{}, - py::arg("options") = momentum::GltfOptions{}) + py::arg("options") = std::optional{}) .def_static( "save_fbx", &saveFBXCharacterToFile, diff --git a/pymomentum/geometry/geometry_pybind.cpp b/pymomentum/geometry/geometry_pybind.cpp index 89c6d36a95..c6112390b9 100644 --- a/pymomentum/geometry/geometry_pybind.cpp +++ b/pymomentum/geometry/geometry_pybind.cpp @@ -187,8 +187,6 @@ PYBIND11_MODULE(geometry, m) { "A constraint on model or joint parameters used to enforce realistic poses. " "Supports various limit types including min/max bounds, linear relationships, " "ellipsoid constraints, and half-plane constraints."); - auto gltfOptionsClass = - py::class_(m, "GltfOptions", "Storage options for Gltf export."); auto fileSaveOptionsClass = py::class_( m, "FileSaveOptions", @@ -197,31 +195,6 @@ PYBIND11_MODULE(geometry, m) { "multiple function parameters. Format-specific options (e.g., FBX coordinate " "system, GLTF extensions) are included but only used by their respective formats."); - gltfOptionsClass.def(py::init<>()) - .def( - py::init(), - py::arg("extensions") = true, - py::arg("collisions") = true, - py::arg("locators") = true, - py::arg("mesh") = true, - py::arg("blend_shapes") = true) - .def( - "__repr__", - [](const mm::GltfOptions& self) { - return fmt::format( - "GltfData(extensions={}, collisions={}, locators={}, mesh={}, blendShapes={})", - self.extensions, - self.collisions, - self.locators, - self.mesh, - self.blendShapes); - }) - .def_readwrite("extensions", &mm::GltfOptions::extensions, "Save momentum extensions") - .def_readwrite("collisions", &mm::GltfOptions::collisions, "Save collision geometry") - .def_readwrite("locators", &mm::GltfOptions::locators, "Save locator data") - .def_readwrite("mesh", &mm::GltfOptions::mesh, "Save mesh data") - .def_readwrite("blend_shapes", &mm::GltfOptions::blendShapes, "Save blend shape data"); - blendShapeClass .def_property_readonly( "base_shape", diff --git a/pymomentum/geometry/gltf_builder_pybind.cpp b/pymomentum/geometry/gltf_builder_pybind.cpp index 24c6897884..82f1dfce20 100644 --- a/pymomentum/geometry/gltf_builder_pybind.cpp +++ b/pymomentum/geometry/gltf_builder_pybind.cpp @@ -82,12 +82,12 @@ Setting this value will affect subsequently added motions and animations. const mm::Character& character, const std::optional& positionOffset, const std::optional& rotationOffset, - const std::optional& options) { + const std::optional& options) { // Use defaults if not provided Eigen::Vector3f actualPositionOffset = positionOffset.value_or(Eigen::Vector3f::Zero()); Eigen::Vector4f actualRotationOffset = rotationOffset.value_or(Eigen::Vector4f(0.0f, 0.0f, 0.0f, 1.0f)); - mm::GltfOptions actualOptions = options.value_or(mm::GltfOptions{}); + mm::FileSaveOptions actualOptions = options.value_or(mm::FileSaveOptions{}); // Convert Vector4f (x,y,z,w) to Quaternionf (w,x,y,z) mm::Quaternionf quaternionOffset( @@ -273,7 +273,7 @@ can be explicitly specified or automatically deduced from the file extension. // Convert to Python bytes const std::string& str = output.str(); - return py::bytes(str); + return {str}; }, R"(Convert the GLTF scene to bytes in memory. diff --git a/pymomentum/geometry/momentum_io.cpp b/pymomentum/geometry/momentum_io.cpp index 2210c7b77a..9d901ce38f 100644 --- a/pymomentum/geometry/momentum_io.cpp +++ b/pymomentum/geometry/momentum_io.cpp @@ -26,8 +26,8 @@ momentum::Character loadGLTFCharacterFromFile(const std::string& path) { momentum::Character loadGLTFCharacterFromBytes(const pybind11::bytes& bytes) { pybind11::buffer_info info(pybind11::buffer(bytes).request()); - const std::byte* data = reinterpret_cast(info.ptr); - const size_t length = static_cast(info.size); + const auto* data = reinterpret_cast(info.ptr); + const auto length = static_cast(info.size); MT_THROW_IF(data == nullptr, "Unable to extract contents from bytes."); @@ -108,7 +108,7 @@ void saveGLTFCharacterToFile( const std::optional& motion, const std::optional& offsets, const std::optional>>& markers, - const momentum::GltfOptions& options) { + const std::optional& options) { if (motion.has_value()) { const auto& [parameters, poses] = motion.value(); MT_THROW_IF( @@ -125,8 +125,7 @@ void saveGLTFCharacterToFile( transpose(motion.value_or(momentum::MotionParameters{})), offsets.value_or(momentum::IdentityParameters{}), markers.value_or(std::vector>{}), - momentum::GltfFileFormat::Auto, - options); + options.value_or(momentum::FileSaveOptions{})); } void saveGLTFCharacterToFileFromSkelStates( @@ -135,7 +134,7 @@ void saveGLTFCharacterToFileFromSkelStates( const float fps, const pybind11::array_t& skelStates, const std::optional>>& markers, - const momentum::GltfOptions& options) { + const std::optional& options) { const auto numFrames = skelStates.shape(0); MT_THROW_IF( @@ -154,8 +153,7 @@ void saveGLTFCharacterToFileFromSkelStates( fps, skeletonStates, markers.value_or(std::vector>{}), - momentum::GltfFileFormat::Auto, - options); + options.value_or(momentum::FileSaveOptions{})); } void saveFBXCharacterToFile( @@ -261,8 +259,8 @@ std::tuple loadGLTFChar std::tuple loadGLTFCharacterWithMotionFromBytes(const pybind11::bytes& gltfBytes) { pybind11::buffer_info info(pybind11::buffer(gltfBytes).request()); - const std::byte* data = reinterpret_cast(info.ptr); - const size_t length = static_cast(info.size); + const auto* data = reinterpret_cast(info.ptr); + const auto length = static_cast(info.size); MT_THROW_IF(data == nullptr, "Unable to extract contents from bytes."); @@ -302,8 +300,8 @@ pybind11::array_t skelStatesToTensor( std::tuple, std::vector> loadGLTFCharacterWithSkelStatesFromBytes(const pybind11::bytes& gltfBytes) { pybind11::buffer_info info(pybind11::buffer(gltfBytes).request()); - const std::byte* data = reinterpret_cast(info.ptr); - const size_t length = static_cast(info.size); + const auto* data = reinterpret_cast(info.ptr); + const auto length = static_cast(info.size); MT_THROW_IF(data == nullptr, "Unable to extract contents from bytes."); diff --git a/pymomentum/geometry/momentum_io.h b/pymomentum/geometry/momentum_io.h index 49ab35ed4e..2413fa18ce 100644 --- a/pymomentum/geometry/momentum_io.h +++ b/pymomentum/geometry/momentum_io.h @@ -43,7 +43,7 @@ void saveGLTFCharacterToFile( const std::optional& motion, const std::optional& offsets, const std::optional>>& markers, - const momentum::GltfOptions& options); + const std::optional& options); void saveGLTFCharacterToFileFromSkelStates( const std::string& path, @@ -51,7 +51,7 @@ void saveGLTFCharacterToFileFromSkelStates( float fps, const pybind11::array_t& skelStates, const std::optional>>& markers, - const momentum::GltfOptions& options); + const std::optional& options); void saveFBXCharacterToFile( const std::string& path, @@ -100,13 +100,13 @@ std::tuple loadGLTFChar const std::string& gltfFilename); std::tuple -loadGLTFCharacterWithMotionFromBytes(const pybind11::bytes& bytes); +loadGLTFCharacterWithMotionFromBytes(const pybind11::bytes& gltfBytes); std::tuple, std::vector> loadGLTFCharacterWithSkelStates(const std::string& gltfFilename); std::tuple, std::vector> -loadGLTFCharacterWithSkelStatesFromBytes(const pybind11::bytes& gltfFilename); +loadGLTFCharacterWithSkelStatesFromBytes(const pybind11::bytes& gltfBytes); std::string toGLTF(const momentum::Character& character); From 896599a4ee9ccbf57efc88c7fd08b3146aa518d4 Mon Sep 17 00:00:00 2001 From: Yuting Ye Date: Sat, 15 Nov 2025 14:49:08 -0800 Subject: [PATCH 3/3] Refactor FBX save functions to use FileSaveOptions (#828) Summary: This diff refactors the FBX save functions to use the `FileSaveOptions` struct instead of individual parameters, following the pattern established in D86039901 for GLTF save functions. Differential Revision: D86883821 --- .../examples/convert_model/convert_model.cpp | 5 +- momentum/io/character_io.cpp | 21 +---- momentum/io/fbx/fbx_io.cpp | 89 +++++-------------- momentum/io/fbx/fbx_io.h | 43 ++------- momentum/marker_tracking/app_utils.cpp | 2 +- pymomentum/geometry/character_pybind.cpp | 12 +-- pymomentum/geometry/momentum_io.cpp | 26 ++---- pymomentum/geometry/momentum_io.h | 9 +- pymomentum/test/test_fbx.py | 32 ++++--- 9 files changed, 70 insertions(+), 169 deletions(-) diff --git a/momentum/examples/convert_model/convert_model.cpp b/momentum/examples/convert_model/convert_model.cpp index c39d0d38bb..201d26d811 100644 --- a/momentum/examples/convert_model/convert_model.cpp +++ b/momentum/examples/convert_model/convert_model.cpp @@ -216,8 +216,9 @@ int main(int argc, char** argv) { // save output if (oextension == ".fbx") { MT_LOGI("Saving fbx file..."); - saveFbx( - options->output_model_file, character, poses, offsets, fps, options->character_mesh_save); + FileSaveOptions fbxOptions; + fbxOptions.mesh = options->character_mesh_save; + saveFbx(options->output_model_file, character, poses, offsets, fps, {}, fbxOptions); } else if (oextension == ".glb" || oextension == ".gltf") { MT_LOGI("Saving gltf/glb file..."); if (hasMotion) { diff --git a/momentum/io/character_io.cpp b/momentum/io/character_io.cpp index f30cf00d4e..d0eb64de36 100644 --- a/momentum/io/character_io.cpp +++ b/momentum/io/character_io.cpp @@ -166,16 +166,7 @@ void saveCharacter( } else if (format == CharacterFormat::Fbx) { // Save as FBX saveFbx( - filename, - character, - motion, - VectorXf(), - static_cast(fps), - options.mesh, - options.coordSystemInfo, - options.permissive, - markerSequence, - options.fbxNamespace); + filename, character, motion, VectorXf(), static_cast(fps), markerSequence, options); } else { MT_THROW( "{} is not a supported format. Supported formats: .fbx, .glb, .gltf", filename.string()); @@ -201,15 +192,7 @@ void saveCharacter( } else if (format == CharacterFormat::Fbx) { // Save as FBX saveFbxWithSkeletonStates( - filename, - character, - skeletonStates, - static_cast(fps), - options.mesh, - options.coordSystemInfo, - options.permissive, - markerSequence, - options.fbxNamespace); + filename, character, skeletonStates, static_cast(fps), markerSequence, options); } else { MT_THROW( "{} is not a supported format. Supported formats: .fbx, .glb, .gltf", filename.string()); diff --git a/momentum/io/fbx/fbx_io.cpp b/momentum/io/fbx/fbx_io.cpp index 1e682d3258..fc88094354 100644 --- a/momentum/io/fbx/fbx_io.cpp +++ b/momentum/io/fbx/fbx_io.cpp @@ -795,14 +795,11 @@ MarkerSequence loadFbxMarkerSequence(const filesystem::path& filename, bool stri void saveFbx( const filesystem::path& filename, const Character& character, - const MatrixXf& poses, // model parameters + const MatrixXf& poses, const VectorXf& identity, const double framerate, - const bool saveMesh, - const FbxCoordSystemInfo& coordSystemInfo, - const bool permissive, std::span> markerSequence, - std::string_view fbxNamespace) { + const FileSaveOptions& options) { CharacterParameters params; if (identity.size() == character.parameterTransform.numJointParameters()) { params.offsets = identity; @@ -810,41 +807,34 @@ void saveFbx( params.offsets = character.parameterTransform.bindPose(); } - // first convert model parameters to joint values CharacterState state; MatrixXf jointValues; if (poses.cols() > 0) { - // Set the initial pose to initialize the state params.pose = poses.col(0); state.set(params, character, false, false, false); - // Resize the jointValues matrix based on the size of joint parameters and number of poses jointValues.resize(state.skeletonState.jointParameters.v.size(), poses.cols()); - // Store the joint parameters for the initial pose jointValues.col(0) = state.skeletonState.jointParameters.v; - // Iterate through each subsequent pose for (Eigen::Index f = 1; f < poses.cols(); f++) { - // set the current pose params.pose = poses.col(f); state.set(params, character, false, false, false); jointValues.col(f) = state.skeletonState.jointParameters.v; } } - // Call the helper function to save FBX file with joint values saveFbxCommon( filename, character, jointValues, framerate, - saveMesh, + options.mesh, false, - coordSystemInfo, - permissive, + options.coordSystemInfo, + options.permissive, markerSequence, - fbxNamespace); + options.fbxNamespace); } void saveFbxWithJointParams( @@ -852,25 +842,19 @@ void saveFbxWithJointParams( const Character& character, const MatrixXf& jointParams, const double framerate, - const bool saveMesh, - const FbxCoordSystemInfo& coordSystemInfo, - const bool permissive, std::span> markerSequence, - std::string_view fbxNamespace) { - // Call the helper function to save FBX file with joint values. - // Set skipActiveJointParamCheck=true to skip the active joint param check as the joint params are - // passed in directly from user. + const FileSaveOptions& options) { saveFbxCommon( filename, character, jointParams, framerate, - saveMesh, + options.mesh, true, - coordSystemInfo, - permissive, + options.coordSystemInfo, + options.permissive, markerSequence, - fbxNamespace); + options.fbxNamespace); } void saveFbxWithSkeletonStates( @@ -878,11 +862,8 @@ void saveFbxWithSkeletonStates( const Character& character, std::span skeletonStates, const double framerate, - const bool saveMesh, - const FbxCoordSystemInfo& coordSystemInfo, - const bool permissive, std::span> markerSequence, - std::string_view fbxNamespace) { + const FileSaveOptions& options) { const size_t nFrames = skeletonStates.size(); MatrixXf jointParams(character.parameterTransform.zero().v.size(), nFrames); for (size_t iFrame = 0; iFrame < nFrames; ++iFrame) { @@ -890,39 +871,24 @@ void saveFbxWithSkeletonStates( skeletonStateToJointParameters(skeletonStates[iFrame], character.skeleton).v; } - // Call the helper function to save FBX file with joint values. - // Set skipActiveJointParamCheck=true to skip the active joint param check as the joint params are - // passed in directly from user. saveFbxCommon( filename, character, jointParams, framerate, - saveMesh, + options.mesh, true, - coordSystemInfo, - permissive, + options.coordSystemInfo, + options.permissive, markerSequence, - fbxNamespace); + options.fbxNamespace); } void saveFbxModel( const filesystem::path& filename, const Character& character, - const FbxCoordSystemInfo& coordSystemInfo, - bool permissive, - std::string_view fbxNamespace) { - saveFbx( - filename, - character, - MatrixXf(), - VectorXf(), - 120.0, - true, - coordSystemInfo, - permissive, - {}, - fbxNamespace); + const FileSaveOptions& options) { + saveFbx(filename, character, MatrixXf(), VectorXf(), 120.0, {}, options); } #else // !MOMENTUM_WITH_FBX_SDK @@ -933,11 +899,8 @@ void saveFbx( const MatrixXf& /* poses */, const VectorXf& /* identity */, const double /* framerate */, - const bool /* saveMesh */, - const FbxCoordSystemInfo& /* coordSystemInfo */, - const bool /* permissive */, std::span> /* markerSequence */, - std::string_view /* fbxNamespace */) { + const FileSaveOptions& /* options */) { MT_THROW( "FBX saving is not supported in OpenFBX-only mode. FBX loading is available via OpenFBX, but saving requires the full Autodesk FBX SDK."); } @@ -947,11 +910,8 @@ void saveFbxWithJointParams( const Character& /* character */, const MatrixXf& /* jointParams */, const double /* framerate */, - const bool /* saveMesh */, - const FbxCoordSystemInfo& /* coordSystemInfo */, - const bool /* permissive */, std::span> /* markerSequence */, - std::string_view /* fbxNamespace */) { + const FileSaveOptions& /* options */) { MT_THROW( "FBX saving is not supported in OpenFBX-only mode. FBX loading is available via OpenFBX, but saving requires the full Autodesk FBX SDK."); } @@ -961,11 +921,8 @@ void saveFbxWithSkeletonStates( const Character& /* character */, std::span /* skeletonStates */, const double /* framerate */, - const bool /* saveMesh */, - const FbxCoordSystemInfo& /* coordSystemInfo */, - const bool /* permissive */, std::span> /* markerSequence */, - std::string_view /* fbxNamespace */) { + const FileSaveOptions& /* options */) { MT_THROW( "FBX saving is not supported in OpenFBX-only mode. FBX loading is available via OpenFBX, but saving requires the full Autodesk FBX SDK."); } @@ -973,9 +930,7 @@ void saveFbxWithSkeletonStates( void saveFbxModel( const filesystem::path& /* filename */, const Character& /* character */, - const FbxCoordSystemInfo& /* coordSystemInfo */, - bool /* permissive */, - std::string_view /* fbxNamespace */) { + const FileSaveOptions& /* options */) { MT_THROW( "FBX saving is not supported in OpenFBX-only mode. FBX loading is available via OpenFBX, but saving requires the full Autodesk FBX SDK."); } diff --git a/momentum/io/fbx/fbx_io.h b/momentum/io/fbx/fbx_io.h index 310a123a18..14605b283e 100644 --- a/momentum/io/fbx/fbx_io.h +++ b/momentum/io/fbx/fbx_io.h @@ -67,80 +67,55 @@ std::tuple, float> loadFbxCharacterWithMotion( /// @param poses Model parameters for each frame (empty for bind pose only) /// @param identity Identity pose parameters (empty to use bind pose) /// @param framerate Animation framerate in frames per second -/// @param saveMesh Whether to include mesh geometry in the output -/// @param coordSystemInfo Coordinate system configuration for the FBX file -/// @param permissive Permissive mode allows saving mesh-only characters (without skin weights) -/// @param fbxNamespace Optional namespace to prepend to all node names (e.g., "ns" will become -/// "ns:") +/// @param markerSequence Optional marker sequence data to save with the character +/// @param options Optional file save options for controlling output (default: FileSaveOptions{}) void saveFbx( const filesystem::path& filename, const Character& character, const MatrixXf& poses = MatrixXf(), const VectorXf& identity = VectorXf(), double framerate = 120.0, - bool saveMesh = false, - const FbxCoordSystemInfo& coordSystemInfo = FbxCoordSystemInfo(), - bool permissive = false, std::span> markerSequence = {}, - std::string_view fbxNamespace = ""); + const FileSaveOptions& options = FileSaveOptions()); /// Save a character with animation using joint parameters directly. /// @param filename Path to the output FBX file /// @param character The character to save /// @param jointParams Joint parameters for each frame (empty for bind pose only) /// @param framerate Animation framerate in frames per second -/// @param saveMesh Whether to include mesh geometry in the output -/// @param coordSystemInfo Coordinate system configuration for the FBX file -/// @param permissive Permissive mode allows saving mesh-only characters (without skin weights) /// @param markerSequence Optional marker sequence data to save with the character -/// @param fbxNamespace Optional namespace to prepend to all node names (e.g., "ns" will become -/// "ns:") +/// @param options Optional file save options for controlling output (default: FileSaveOptions{}) void saveFbxWithJointParams( const filesystem::path& filename, const Character& character, const MatrixXf& jointParams = MatrixXf(), double framerate = 120.0, - bool saveMesh = false, - const FbxCoordSystemInfo& coordSystemInfo = FbxCoordSystemInfo(), - bool permissive = false, std::span> markerSequence = {}, - std::string_view fbxNamespace = ""); + const FileSaveOptions& options = FileSaveOptions()); /// Save a character with animation using skeleton states directly. /// @param filename Path to the output FBX file /// @param character The character to save /// @param skeletonStates SkeletonState for each frame (empty for bind pose only) /// @param framerate Animation framerate in frames per second -/// @param saveMesh Whether to include mesh geometry in the output -/// @param coordSystemInfo Coordinate system configuration for the FBX file -/// @param permissive Permissive mode allows saving mesh-only characters (without skin weights) /// @param markerSequence Optional marker sequence data to save with the character -/// @param fbxNamespace Optional namespace to prepend to all node names (e.g., "ns" will become -/// "ns:") +/// @param options Optional file save options for controlling output (default: FileSaveOptions{}) void saveFbxWithSkeletonStates( const filesystem::path& filename, const Character& character, std::span skeletonStates, double framerate = 120.0, - bool saveMesh = false, - const FbxCoordSystemInfo& coordSystemInfo = FbxCoordSystemInfo(), - bool permissive = false, std::span> markerSequence = {}, - std::string_view fbxNamespace = ""); + const FileSaveOptions& options = FileSaveOptions()); /// Save a character model (skeleton and mesh) without animation. /// @param filename Path to the output FBX file /// @param character The character to save -/// @param coordSystemInfo Coordinate system configuration for the FBX file -/// @param permissive Permissive mode allows saving mesh-only characters (without skin weights) -/// @param fbxNamespace Optional namespace to prepend to all node names (e.g., "ns" will become -/// "ns:") +/// @param options Optional file save options for controlling output (default: FileSaveOptions{}) void saveFbxModel( const filesystem::path& filename, const Character& character, - const FbxCoordSystemInfo& coordSystemInfo = FbxCoordSystemInfo(), - bool permissive = false, - std::string_view fbxNamespace = ""); + const FileSaveOptions& options = FileSaveOptions()); /// Loads a MarkerSequence from an FBX file. /// diff --git a/momentum/marker_tracking/app_utils.cpp b/momentum/marker_tracking/app_utils.cpp index 7e6ed73eb8..4386a76b87 100644 --- a/momentum/marker_tracking/app_utils.cpp +++ b/momentum/marker_tracking/app_utils.cpp @@ -205,7 +205,7 @@ void saveMotion( const VectorXf idVec = character.parameterTransform.apply(id).v; if (ext == ".fbx") { - saveFbx(output, character, finalMotion, idVec, fps, saveMarkerMesh); + saveFbx(output, character, finalMotion, idVec, fps); } else if (ext == ".glb" || ext == ".gltf") { GltfBuilder fileBuilder; fileBuilder.addMotion( diff --git a/pymomentum/geometry/character_pybind.cpp b/pymomentum/geometry/character_pybind.cpp index d6b6d9dc9c..325e2dcc2b 100644 --- a/pymomentum/geometry/character_pybind.cpp +++ b/pymomentum/geometry/character_pybind.cpp @@ -753,9 +753,8 @@ support the proprietary momentum motion format for storing model parameters in G :param fps: Frequency in frames per second :param motion: [Optional] 2D pose matrix in [n_frames x n_parameters] :param offsets: [Optional] Offset array in [(n_joints x n_parameters_per_joint)] -:param coord_system_info: [Optional] FBX coordinate system info :param markers: Additional marker (3d positions) data in [n_frames][n_markers] -:param fbx_namespace: [Optional] Namespace prefix for all node names (e.g., "ns" becomes "ns:") +:param options: [Optional] FileSaveOptions for controlling output (mesh, locators, collisions, coordinate system, namespace, etc.) )", py::arg("path"), py::arg("character"), @@ -763,8 +762,7 @@ support the proprietary momentum motion format for storing model parameters in G py::arg("motion") = std::optional{}, py::arg("offsets") = std::optional{}, py::arg("markers") = std::optional>>{}, - py::arg("coord_system_info") = std::optional{}, - py::arg("fbx_namespace") = "") + py::arg("options") = momentum::FileSaveOptions()) .def_static( "save_fbx_with_joint_params", &saveFBXCharacterToFileWithJointParams, @@ -775,17 +773,15 @@ support the proprietary momentum motion format for storing model parameters in G :param character: A Character to be saved to the output file. :param fps: Frequency in frames per second :param joint_params: [Optional] 2D pose matrix in [n_frames x n_parameters] -:param coord_system_info: [Optional] FBX coordinate system info :param markers: Additional marker (3d positions) data in [n_frames][n_markers] -:param fbx_namespace: [Optional] Namespace prefix for all node names (e.g., "ns" becomes "ns:") +:param options: [Optional] FileSaveOptions for controlling output (mesh, locators, collisions, coordinate system, namespace, etc.) )", py::arg("path"), py::arg("character"), py::arg("fps") = 120.f, py::arg("joint_params") = std::optional{}, py::arg("markers") = std::optional>>{}, - py::arg("coord_system_info") = std::optional{}, - py::arg("fbx_namespace") = "") + py::arg("options") = momentum::FileSaveOptions()) .def_static( "save", &saveCharacterToFile, diff --git a/pymomentum/geometry/momentum_io.cpp b/pymomentum/geometry/momentum_io.cpp index 9d901ce38f..2cf220f436 100644 --- a/pymomentum/geometry/momentum_io.cpp +++ b/pymomentum/geometry/momentum_io.cpp @@ -163,20 +163,15 @@ void saveFBXCharacterToFile( const std::optional& motion, const std::optional& offsets, const std::optional>>& markers, - const std::optional& coordSystemInfo, - std::string_view fbxNamespace) { - // Always use saveFbx to support markers even without motion + const momentum::FileSaveOptions& options) { momentum::saveFbx( path, character, motion.has_value() ? motion.value().transpose() : Eigen::MatrixXf(), offsets.has_value() ? offsets.value() : Eigen::VectorXf(), fps, - true, /*saveMesh*/ - coordSystemInfo.value_or(momentum::FbxCoordSystemInfo()), - false, /*permissive*/ markers.value_or(std::vector>{}), - fbxNamespace); + options); } void saveFBXCharacterToFileWithJointParams( @@ -185,19 +180,14 @@ void saveFBXCharacterToFileWithJointParams( const float fps, const std::optional& jointParams, const std::optional>>& markers, - const std::optional& coordSystemInfo, - std::string_view fbxNamespace) { - // Always use saveFbxWithJointParams to support markers even without motion + const momentum::FileSaveOptions& options) { momentum::saveFbxWithJointParams( path, character, jointParams.has_value() ? jointParams.value().transpose() : Eigen::MatrixXf(), fps, - true, /*saveMesh*/ - coordSystemInfo.value_or(momentum::FbxCoordSystemInfo()), - false, /*permissive*/ markers.value_or(std::vector>{}), - fbxNamespace); + options); } void saveFBXCharacterToFileWithSkelStates( @@ -206,18 +196,14 @@ void saveFBXCharacterToFileWithSkelStates( float fps, const pybind11::array_t& skelStates, const std::optional>>& markers, - const std::optional& coordSystemInfo, - std::string_view fbxNamespace) { + const momentum::FileSaveOptions& options) { momentum::saveFbxWithSkeletonStates( path, character, arrayToSkeletonStates(skelStates, character), fps, - true, /*saveMesh*/ - coordSystemInfo.value_or(momentum::FbxCoordSystemInfo()), - false, /*permissive*/ markers.value_or(std::vector>{}), - fbxNamespace); + options); } void saveCharacterToFile( diff --git a/pymomentum/geometry/momentum_io.h b/pymomentum/geometry/momentum_io.h index 2413fa18ce..c77a646b54 100644 --- a/pymomentum/geometry/momentum_io.h +++ b/pymomentum/geometry/momentum_io.h @@ -60,8 +60,7 @@ void saveFBXCharacterToFile( const std::optional& motion, const std::optional& offsets, const std::optional>>& markers, - const std::optional& coordSystemInfo, - std::string_view fbxNamespace = ""); + const momentum::FileSaveOptions& options = momentum::FileSaveOptions()); void saveFBXCharacterToFileWithJointParams( const std::string& path, @@ -69,8 +68,7 @@ void saveFBXCharacterToFileWithJointParams( float fps, const std::optional& jointParams, const std::optional>>& markers, - const std::optional& coordSystemInfo, - std::string_view fbxNamespace = ""); + const momentum::FileSaveOptions& options = momentum::FileSaveOptions()); void saveFBXCharacterToFileWithSkelStates( const std::string& path, @@ -78,8 +76,7 @@ void saveFBXCharacterToFileWithSkelStates( float fps, const pybind11::array_t& skelStates, const std::optional>>& markers, - const std::optional& coordSystemInfo, - std::string_view fbxNamespace = ""); + const momentum::FileSaveOptions& options = momentum::FileSaveOptions()); void saveCharacterToFile( const std::string& path, diff --git a/pymomentum/test/test_fbx.py b/pymomentum/test/test_fbx.py index b9f59a0f40..b5ea244379 100644 --- a/pymomentum/test/test_fbx.py +++ b/pymomentum/test/test_fbx.py @@ -143,17 +143,20 @@ def test_save_motions_with_model_params(self) -> None: # Test saving with model parameters with tempfile.NamedTemporaryFile(suffix=".fbx") as temp_file: offsets = np.zeros(self.joint_params.shape[1]) + options = pym_geometry.FileSaveOptions( + coord_system_info=pym_geometry.FbxCoordSystemInfo( + pym_geometry.FbxUpVector.YAxis, + pym_geometry.FbxFrontVector.ParityEven, + pym_geometry.FbxCoordSystem.LeftHanded, + ) + ) pym_geometry.Character.save_fbx( path=temp_file.name, character=self.character, motion=self.model_params.numpy(), offsets=offsets, fps=60, - coord_system_info=pym_geometry.FbxCoordSystemInfo( - pym_geometry.FbxUpVector.YAxis, - pym_geometry.FbxFrontVector.ParityEven, - pym_geometry.FbxCoordSystem.LeftHanded, - ), + options=options, ) self._verify_fbx(temp_file.name) @@ -173,16 +176,19 @@ def test_save_motions_with_joint_params(self) -> None: # Test saving with joint parameters using non-default coord-system with tempfile.NamedTemporaryFile(suffix=".fbx") as temp_file: + options = pym_geometry.FileSaveOptions( + coord_system_info=pym_geometry.FbxCoordSystemInfo( + pym_geometry.FbxUpVector.YAxis, + pym_geometry.FbxFrontVector.ParityEven, + pym_geometry.FbxCoordSystem.RightHanded, + ) + ) pym_geometry.Character.save_fbx_with_joint_params( path=temp_file.name, character=self.character, joint_params=self.joint_params.numpy(), fps=60, - coord_system_info=pym_geometry.FbxCoordSystemInfo( - pym_geometry.FbxUpVector.YAxis, - pym_geometry.FbxFrontVector.ParityEven, - pym_geometry.FbxCoordSystem.RightHanded, - ), + options=options, ) self._verify_fbx(temp_file.name) @@ -215,13 +221,14 @@ def test_save_with_namespace(self) -> None: with tempfile.NamedTemporaryFile(suffix=".fbx") as temp_file: offsets = np.zeros(self.joint_params.shape[1]) # Save with namespace + options = pym_geometry.FileSaveOptions(fbx_namespace="test_ns") pym_geometry.Character.save_fbx( path=temp_file.name, character=self.character, motion=self.model_params.numpy(), offsets=offsets, fps=60, - fbx_namespace="test_ns", + options=options, ) # Verify file can be loaded self._verify_fbx(temp_file.name) @@ -232,12 +239,13 @@ def test_save_with_joint_params_and_namespace(self) -> None: """Test FBX save with joint params and namespace parameter.""" with tempfile.NamedTemporaryFile(suffix=".fbx") as temp_file: + options = pym_geometry.FileSaveOptions(fbx_namespace="test_ns") pym_geometry.Character.save_fbx_with_joint_params( path=temp_file.name, character=self.character, joint_params=self.joint_params.numpy(), fps=60, - fbx_namespace="test_ns", + options=options, ) # Verify file can be loaded self._verify_fbx(temp_file.name)