diff --git a/pxr/usd/usdMtlx/parser.cpp b/pxr/usd/usdMtlx/parser.cpp index e009159303..235bf62cbb 100644 --- a/pxr/usd/usdMtlx/parser.cpp +++ b/pxr/usd/usdMtlx/parser.cpp @@ -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 @@ -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; } @@ -124,6 +144,86 @@ struct ShaderBuilder { std::map _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 allLabels = UsdMtlxSplitStringArray(enumLabels); + std::vector 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 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, @@ -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()) { - 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); @@ -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()); } @@ -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()) { - name = UsdMtlxTokens->DefaultOutputName.GetString(); + name = UsdMtlxTokens->DefaultOutputName; } // Remap property name. @@ -242,6 +338,46 @@ 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. Please note that this MaterialX" + " unit can diverge from USD norms.", + 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( @@ -319,10 +455,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 @@ -385,7 +521,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(); } } @@ -444,7 +580,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()); diff --git a/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.py b/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.py index dba942f8d2..433836dc55 100644 --- a/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.py +++ b/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.py @@ -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', diff --git a/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.testenv/test.mtlx b/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.testenv/test.mtlx index c908bb503d..540a2649a7 100644 --- a/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.testenv/test.mtlx +++ b/pxr/usd/usdMtlx/testenv/testUsdMtlxParser.testenv/test.mtlx @@ -6,7 +6,7 @@ - + @@ -15,8 +15,8 @@ - - + +