Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse MaterialX metadata into SdrProperty #1895

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 153 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)
spiffmon marked this conversation as resolved.
Show resolved Hide resolved
(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,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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what unittype is, but would it also be useful? Seems like defaultgeomprop would also be handy to provide?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see only two places in MaterialX where unittype is defined. There are a few angles defined as being "unittype = radian" and one thin_film thickness being tagged with "unittype = nanometer". I suspect ST coordinates could be in meters in some future, but we are not there yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh hmmm... these will want to be very prominently documented, as they diverge from USD norms. All angles are degrees in USD schemas, and linear units are generally relative to stage metersPerUnit . So yeah, I'd double-down on wanting to fold unittype into the description, if the nodes that use them don't already provide documentation that mentions them!

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(
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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());
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