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 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..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);