Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion documents/Specification/MaterialX.Specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ Attributes for <nodedef> elements:
* `inherit` (string, optional): the `name` of a <nodedef> to inherit node definitions from; the output types of this nodedef and the inherited one must match, and the input/output definitions of this nodedef will be applied on top of those in the inherited-from one.
* `nodegroup` (string, optional): an optional group to which this node declaration belongs. Standard MaterialX nodes have `nodegroup` values matching the titles of the section headings in which they are described, e.g. "texture2d", "procedural", "geometric", "application", "math", "adjustment", "compositing", "conditional", "channel", "convolution", or "organization".
* `version` (string, optional): a version string for this nodedef, allowing usage of a node to reference a specific version of a node. Version strings should be of the format "_major_[._minor_]", i.e. one or two integer numbers separated by a dot (the minor version is assumed to be "0" if not provided). If there are multiple nodedefs for the same `node` and `target` with the same combination of input and output types, they must each specify a `version`.
* `isdefaultversion` (boolean, optional): If true, then this nodedef should be used for node instances which do not request a specific version. Specifying `isdefaultversion` "true" is only required if there are multiple nodedefs for a node declaring a `version`, and it is not permissible for multiple nodedefs for the same `node` and `target` with the same combination of input and output types to set `isdefaultversion` "true". Defaults to "false".
* `isdefaultversion` (boolean, optional): If true, then this nodedef should be used for node instances that do not request a specific version when the declaration is ambiguous. Ambiguity includes cases where a node instance is declared with insufficient inputs to determine a unique nodedef signature, or where multiple nodedef versions are available and no explicit version is specified on the node instance. In these cases, the nodedef with `isdefaultversion` "true" indicates which definition is instantiated. It is not permissible for multiple nodedefs for the same `node` and `target` with the same combination of input and output types to set `isdefaultversion` "true". Defaults to "false".
* `target` (stringarray, optional): the set of targets to which this nodedef is restricted. By default, a nodedef is considered universal, not restricted to any specific targets, but it is possible that certain targets may have different parameter names or usage for the same node.
* `uiname` (string, optional): an alternative "node" value for this nodedef to be displayed in the UI. If `uiname` is not provided, then `node` is the presumed UI node value for the nodedef. This is most useful when the <nodedef> defines a namespace, so the user doesn't need to see a full namespaced path for the node.
* `internalgeomprops` (stringarray, optional): a list of MaterialX geometric properties (e.g. "position", "normal", "texcoord", etc. or any name defined by a <geompropdef> element) that the node expects to be able to access internally. This metadata hint allows code generators to ensure this data is available and can be used for error checking. `Internalgeomprops` is most useful for nodes whose implementation is defined by external code; it is not necessary for nodegraph-defined nodes, as the list of geometric properties accessed can be determined by examining the nodegraph.
Expand Down
28 changes: 27 additions & 1 deletion source/MaterialXCore/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ NodeDefPtr Node::getNodeDef(const string& target, bool allowRoughMatch) const
}
vector<NodeDefPtr> nodeDefs = getDocument()->getMatchingNodeDefs(getQualifiedName(getCategory()));
vector<NodeDefPtr> secondary = getDocument()->getMatchingNodeDefs(getCategory());
vector<NodeDefPtr> exactMatches;
vector<NodeDefPtr> roughMatches;
nodeDefs.insert(nodeDefs.end(), secondary.begin(), secondary.end());
for (NodeDefPtr nodeDef : nodeDefs)
Expand All @@ -105,10 +106,35 @@ NodeDefPtr Node::getNodeDef(const string& target, bool allowRoughMatch) const
}
continue;
}
return nodeDef;
exactMatches.push_back(nodeDef);
}

if (!exactMatches.empty())
{
if (getVersionString().empty())
{
for (NodeDefPtr nodeDef : exactMatches)
{
if (nodeDef->getDefaultVersion())
{
return nodeDef;
}
}
}
return exactMatches[0];
}
if (!roughMatches.empty())
{
if (getVersionString().empty())
{
for (NodeDefPtr nodeDef : roughMatches)
{
if (nodeDef->getDefaultVersion())
{
return nodeDef;
}
}
}
return roughMatches[0];
}
return NodeDefPtr();
Expand Down
64 changes: 64 additions & 0 deletions source/MaterialXTest/MaterialXCore/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,70 @@ TEST_CASE("Inheritance", "[nodedef]")
nodedefSpecularInput->getAttribute(mx::ValueElement::VALUE_ATTRIBUTE));
}

TEST_CASE("Default Instance", "[nodedef]")
{
mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath();
mx::DocumentPtr doc = mx::createDocument();
mx::loadLibraries({ "libraries" }, searchPath, doc);
REQUIRE(doc->validate());

// When a node instance has no explicit version and multiple exact matches
// are available, prefer a single nodedef marked as the default version.
mx::NodeDefPtr ambiguousDefV1 = doc->addNodeDef("ND_ambiguous_a", "float", "ambiguous");
ambiguousDefV1->setNodeGroup(mx::NodeDef::PROCEDURAL_NODE_GROUP);
ambiguousDefV1->setVersionString("1.0");
ambiguousDefV1->addInput("value", "float");
mx::NodePtr ambiguousNodeV1 = doc->addNode("ambiguous", mx::EMPTY_STRING, "float");
ambiguousNodeV1->setVersionString("1.0");
REQUIRE(ambiguousNodeV1->getNodeDef() == ambiguousDefV1);

mx::NodeDefPtr ambiguousDefV2 = doc->addNodeDef("ND_ambiguous_b", "float", "ambiguous");
ambiguousDefV2->setNodeGroup(mx::NodeDef::PROCEDURAL_NODE_GROUP);
ambiguousDefV2->setDefaultVersion(true);
ambiguousDefV2->setVersionString("2.0");
ambiguousDefV2->addInput("value", "float");

mx::NodePtr ambiguousNodeV2 = doc->addNode("ambiguous", mx::EMPTY_STRING, "float");
ambiguousNodeV2->addInput("value", "float");
REQUIRE(ambiguousNodeV2->getNodeDef() == ambiguousDefV2);

// Modify <normalmap> make second nodedef in the list the default version.
// instead of being order dependent resulting in choosing
// the first one found in Node::getNodeDef().
mx::NodePtr normalmapNode = doc->addNode("normalmap", mx::EMPTY_STRING, "vector3");
normalmapNode->addInputsFromNodeDef();
mx::InputPtr normalmapNodeScaleInput = normalmapNode->getInput("scale");
const std::string previousScaleType = normalmapNodeScaleInput->getType();

std::vector<mx::NodeDefPtr> nodeDefs = doc->getMatchingNodeDefs(normalmapNode->getQualifiedName(normalmapNode->getCategory()));
std::string defaultScaleType = mx::EMPTY_STRING;
bool isFirstNodeDef = true;
for (auto nodeDef : nodeDefs)
{
if (isFirstNodeDef)
{
nodeDef->setDefaultVersion(false);
isFirstNodeDef = false;
continue;
}

nodeDef->setDefaultVersion(true);
mx::InputPtr scaleInput = nodeDef->getInput("scale");
if (defaultScaleType.empty() && scaleInput)
{
defaultScaleType = scaleInput->getType();
}
}

// Check new logic and compare against definition returned with a default being set
mx::NodePtr normalmapNodeDefault = doc->addNode("normalmap", mx::EMPTY_STRING, "vector3");
normalmapNodeDefault->addInputsFromNodeDef();
mx::InputPtr normalmapNodeDefaultScaleInput = normalmapNodeDefault->getInput("scale");
REQUIRE(normalmapNodeDefaultScaleInput->getType() == defaultScaleType);

REQUIRE(defaultScaleType != previousScaleType);
}

TEST_CASE("Topological sort", "[nodegraph]")
{
// Create a document.
Expand Down
Loading