From c6e982cfacc89246080d217dac14702f4b40ba98 Mon Sep 17 00:00:00 2001 From: Lou Rohan Date: Fri, 30 Jan 2026 10:28:13 -0600 Subject: [PATCH] Add support for USD material interfaces - use GetValueProducingAttributes() for improved input resolution. - use std::filesystem to avoid leaking file handles --- plugin/usd_decoder/material_parsing.cc | 124 ++++++++++++++----------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/plugin/usd_decoder/material_parsing.cc b/plugin/usd_decoder/material_parsing.cc index 259d0345..100993fb 100644 --- a/plugin/usd_decoder/material_parsing.cc +++ b/plugin/usd_decoder/material_parsing.cc @@ -15,6 +15,7 @@ #include "material_parsing.h" #include +#include #include #include @@ -24,15 +25,13 @@ #include #include #include -#include -#include -#include #include #include #include #include #include #include +#include // Using to satisfy TF_DEFINE_PRIVATE_TOKENS macro below and avoid operating in // PXR_NS. @@ -119,69 +118,57 @@ ResolvedShaderInput ReadUsdUVTexture(mjSpec* spec, texture->colorspace = mjtColorSpace::mjCOLORSPACE_AUTO; } - pxr::ArResolver& resolver = pxr::ArGetResolver(); pxr::SdfAssetPath resolved_texture_asset_path; + if (auto file_input = shader.GetInput(kTokens->file)) { - file_input.Get(&resolved_texture_asset_path); + // Use GetValueProducingAttributes to follow all connections including + // material interfaces. + auto value_attrs = pxr::UsdShadeUtils::GetValueProducingAttributes(file_input); + for (const auto& attr : value_attrs) { + if (attr.Get(&resolved_texture_asset_path) && + !resolved_texture_asset_path.GetAssetPath().empty()) { + break; + } + } } else { mju_error("UsdUVTexture missing inputs:file."); return out; } - std::string resolved_texture_path = - resolved_texture_asset_path.GetResolvedPath(); - - if (pxr::UsdShadeUdimUtils::IsUdimIdentifier(resolved_texture_path)) { + // Use the resolved path from USD, fall back to asset path if not resolved + std::string resolved_path_str = resolved_texture_asset_path.GetResolvedPath(); + if (resolved_path_str.empty()) { + resolved_path_str = resolved_texture_asset_path.GetAssetPath(); + } + + if (resolved_path_str.empty()) { + mju_warning("UsdUVTexture %s: No texture file path specified.", + shader.GetPath().GetAsString().c_str()); + return out; + } + + if (pxr::UsdShadeUdimUtils::IsUdimIdentifier(resolved_path_str)) { mju_error("MuJoCo does not support UDIM textures: %s", - resolved_texture_path.c_str()); + resolved_path_str.c_str()); return out; } - auto extension = resolver.GetExtension(resolved_texture_path); - - FILE* fp = fopen(resolved_texture_path.c_str(), "r"); - if (fp == nullptr) { - mju_error( - "USD decoder only supports assets that are available on the file " - "system"); + if (!std::filesystem::exists(resolved_path_str)) { + mju_warning( + "USD decoder only supports assets that are available on the file " + "system. Could not open: %s", resolved_path_str.c_str()); return out; } + texture->nchannel = nchannels; - mjs_setString(texture->file, resolved_texture_path.c_str()); + mjs_setString(texture->file, resolved_path_str.c_str()); out.sampler = texture; return out; } -// Given an input to a shader, attempts to bake the shader and it's inputs -// into a singular value or texture sampler (ResolvedShaderInput). -template -ResolvedShaderInput ReadShaderInput( - mjSpec* spec, const pxr::UsdShadeConnectableAPI source, - const pxr::TfToken source_name, - const pxr::UsdShadeAttributeType source_type) { - ResolvedShaderInput out; - pxr::UsdShadeShader shader(source.GetPrim()); - pxr::TfToken shader_id = GetShaderId(shader); - if (shader_id == kTokens->UsdUVTexture) { - unsigned nchannels = -1; - if (source_name == kTokens->rgb) { - nchannels = 3; - } else if (source_name == kTokens->r) { - nchannels = 1; - } else { - mju_error("Unsupported texture channel: %s", source_name.GetText()); - return out; - } - - return ReadUsdUVTexture(spec, shader, nchannels); - } else { - mju_warning("Unsupported shader type: %s", shader_id.GetText()); - } - - return out; -} - // Reads UsdShadeInput as a value of type T or a texture sampler. +// Uses GetValueProducingAttributes to follow all connections including +// material interfaces. template ResolvedShaderInput ReadShaderInput(mjSpec* spec, pxr::UsdShadeInput input) { ResolvedShaderInput out; @@ -189,14 +176,43 @@ ResolvedShaderInput ReadShaderInput(mjSpec* spec, pxr::UsdShadeInput input) { return out; } - pxr::UsdShadeConnectableAPI source; - pxr::TfToken source_name; - pxr::UsdShadeAttributeType source_type; - if (input.GetConnectedSource(&source, &source_name, &source_type)) { - out = ReadShaderInput(spec, source, source_name, source_type); - } else { - out.value = ReadInput(input); + // GetValueProducingAttributes follows connections recursively, including + // through material interfaces, and returns the attributes that produce values. + auto value_attrs = pxr::UsdShadeUtils::GetValueProducingAttributes(input); + + for (const auto& attr : value_attrs) { + // Check if this attribute belongs to a UsdUVTexture shader (for textures) + pxr::UsdPrim prim = attr.GetPrim(); + pxr::UsdShadeShader shader(prim); + if (shader) { + pxr::TfToken shader_id = GetShaderId(shader); + if (shader_id == kTokens->UsdUVTexture) { + // Determine channel count from the output name + pxr::TfToken attr_name = attr.GetBaseName(); + unsigned nchannels = 3; // default + if (attr_name == kTokens->rgb) { + nchannels = 3; + } else if (attr_name == kTokens->r) { + nchannels = 1; + } + out = ReadUsdUVTexture(spec, shader, nchannels); + if (out.sampler.has_value()) { + return out; + } + } + // For other shader types, fall through to try reading as a value + } + + // Try to read as a direct value + out.value = ReadInput(pxr::UsdShadeInput(attr)); + if (out.value.has_value()) { + return out; + } } + + // If no value-producing attributes found, try reading the input directly + // (for non-connected inputs with authored values) + out.value = ReadInput(input); return out; }