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 80ec093efb..d0eb64de36 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,21 +162,11 @@ void saveCharacter( {character.parameterTransform.name, motion}, {}, markerSequence, - options.gltfFileFormat, - gltfOptions); + options); } 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()); @@ -206,34 +188,11 @@ 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( - 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/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/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 b7e9bba202..325e2dcc2b 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, @@ -751,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"), @@ -761,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, @@ -773,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/geometry_pybind.cpp b/pymomentum/geometry/geometry_pybind.cpp index 39a938e469..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("blendShapes") = 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", @@ -493,19 +466,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 +538,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 +681,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..82f1dfce20 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::FileSaveOptions actualOptions = options.value_or(mm::FileSaveOptions{}); // 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, @@ -272,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..2cf220f436 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( @@ -165,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( @@ -187,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( @@ -208,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( @@ -261,8 +245,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 +286,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..c77a646b54 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, @@ -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, @@ -100,13 +97,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); 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)