From 4bad134ba22171f012d212b6f3a469cd23774e56 Mon Sep 17 00:00:00 2001 From: Sam Haves Date: Mon, 18 Aug 2025 11:37:27 -0700 Subject: [PATCH] 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 --- .../usd/plugins/mjcf/mjcf_file_format.cc | 155 ++++++++++++++---- 1 file changed, 122 insertions(+), 33 deletions(-) diff --git a/src/experimental/usd/plugins/mjcf/mjcf_file_format.cc b/src/experimental/usd/plugins/mjcf/mjcf_file_format.cc index e38b2c74..dff797a9 100644 --- a/src/experimental/usd/plugins/mjcf/mjcf_file_format.cc +++ b/src/experimental/usd/plugins/mjcf/mjcf_file_format.cc @@ -16,8 +16,10 @@ #include #include +#include #include #include +#include #include #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 &files, - tinyxml2::XMLElement *elem, - const std::string &resolved_path) { - // get filename - const char *file = elem->Attribute("file"); +void AccumulateFiles(std::unordered_set &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 texture_files; + std::vector mesh_files; + std::vector 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 &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 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 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 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;