Skip to content

Commit

Permalink
Add binding for loadUrdfCharacter
Browse files Browse the repository at this point in the history
Summary:
Add Python bindings for `loadUrdfCharacter()` with two functions:
- `load_urdf()`: Load a character from a URDF file.
- `load_urdf_from_bytes()`: Load a character from URDF bytes

Differential Revision: D69280695
  • Loading branch information
jeongseok-meta authored and facebook-github-bot committed Feb 7, 2025
1 parent ab9115b commit 04a2f56
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 22 deletions.
74 changes: 57 additions & 17 deletions momentum/io/urdf/urdf_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,20 +218,10 @@ bool loadUrdfSkeletonRecursive(
return true;
}

} // namespace

template <typename T>
CharacterT<T> loadUrdfCharacter(const filesystem::path& filepath) {
urdf::ModelInterfaceSharedPtr urdfModel;

try {
urdfModel = urdf::parseURDFFile(filepath.string());
} catch (const std::runtime_error& e) {
MT_THROW("Failed to parse URDF file from: {}. Error: {}", filepath.string(), e.what());
}

CharacterT<T> loadUrdfCharacterFromUrdfModel(urdf::ModelInterfaceSharedPtr urdfModel) {
const urdf::Link* root = urdfModel->getRoot().get();
MT_THROW_IF(!root, "Failed to parse URDF file from: {}. No root link found.", filepath.string());
MT_THROW_IF(!root, "Failed to parse URDF file from. No root link found.");

ParsingData<T> data;

Expand All @@ -241,20 +231,19 @@ CharacterT<T> loadUrdfCharacter(const filesystem::path& filepath) {
if (root->name == "world") {
if (root->child_links.empty()) {
MT_THROW(
"Failed to parse URDF file from: {}. The world link must have at least one child link, but it has none.",
filepath.string());
"Failed to parse URDF. The world link must have at least one child link, but it has none.");
} else if (root->child_links.size() > 1) {
MT_THROW(
"Failed to parse URDF file from: {}. The world link must have only one child link, but it has {}.",
filepath.string(),
"Failed to parse URDF. The world link must have only one child link, but it has {}.",

root->child_links.size());
}
root = root->child_links[0].get();
}

if (!loadUrdfSkeletonRecursive(
data, kInvalidIndex, Quaternionf::Identity(), urdfModel.get(), root)) {
MT_THROW("Failed to parse URDF file from: {}.", filepath.string());
MT_THROW("Failed to parse URDF.");
}

Skeleton& skeleton = data.skeleton;
Expand All @@ -275,7 +264,58 @@ CharacterT<T> loadUrdfCharacter(const filesystem::path& filepath) {
return CharacterT<T>(data.skeleton, data.parameterTransform);
}

} // namespace

template <typename T>
CharacterT<T> loadUrdfCharacter(const filesystem::path& filepath) {
urdf::ModelInterfaceSharedPtr urdfModel;

try {
urdfModel = urdf::parseURDFFile(filepath.string());
} catch (const std::exception& e) {
MT_THROW("Failed to parse URDF file from: {}. Error: {}", filepath.string(), e.what());
} catch (...) {
MT_THROW("Failed to parse URDF file from: {}", filepath.string());
}

try {
return loadUrdfCharacterFromUrdfModel<T>(urdfModel);
} catch (const std::exception& e) {
MT_THROW(
"Failed to create Character from URDF file: {}. Error: {}", filepath.string(), e.what());
} catch (...) {
MT_THROW("Failed to create Character from URDF file: {}", filepath.string());
}
}

template CharacterT<float> loadUrdfCharacter(const filesystem::path& filepath);
template CharacterT<double> loadUrdfCharacter(const filesystem::path& filepath);

template <typename T>
CharacterT<T> loadUrdfCharacter(gsl::span<const std::byte> bytes) {
urdf::ModelInterfaceSharedPtr urdfModel;

try {
// Convert the byte span to a string
std::string urdfString(reinterpret_cast<const char*>(bytes.data()), bytes.size());
// Parse the URDF from the string
urdfModel = urdf::parseURDF(urdfString);
} catch (const std::exception& e) {
MT_THROW("Failed to read URDF file from URDF bytes. Error: {}", e.what());
} catch (...) {
MT_THROW("Failed to read URDF file from URDF bytes");
}

try {
return loadUrdfCharacterFromUrdfModel<T>(urdfModel);
} catch (const std::exception& e) {
MT_THROW("Failed to create Character from URDF bytes. Error: {}", e.what());
} catch (...) {
MT_THROW("Failed to create Character from URDF bytes");
}
}

template CharacterT<float> loadUrdfCharacter(gsl::span<const std::byte> bytes);
template CharacterT<double> loadUrdfCharacter(gsl::span<const std::byte> bytes);

} // namespace momentum
4 changes: 4 additions & 0 deletions momentum/io/urdf/urdf_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ namespace momentum {
template <typename T = float>
[[nodiscard]] CharacterT<T> loadUrdfCharacter(const filesystem::path& filepath);

/// Loads a character from a URDF file using the provided byte data.
template <typename T = float>
[[nodiscard]] CharacterT<T> loadUrdfCharacter(gsl::span<const std::byte> bytes);

} // namespace momentum
25 changes: 24 additions & 1 deletion pymomentum/geometry/geometry_pybind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <momentum/character/skin_weights.h>
#include <momentum/io/fbx/fbx_io.h>
#include <momentum/io/shape/blend_shape_io.h>
#include <momentum/io/urdf/urdf_io.h>
#include <momentum/math/mesh.h>
#include <momentum/math/mppca.h>
#include <momentum/test/character/character_helpers.h>
Expand Down Expand Up @@ -158,6 +159,8 @@ PYBIND11_MODULE(geometry, m) {
// - load_fbx_with_motion_from_bytes(fbx_bytes, permissive)
// - load_gltf(path)
// - load_gltf_with_motion(gltfFilename)
// - load_urdf(urdf_filename)
// - load_urdf_from_bytes(urdf_bytes)
// - save_gltf(path, character, fps, motion, offsets, markers)
// - save_gltf_from_skel_states(path, character, fps, skel_states,
// joint_params, markers)
Expand Down Expand Up @@ -529,6 +532,26 @@ Note: In practice, most limits are enforced on the model parameters, but momentu
:parameter path: A .gltf file; e.g. character_s0.glb.
)",
py::arg("path"))
// loadURDFCharcterFromFile(urdfPath)
.def_static(
"load_urdf",
&loadURDFCharcterFromFile,
py::call_guard<py::gil_scoped_release>(),
R"(Load a character from a urdf file.
:parameter urdf_filename: A .urdf file; e.g. character.urdf.
)",
py::arg("urdf_filename"))
// loadURDFCharcterFromBytes(urdfBytes)
.def_static(
"load_urdf_from_bytes",
&loadURDFCharcterFromBytes,
py::call_guard<py::gil_scoped_release>(),
R"(Load a character from urdf bytes.
:parameter urdf_bytes: Bytes array containing the urdf definition.
)",
py::arg("urdf_bytes"))
// saveGLTFCharacterToFile(filename, character)
.def_static(
"save_gltf",
Expand Down Expand Up @@ -622,7 +645,7 @@ Note: In practice, most limits are enforced on the model parameters, but momentu
return character.simplify(enabledParams);
},
R"(Simplifies the character by removing extra joints; this can help to speed up IK, but passing in a set of
parameters rather than joints. Does not modify the parameter transform. This is the equivalent of calling
parameters rather than joints. Does not modify the parameter transform. This is the equivalent of calling
```character.simplify_skeleton(character.joints_from_parameters(enabled_params))```.
:parameter enabled_parameters: Model parameters to be kept in the simplified model. Defaults to including all parameters.
Expand Down
10 changes: 10 additions & 0 deletions pymomentum/geometry/momentum_geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <momentum/io/skeleton/locator_io.h>
#include <momentum/io/skeleton/mppca_io.h>
#include <momentum/io/skeleton/parameter_transform_io.h>
#include <momentum/io/urdf/urdf_io.h>
#include <momentum/math/mesh.h>

#include <torch/csrc/jit/python/python_ivalue.h>
Expand Down Expand Up @@ -314,6 +315,15 @@ momentum::Character loadLocatorsFromBytes(
return result;
}

momentum::Character loadURDFCharcterFromFile(const std::string& urdfPath) {
return momentum::loadUrdfCharacter<float>(urdfPath);
}

momentum::Character loadURDFCharcterFromBytes(
const pybind11::bytes& urdfBytes) {
return momentum::loadUrdfCharacter<float>(toSpan<std::byte>(urdfBytes));
}

std::shared_ptr<const momentum::Mppca> loadPosePriorFromFile(
const std::string& path) {
return momentum::loadMppca(path);
Expand Down
11 changes: 7 additions & 4 deletions pymomentum/geometry/momentum_geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ momentum::Character loadConfigFromBytes(
const momentum::Character& character,
const pybind11::bytes& bytes);

momentum::Character loadURDFCharcterFromFile(const std::string& urdfPath);
momentum::Character loadURDFCharcterFromBytes(const pybind11::bytes& urdfBytes);

// Convert uniform noise to meaningful model parameters.
// unifNoise: size: batchSize (optional) x #modelParameters, each entry
// range in [0, 1].
Expand Down Expand Up @@ -179,10 +182,10 @@ std::vector<int> bitsetToJointList(const std::vector<bool>& jointMask);
/// @param[in] character A momentum character object with locators
/// @param[in] names A vector of locator names
/// @return A tuple of (locator_parents, locator_offsets)
/// locator_parents has the same size as names vector and contains parent index
/// of the locator if found otherwise -1. locator_offsets is a matrix of size
/// (names.size(), 3) containing the offset of the locator wrt locators' parent
/// joint
/// locator_parents has the same size as names vector and contains parent
/// index of the locator if found otherwise -1. locator_offsets is a matrix of
/// size (names.size(), 3) containing the offset of the locator wrt locators'
/// parent joint
std::tuple<Eigen::VectorXi, RowMatrixf> getLocators(
const momentum::Character& character,
const std::vector<std::string>& names);
Expand Down

0 comments on commit 04a2f56

Please sign in to comment.