Add proper handling of MJCF asset/mesh/texturedir compiler attributes to USD plugin.

This allows the MJCF SdfFileFormat plugin to load MJCF files that specify assetdir, meshdir, or texturedir attributes. This is specifically important for resolver implementations that fetch assets into a local cache.

PiperOrigin-RevId: 796509754
Change-Id: Ib2b0c9cf9d6a3351ceea49d90c882edf94d0b880
This commit is contained in:
Sam Haves
2025-08-18 11:37:27 -07:00
committed by Copybara-Service
parent 424226b640
commit 4bad134ba2
@@ -16,8 +16,10 @@
#include <array>
#include <memory>
#include <stack>
#include <string>
#include <unordered_set>
#include <vector>
#include <mujoco/mujoco.h>
#include "mjcf/mujoco_to_usd.h"
@@ -54,48 +56,134 @@ TF_REGISTRY_FUNCTION(TfEnum) {
namespace {
bool IsAbsolutePath(const std::string &path) {
// empty: not absolute
if (path.empty()) {
return false;
}
// path is scheme:filename which we consider an absolute path
// e.g. file URI's are always absolute paths
if (mjp_getResourceProvider(path.c_str()) != nullptr) {
return true;
}
// check first char
const char *str = path.c_str();
if (str[0] == '\\' || str[0] == '/') {
return true;
}
// find ":/" or ":\"
if (path.find(":/") != std::string::npos ||
path.find(":\\") != std::string::npos) {
return true;
}
return false;
}
void ResolveMjcfDependencies(const std::string &xml_string,
const std::string &resolved_path);
void AccumulateFilesRecursive(std::unordered_set<std::string> &files,
tinyxml2::XMLElement *elem,
const std::string &resolved_path) {
// get filename
const char *file = elem->Attribute("file");
void AccumulateFiles(std::unordered_set<std::string> &files,
tinyxml2::XMLElement *root,
const std::string &resolved_path) {
const char *asset_dir = nullptr;
const char *mesh_dir = nullptr;
const char *texture_dir = nullptr;
std::vector<std::string> texture_files;
std::vector<std::string> mesh_files;
std::vector<std::string> hfield_files;
if (file != nullptr) {
auto identifier = pxr::ArGetResolver().CreateIdentifier(
std::string(file), pxr::ArResolvedPath(resolved_path));
if (!strcasecmp(elem->Value(), "include") ||
!strcasecmp(elem->Value(), "model")) {
auto include_resolved_path = pxr::ArGetResolver().Resolve(identifier);
auto asset = pxr::ArGetResolver().OpenAsset(include_resolved_path);
ResolveMjcfDependencies(asset->GetBuffer().get(), include_resolved_path);
const std::string parent_dir = TfNormPath(TfGetPathName(resolved_path));
// Neither of these elements should have children.
return;
}
files.insert(identifier);
}
if (!strcasecmp(elem->Value(), "texture")) {
static const char *attributes[] = {"fileright", "fileup", "fileleft",
"filedown", "filefront", "fileback"};
for (const auto &attribute : attributes) {
const char *file = elem->Attribute(attribute);
if (file != nullptr) {
auto accumulate_files = [&](const std::vector<std::string> &candidate_files,
const char *prefix_path) {
for (const auto &file : candidate_files) {
std::string file_with_prefix = prefix_path == nullptr ? file : TfStringCatPaths(prefix_path, file);
if (IsAbsolutePath(file)) {
auto identifier = pxr::ArGetResolver().CreateIdentifier(file);
// If this path is an absolute path, insert that path.
files.insert(identifier);
} else if (IsAbsolutePath(file_with_prefix)) {
auto identifier =
pxr::ArGetResolver().CreateIdentifier(file_with_prefix);
// Else if prefix is an absolute path, insert prefix / file.
files.insert(identifier);
} else {
// Else insert resolved_path / prefix / file.
auto identifier = pxr::ArGetResolver().CreateIdentifier(
std::string(file), pxr::ArResolvedPath(resolved_path));
file_with_prefix, pxr::ArResolvedPath(resolved_path));
files.insert(identifier);
}
}
};
std::stack<tinyxml2::XMLElement *> elements;
elements.push(root);
while (!elements.empty()) {
tinyxml2::XMLElement *elem = elements.top();
elements.pop();
if (!strcasecmp(elem->Value(), "include") ||
!strcasecmp(elem->Value(), "model")) {
const char *file = elem->Attribute("file");
if (file != nullptr) {
auto identifier = pxr::ArGetResolver().CreateIdentifier(
std::string(file), pxr::ArResolvedPath(resolved_path));
auto include_resolved_path = pxr::ArGetResolver().Resolve(identifier);
auto asset = pxr::ArGetResolver().OpenAsset(include_resolved_path);
ResolveMjcfDependencies(asset->GetBuffer().get(),
include_resolved_path);
// Neither of these elements should have children.
continue;
}
} else if (!strcasecmp(elem->Value(), "compiler")) {
asset_dir = elem->Attribute("assetdir");
mesh_dir = elem->Attribute("meshdir");
texture_dir = elem->Attribute("texturedir");
// compiler elements don't have children.
continue;
} else if (!strcasecmp(elem->Value(), "mesh")) {
// mesh elements don't have children.
const char *file = elem->Attribute("file");
if (file != nullptr) {
mesh_files.emplace_back(file);
}
continue;
} else if (!strcasecmp(elem->Value(), "hfield")) {
// hfield elements don't have children.
const char *file = elem->Attribute("file");
if (file != nullptr) {
hfield_files.emplace_back(file);
}
continue;
} else if (!strcasecmp(elem->Value(), "texture")) {
static const char *attributes[] = {"file", "fileright", "fileup",
"fileleft", "filedown", "filefront",
"fileback"};
for (const auto &attribute : attributes) {
const char *file = elem->Attribute(attribute);
if (file != nullptr) {
texture_files.emplace_back(file);
}
}
}
tinyxml2::XMLElement *child = elem->FirstChildElement();
while (child) {
elements.push(child);
child = child->NextSiblingElement();
}
}
tinyxml2::XMLElement *child = elem->FirstChildElement();
for (; child; child = child->NextSiblingElement()) {
AccumulateFilesRecursive(files, child, resolved_path);
}
accumulate_files(texture_files,
texture_dir == nullptr ? asset_dir : texture_dir);
accumulate_files(mesh_files, mesh_dir == nullptr ? asset_dir : mesh_dir);
accumulate_files(hfield_files, asset_dir);
}
void ResolveMjcfDependencies(const std::string &xml_string,
@@ -119,9 +207,9 @@ void ResolveMjcfDependencies(const std::string &xml_string,
// Accumulate file dependencies.
std::unordered_set<std::string> files = {};
AccumulateFilesRecursive(files, root, resolved_path);
AccumulateFiles(files, root, resolved_path);
auto open_asset = [resolved_path](const std::string &identifier) {
auto open_asset = [](const std::string &identifier) {
pxr::ArGetResolver().OpenAsset(pxr::ArGetResolver().Resolve(identifier));
};
// Open all assets in parallel.
@@ -187,6 +275,7 @@ bool UsdMjcfFileFormat::Read(pxr::SdfLayer *layer,
std::array<char, 1024> error;
mjSpec *spec =
mj_parseXML(resolved_path.c_str(), nullptr, error.data(), error.size());
if (spec == nullptr) {
TF_WARN(XmlParsingError, "%s", error.data());
return false;