Skip to content

Commit

Permalink
Merge pull request #1895 from seando-adsk/t_gamaj/USD-28/parse_materi…
Browse files Browse the repository at this point in the history
…alx_metadata

Parse MaterialX metadata into SdrProperty

(Internal change: 2261161)
  • Loading branch information
pixar-oss committed Jan 31, 2023
2 parents 77cc64b + 4545ae1 commit f50c8fa
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 20 deletions.
169 changes: 152 additions & 17 deletions pxr/usd/usdMtlx/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ TF_DEFINE_PRIVATE_TOKENS(

((discoveryType, "mtlx"))
((sourceType, ""))

(colorspace)
(defaultgeomprop)
(defaultinput)
(doc)
((enum_, "enum"))
(enumvalues)
(nodecategory)
(nodegroup)
(target)
(uifolder)
(uimax)
(uimin)
(uiname)
(uisoftmax)
(uisoftmin)
(uistep)
(unit)
(unittype)
(UV0)
);

// This environment variable lets users override the name of the primary
Expand All @@ -60,7 +80,7 @@ static const std::string _GetPrimaryUvSetName()
{
static const std::string env = TfGetEnvSetting(USDMTLX_PRIMARY_UV_NAME);
if (env.empty()) {
return UsdUtilsGetPrimaryUVSetName().GetString();
return UsdUtilsGetPrimaryUVSetName();
}
return env;
}
Expand Down Expand Up @@ -124,6 +144,86 @@ struct ShaderBuilder {
std::map<std::string, std::string> _propertyNameRemapping;
};

static
void
ParseMetadata(
NdrTokenMap& metadata,
const TfToken& key,
const mx::ConstElementPtr& element,
const std::string& attribute)
{
const auto& value = element->getAttribute(attribute);
if (!value.empty()) {
metadata.emplace(key, value);
}
}

static
void
ParseMetadata(
NdrTokenMap& metadata,
const TfToken& key,
const mx::ConstElementPtr& element)
{
const auto& value = element->getAttribute(key);
if (!value.empty()) {
metadata.emplace(key, value);
}
}

static
void
ParseOptions(
NdrOptionVec& options,
const mx::ConstElementPtr& element
)
{
const auto& enumLabels = element->getAttribute(_tokens->enum_);
if (enumLabels.empty()) {
return;
}

const auto& enumValues = element->getAttribute(_tokens->enumvalues);
std::vector<std::string> allLabels = UsdMtlxSplitStringArray(enumLabels);
std::vector<std::string> allValues = UsdMtlxSplitStringArray(enumValues);

if (!allValues.empty() && allValues.size() != allLabels.size()) {
// An array of vector2 values will produce twice the expected number of
// elements. We can fix that by regrouping them.
if (allValues.size() > allLabels.size() &&
allValues.size() % allLabels.size() == 0) {

size_t stride = allValues.size() / allLabels.size();
std::vector<std::string> rebuiltValues;
std::string currentValue;
for (size_t i = 0; i < allValues.size(); ++i) {
if (i % stride != 0) {
currentValue += mx::ARRAY_PREFERRED_SEPARATOR;
}
currentValue += allValues[i];
if ((i+1) % stride == 0) {
rebuiltValues.push_back(currentValue);
currentValue = "";
}
}
allValues.swap(rebuiltValues);
} else {
// Can not reconcile the size difference:
allValues.clear();
}
}

auto itLabels = allLabels.cbegin();
auto itValues = allValues.cbegin();
while (itLabels != allLabels.cend()) {
TfToken value;
if (itValues != allValues.cend()) {
value = TfToken(*itValues++);
}
options.emplace_back(TfToken(*itLabels++), value);
}
}

void
ShaderBuilder::AddProperty(
const mx::ConstTypedElementPtr& element,
Expand Down Expand Up @@ -177,27 +277,24 @@ ShaderBuilder::AddProperty(
}

// If this is an output then save the defaultinput, if any.
static const std::string defaultinputName("defaultinput");
if (isOutput) {
const auto& defaultinput = element->getAttribute(defaultinputName);
const auto& defaultinput = element->getAttribute(_tokens->defaultinput);
if (!defaultinput.empty()) {
metadata.emplace(SdrPropertyMetadata->DefaultInput, defaultinput);
}
}

// Record the targets on inputs.
static const std::string targetName("target");
if (!isOutput) {
const auto& target = element->getAttribute(targetName);
const auto& target = element->getAttribute(_tokens->target);
if (!target.empty()) {
metadata.emplace(SdrPropertyMetadata->Target, target);
}
}

// Record the colorspace on inputs and outputs.
static const std::string colorspaceName("colorspace");
if (isOutput || element->isA<mx::Input>()) {
const auto& colorspace = element->getAttribute(colorspaceName);
const auto& colorspace = element->getAttribute(_tokens->colorspace);
if (!colorspace.empty() &&
colorspace != element->getParent()->getActiveColorSpace()) {
metadata.emplace(SdrPropertyMetadata->Colorspace, colorspace);
Expand All @@ -208,18 +305,17 @@ ShaderBuilder::AddProperty(
auto name = element->getName();

// Record builtin primvar references for this node's inputs.
static const std::string defaultgeompropName("defaultgeomprop");
if (!isOutput && primvars != nullptr) {

// If an input has "defaultgeomprop", that means it reads from the
// primvar specified unless connected. We mark these in Sdr as
// always-required primvars; note that this means we might overestimate
// which primvars are referenced in a material.
const auto& defaultgeomprop = element->getAttribute(defaultgeompropName);
const auto& defaultgeomprop = element->getAttribute(_tokens->defaultgeomprop);
if (!defaultgeomprop.empty()) {
// Note: MaterialX uses a default texcoord of "UV0", which we
// inline replace with the configured default.
if (defaultgeomprop == "UV0") {
if (defaultgeomprop == _tokens->UV0) {
if (!addedTexcoordPrimvar) {
primvars->push_back(_GetPrimaryUvSetName());
}
Expand All @@ -233,7 +329,7 @@ ShaderBuilder::AddProperty(
// multiple outputs. The default name would be the name of the
// nodedef itself, which seems wrong. We pick a different name.
if (auto nodeDef = element->asA<mx::NodeDef>()) {
name = UsdMtlxTokens->DefaultOutputName.GetString();
name = UsdMtlxTokens->DefaultOutputName;
}

// Remap property name.
Expand All @@ -242,6 +338,45 @@ ShaderBuilder::AddProperty(
metadata[SdrPropertyMetadata->ImplementationName] = j->second;
}

if (!isOutput) {
ParseMetadata(metadata, SdrPropertyMetadata->Label, element, _tokens->uiname);
ParseMetadata(metadata, SdrPropertyMetadata->Help, element, _tokens->doc);
ParseMetadata(metadata, SdrPropertyMetadata->Page, element, _tokens->uifolder);

ParseMetadata(metadata, _tokens->uimin, element);
ParseMetadata(metadata, _tokens->uimax, element);
ParseMetadata(metadata, _tokens->uisoftmin, element);
ParseMetadata(metadata, _tokens->uisoftmax, element);
ParseMetadata(metadata, _tokens->uistep, element);
ParseMetadata(metadata, _tokens->unit, element);
ParseMetadata(metadata, _tokens->unittype, element);
ParseMetadata(metadata, _tokens->defaultgeomprop, element);

if (!metadata.count(SdrPropertyMetadata->Help) &&
metadata.count(_tokens->unit)) {
// The unit can be helpful if there is no documentation.
metadata.emplace(SdrPropertyMetadata->Help,
TfStringPrintf("Unit is %s.",
metadata[_tokens->unit].c_str()));
}

for (const auto& pair : metadata) {
const TfToken attrName = pair.first;
const std::string attrValue = pair.second;

const auto& allTokens = SdrPropertyMetadata->allTokens;
if (std::find(allTokens.begin(), allTokens.end(), attrName) !=
allTokens.end()) {
continue;
}

// Attribute hasn't been handled yet, so put it into the hints dict.
hints.insert({attrName, attrValue});
}

ParseOptions(options, element);
}

// Add the property.
properties.push_back(
SdrShaderPropertyUniquePtr(
Expand Down Expand Up @@ -319,10 +454,10 @@ ParseElement(ShaderBuilder* builder, const mx::ConstNodeDefPtr& nodeDef)

// Metadata
builder->metadata[SdrNodeMetadata->Label] = nodeDef->getNodeString();
ParseMetadata(builder, SdrNodeMetadata->Category, nodeDef, "nodecategory");
ParseMetadata(builder, SdrNodeMetadata->Help, nodeDef, "doc");
ParseMetadata(builder, SdrNodeMetadata->Target, nodeDef, "target");
ParseMetadata(builder, SdrNodeMetadata->Role, nodeDef, "nodegroup");
ParseMetadata(builder, SdrNodeMetadata->Category, nodeDef, _tokens->nodecategory);
ParseMetadata(builder, SdrNodeMetadata->Help, nodeDef, _tokens->doc);
ParseMetadata(builder, SdrNodeMetadata->Target, nodeDef, _tokens->target);
ParseMetadata(builder, SdrNodeMetadata->Role, nodeDef, _tokens->nodegroup);

// XXX -- version

Expand Down Expand Up @@ -385,7 +520,7 @@ ParseElement(ShaderBuilder* builder, const mx::ConstNodeDefPtr& nodeDef)
// Note: MaterialX uses a default texcoord of "UV0", which we
// inline replace with the configured default.
for (auto& name : split) {
if (name == "UV0") {
if (name == _tokens->UV0) {
name = _GetPrimaryUvSetName();
}
}
Expand Down Expand Up @@ -444,7 +579,7 @@ UsdMtlxParserPlugin::Parse(const NdrNodeDiscoveryResult& discoveryResult)
return GetInvalidNode(discoveryResult);
}

auto nodeDef = document->getNodeDef(discoveryResult.identifier.GetString());
auto nodeDef = document->getNodeDef(discoveryResult.identifier);
if (!nodeDef) {
TF_WARN("Invalid MaterialX NodeDef; unknown node name ' %s '",
discoveryResult.identifier.GetText());
Expand Down
36 changes: 36 additions & 0 deletions pxr/usd/usdMtlx/testenv/testUsdMtlxParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,42 @@ def test_NodeParser(self):
self.assertEqual(sorted(node.GetInputNames()), ["in", "note"])
self.assertEqual(node.GetOutputNames(), ['out'])

# Verify some metadata:
node = Sdr.Registry().GetShaderNodeByIdentifier(
'UsdMtlxTestNamespace:nd_vector')
self.assertEqual(node.GetHelp(), "Vector help")
# Properties without a Page metadata end up in an unnamed page. This
# means that all MaterialX outputs will be assigned to the unnamed page
# when the metadata is used.
self.assertEqual(node.GetPages(), ["UI Page", ""])
self.assertEqual(node.GetPropertyNamesForPage("UI Page"), ["in",])
self.assertEqual(node.GetPropertyNamesForPage(""), ["note", "out"])
input = node.GetInput("in")
self.assertEqual(input.GetHelp(), "Property help")
self.assertEqual(input.GetLabel(), "UI Vector")
self.assertEqual(input.GetPage(), "UI Page")
self.assertEqual(input.GetOptions(),
[("X", "1, 0, 0"), ("Y", "0, 1, 0"), ("Z", "0, 0, 1")])

node = Sdr.Registry().GetShaderNodeByIdentifier(
'UsdMtlxTestNamespace:nd_float')
expected = {
"uimin": "-360.0",
"uimax": "360.0",
"uisoftmin": "0.0",
"uisoftmax": "180.0",
"uistep": "1.0",
"unittype": "angle",
"unit": "degree"
}
input = node.GetInput("in")
self.assertEqual(input.GetHelp(), "Unit is degree.")
hints = input.GetHints()
metadata = input.GetMetadata()
for key in expected.keys():
self.assertEqual(hints[key], expected[key])
self.assertEqual(metadata[key], expected[key])

# Verify converted types.
typeNameMap = {
'boolean': 'bool',
Expand Down
6 changes: 3 additions & 3 deletions pxr/usd/usdMtlx/testenv/testUsdMtlxParser.testenv/test.mtlx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<output name="out" type="integer" />
</nodedef>
<nodedef name="nd_float" node="UsdMtlxTestNode">
<input name="in" type="float" />
<input name="in" type="float" uimin="-360.0" uimax="360.0" uisoftmin="0.0" uisoftmax="180.0" uistep="1.0" unittype="angle" unit="degree" />
<input name="note" type="string" value="" uniform="true" />
<output name="out" type="float" />
</nodedef>
Expand All @@ -15,8 +15,8 @@
<input name="note" type="string" value="" uniform="true" />
<output name="out" type="string" />
</nodedef>
<nodedef name="nd_vector" node="UsdMtlxTestNode">
<input name="in" type="vector3" />
<nodedef name="nd_vector" node="UsdMtlxTestNode" doc="Vector help">
<input name="in" type="vector3" enum="X,Y,Z" enumvalues="1,0,0, 0,1,0, 0,0,1" doc="Property help" uiname="UI Vector" uifolder="UI Page" />
<input name="note" type="string" value="" uniform="true" />
<output name="out" type="vector3" />
</nodedef>
Expand Down

0 comments on commit f50c8fa

Please sign in to comment.