Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f8ceb63
Initial test.
kwokcb May 21, 2025
a9cc157
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb May 22, 2025
b96d4cd
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Jun 5, 2025
814e524
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Jul 8, 2025
179a6d6
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Jul 15, 2025
36a154f
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Aug 19, 2025
cf7d0f2
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Aug 26, 2025
8ef6904
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Sep 1, 2025
c52561b
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Sep 3, 2025
cad97bc
Add create options and unit test.
kwokcb Sep 5, 2025
5ac34e5
Add doc for DefinitionsOptions arg on addNodeDefFromGraph().
kwokcb Sep 7, 2025
4dc3ee1
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Sep 12, 2025
7add695
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Sep 16, 2025
df75cae
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Oct 5, 2025
f3b5985
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Oct 14, 2025
cb430ab
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Oct 23, 2025
ece55fc
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Oct 25, 2025
ad477ee
Review updates
kwokcb Oct 25, 2025
8caf533
Fix Windows test warnings.
kwokcb Oct 25, 2025
4057ff9
Add Javascript bindings + unit test.
kwokcb Oct 28, 2025
7e12bf1
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Nov 3, 2025
231bcce
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Nov 10, 2025
6fc3a7b
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Nov 11, 2025
d999875
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Nov 12, 2025
aa3ca63
Merge branch 'main' into functional_nodedefs
kwokcb Nov 15, 2025
d24b4f1
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Nov 17, 2025
fc97603
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Nov 22, 2025
5f62af2
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Nov 24, 2025
8905df3
Merge branch 'AcademySoftwareFoundation:main' into functional_nodedefs
kwokcb Dec 3, 2025
f481f6f
Merge branch 'main' into functional_nodedefs
kwokcb Dec 9, 2025
0018a27
Merge remote-tracking branch 'upstream/main' into functional_nodedefs
kwokcb Dec 13, 2025
dd352e6
Add inlining, inheritance, and shared implementation logic and more …
kwokcb Dec 13, 2025
a3ed34d
Merge remote-tracking branch 'upstream/main' into functional_nodedefs
kwokcb Dec 17, 2025
3e70748
Auto sync.
kwokcb Dec 17, 2025
d6346f8
Merge remote-tracking branch 'upstream/main' into functional_nodedefs
kwokcb Dec 29, 2025
f9a5603
Fix merge.
kwokcb Dec 29, 2025
f235733
Merge remote-tracking branch 'aswf/main' into functional_nodedefs
kwokcb Jan 6, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0"?>
<materialx version="1.39">
<nodegraph name="NG_test_colorcorrect_reference">
<multiply name="AlphaGain" type="float">
<input name="in1" type="float" nodename="inputAlpha" />
<input name="in2" type="float" interfacename="AlphaGain_in2" />
</multiply>
<add name="AlphaOffset" type="float">
<input name="in1" type="float" nodename="AlphaGain" />
<input name="in2" type="float" interfacename="AlphaOffset_in2" />
</add>
<multiply name="ColorGain" type="color3">
<input name="in1" type="color3" nodename="inputColor" />
<input name="in2" type="color3" interfacename="ColorGain_in2" />
</multiply>
<add name="ColorOffset" type="color3">
<input name="in1" type="color3" nodename="ColorGain" />
<input name="in2" type="color3" interfacename="ColorOffset_in2" />
</add>
<constant name="inputColor" type="color3">
<input name="value" type="color3" interfacename="inputColor_value" />
</constant>
<constant name="inputAlpha" type="float">
<input name="value" type="float" interfacename="inputAlpha_value" />
</constant>
<output name="out" type="color3" nodename="ColorOffset" />
<output name="out1" type="float" nodename="AlphaOffset" />
<input name="AlphaGain_in2" type="float" value="0.8" uiname="AlphaGain in2" uifolder="Common" />
<input name="AlphaOffset_in2" type="float" value="1" uiname="AlphaOffset in2" uifolder="Common" />
<input name="ColorGain_in2" type="color3" value="0.9, 0.9, 0.9" uiname="ColorGain in2" uifolder="Common" />
<input name="ColorOffset_in2" type="color3" value="0.379147, 0.0341412, 0.0341412" uiname="ColorOffset in2" uifolder="Common" />
<input name="inputColor_value" type="color3" value="0.5, 0.5, 0.5" uiname="inputColor value" uifolder="Common" />
<input name="inputAlpha_value" type="float" value="1" uiname="inputAlpha value" uifolder="Common" />
</nodegraph>
<nodedef name="ND_test_colorcorrect" node="test_colorcorrect" version="1.0" isdefaultversion="false" nodegroup="adjustment" uiname="test_colorcorrect Version: 1.0" doc="This is version 1 of the definition for the graph: NG_test_colorcorrect">
<nodegraph name="NG_test_colorcorrect">
<multiply name="AlphaGain" type="float">
<input name="in1" type="float" nodename="inputAlpha" />
<input name="in2" type="float" interfacename="AlphaGain_in2" />
</multiply>
<add name="AlphaOffset" type="float">
<input name="in1" type="float" nodename="AlphaGain" />
<input name="in2" type="float" interfacename="AlphaOffset_in2" />
</add>
<multiply name="ColorGain" type="color3">
<input name="in1" type="color3" nodename="inputColor" />
<input name="in2" type="color3" interfacename="ColorGain_in2" />
</multiply>
<add name="ColorOffset" type="color3">
<input name="in1" type="color3" nodename="ColorGain" />
<input name="in2" type="color3" interfacename="ColorOffset_in2" />
</add>
<constant name="inputColor" type="color3">
<input name="value" type="color3" interfacename="inputColor_value" />
</constant>
<constant name="inputAlpha" type="float">
<input name="value" type="float" interfacename="inputAlpha_value" />
</constant>
<output name="out" type="color3" nodename="ColorOffset" />
<output name="out1" type="float" nodename="AlphaOffset" />
</nodegraph>
<input name="AlphaGain_in2" type="float" value="0.8" uiname="AlphaGain in2" uifolder="Common" />
<input name="AlphaOffset_in2" type="float" value="1" uiname="AlphaOffset in2" uifolder="Common" />
<input name="ColorGain_in2" type="color3" value="0.9, 0.9, 0.9" uiname="ColorGain in2" uifolder="Common" />
<input name="ColorOffset_in2" type="color3" value="0.379147, 0.0341412, 0.0341412" uiname="ColorOffset in2" uifolder="Common" />
<input name="inputColor_value" type="color3" value="0.5, 0.5, 0.5" uiname="inputColor value" uifolder="Common" />
<input name="inputAlpha_value" type="float" value="1" uiname="inputAlpha value" uifolder="Common" />
<output name="out" type="color3" />
<output name="out1" type="float" />
</nodedef>
<test_colorcorrect name="test_colorcorrect_instance" version="1.0" type="multioutput">
<input name="inputColor_value" type="color3" value="0.0, 0.0, 0.0" />
<input name="ColorOffset_in2" type="color3" value="1.0, 0.0, 0.0" />
<output name="out" type="color3" />
</test_colorcorrect>
<surface_unlit name="surface_unlit" type="surfaceshader">
<input name="emission_color" type="color3" output="out" nodename="test_colorcorrect_instance" />
</surface_unlit>
</materialx>
6 changes: 6 additions & 0 deletions source/MaterialXCore/Definition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ void Implementation::setNodeDef(ConstNodeDefPtr nodeDef)

NodeDefPtr Implementation::getNodeDef() const
{
ElementPtr parent = getSelfNonConst()->getParent();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"Backwards" search looks for parent definitions now.

NodeDefPtr nodedef = parent->asA<NodeDef>();
if (nodedef)
{
return nodedef;
}
return resolveNameReference<NodeDef>(getNodeDefString());
}

Expand Down
42 changes: 35 additions & 7 deletions source/MaterialXCore/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Document::Cache
portElementMap.clear();
nodeDefMap.clear();
implementationMap.clear();
std::unordered_map<string, std::vector<InterfaceElementPtr>> funcNodeDefMap;

// Traverse the document to build a new cache.
for (ElementPtr elem : doc.lock()->traverseTree())
Expand Down Expand Up @@ -79,6 +80,14 @@ class Document::Cache
if (nodeDef)
{
nodeDefMap[nodeDef->getQualifiedName(nodeString)].push_back(nodeDef);

// Look for child nodegraph implementations
vector<NodeGraphPtr> nodeGraphs = nodeDef->getChildrenOfType<NodeGraph>();
for (NodeGraphPtr nodeGraph : nodeGraphs)
{
funcNodeDefMap[elem->getName()].push_back(nodeGraph);
}

}
}
if (!nodeDefString.empty())
Expand Down Expand Up @@ -110,6 +119,16 @@ class Document::Cache
}
}

// Functional node definitions have lower precedence than non-functional ones.
// So append them to the back of implementation list associated
// with any existing nodedef entry (or create a new one if does not exist).
//
for (const auto& [nodedefKey, appendImplementations] : funcNodeDefMap)
{
auto& implementations = implementationMap[nodedefKey];
implementations.insert(implementations.end(), appendImplementations.begin(), appendImplementations.end());
}

valid = true;
}
}
Expand Down Expand Up @@ -147,7 +166,8 @@ void Document::initialize()
}

NodeDefPtr Document::addNodeDefFromGraph(NodeGraphPtr nodeGraph, const string& nodeDefName,
const string& category, const string& newGraphName)
const string& category, const string& newGraphName,
DefinitionOptions *options)
{
if (category.empty())
{
Expand All @@ -159,26 +179,34 @@ NodeDefPtr Document::addNodeDefFromGraph(NodeGraphPtr nodeGraph, const string& n
throw Exception("Cannot create duplicate nodedef: " + nodeDefName);
}

if (getNodeGraph(newGraphName))
bool addAsChild = options ? options->addImplementationAsChild : false;

if (!addAsChild && getNodeGraph(newGraphName))
{
throw Exception("Cannot create duplicate nodegraph: " + newGraphName);
}

// Create a new nodedef and set its category
NodeDefPtr nodeDef = addNodeDef(nodeDefName, EMPTY_STRING);
nodeDef->setNodeString(category);

// Create a new functional nodegraph, and copy over the
// contents from the compound nodegraph
NodeGraphPtr graph = addNodeGraph(newGraphName);
NodeGraphPtr graph = !addAsChild ? addNodeGraph(newGraphName) : nodeDef->addChild<NodeGraph>(newGraphName);
graph->copyContentFrom(nodeGraph);

for (auto graphChild : graph->getChildren())
{
graphChild->removeAttribute(Element::XPOS_ATTRIBUTE);
graphChild->removeAttribute(Element::YPOS_ATTRIBUTE);
}
graph->setNodeDefString(nodeDefName);

// Create a new nodedef and set its category
NodeDefPtr nodeDef = addNodeDef(nodeDefName, EMPTY_STRING);
nodeDef->setNodeString(category);
// Reference from nodegraph to nodedef is not required
// as the graph is a child of the nodedef.
if (!addAsChild)
{
graph->setNodeDefString(nodeDefName);
}

// Expose any existing interfaces from the graph.
// Any connection attributes ("nodegraph", "nodename", "interfacename") on the
Expand Down
15 changes: 14 additions & 1 deletion source/MaterialXCore/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ using DocumentPtr = shared_ptr<Document>;
/// A shared pointer to a const Document
using ConstDocumentPtr = shared_ptr<const Document>;

class DefinitionOptions;

/// @class Document
/// A MaterialX document, which represents the top-level element in the
/// MaterialX ownership hierarchy.
Expand Down Expand Up @@ -361,9 +363,11 @@ class MX_CORE_API Document : public GraphElement
/// @param newGraphName Name of new functional NodeGraph.
/// @param nodeDefName Name of new NodeDef
/// @param category Category of the new NodeDef
/// @param options Optional creation options. Default is nullptr.
/// @return New declaration if successful.
NodeDefPtr addNodeDefFromGraph(NodeGraphPtr nodeGraph, const string& nodeDefName,
const string& category, const string& newGraphName);
const string& category, const string& newGraphName,
DefinitionOptions * options = nullptr);

/// Return the NodeDef, if any, with the given name.
NodeDefPtr getNodeDef(const string& name) const
Expand Down Expand Up @@ -708,6 +712,15 @@ class MX_CORE_API Document : public GraphElement
std::unique_ptr<Cache> _cache;
};

/// @class DefinitionOptions
/// Options for defining a NodeDef from an immplementation
class MX_CORE_API DefinitionOptions
{
public:
/// Add implementation as child of NodeDef as opposed a sibliing.
bool addImplementationAsChild = false;
};

/// Create a new Document.
/// @relates Document
MX_CORE_API DocumentPtr createDocument();
Expand Down
9 changes: 8 additions & 1 deletion source/MaterialXCore/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,14 @@ void NodeGraph::modifyInterfaceName(const string& inputPath, const string& inter

NodeDefPtr NodeGraph::getNodeDef() const
{
NodeDefPtr nodedef = resolveNameReference<NodeDef>(getNodeDefString());
ElementPtr parent = getSelfNonConst()->getParent();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"Backwards" search looks for parent definitions now.

NodeDefPtr nodedef = parent->asA<NodeDef>();
if (nodedef)
{
return nodedef;
}

nodedef = resolveNameReference<NodeDef>(getNodeDefString());
// If not directly defined look for an implementation which has a nodedef association
if (!nodedef)
{
Expand Down
98 changes: 94 additions & 4 deletions source/MaterialXTest/MaterialXCore/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <MaterialXFormat/XmlIo.h>
#include <MaterialXFormat/Util.h>

#include <iostream>

namespace mx = MaterialX;

bool isTopologicalOrder(const std::vector<mx::ElementPtr>& elems)
Expand Down Expand Up @@ -675,7 +677,64 @@ TEST_CASE("Organization", "[nodegraph]")
CHECK(nodeGraph->getBackdrops().empty());
}

TEST_CASE("Node Definition Creation", "[nodedef]")
void testFunctionalNodeDef()
{
mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath();
mx::DocumentPtr doc = mx::createDocument();
mx::readFromXmlFile(doc, "resources/Materials/TestSuite/stdlib/definition/functional_nodedef.mtlx", searchPath);

std::vector<mx::NodeDefPtr> nodedefs = doc->getNodeDefs();
for (mx::NodeDefPtr nodeDef : nodedefs)
{
std::string nodeDefName = nodeDef->getName();
std::string nodeGraphName = "NG_" + nodeDefName.substr(3); // Remove the 'ND_' prefix

mx::InterfaceElementPtr implementation = nodeDef->getImplementation();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Basic child nodegraph implementation search test.

REQUIRE(implementation != nullptr);
std::string msg = "Testing NodeDef: " + nodeDefName + " with implementation: " + implementation->getName();
INFO(msg);
mx::NodeGraphPtr functionalNodeGraph = implementation->asA<mx::NodeGraph>();
REQUIRE(functionalNodeGraph != nullptr);
if (functionalNodeGraph)
{
// Test that the child nodegraph is found via implementation search
REQUIRE(functionalNodeGraph->getName() == nodeGraphName);

// Test that this is actually a child nodegraph of the NodeDef
std::vector<mx::NodeGraphPtr> childNodeGraphs = nodeDef->getChildrenOfType<mx::NodeGraph>();
for (mx::NodeGraphPtr childNodeGraph : childNodeGraphs)
{
if (childNodeGraph->getName() == nodeGraphName)
{
// Test that the child nodegraph is the functional graph
REQUIRE(childNodeGraph == functionalNodeGraph);
}
}
}

std::string referenceGraphName = nodeGraphName + "_reference";
mx::NodeGraphPtr referenceNodeGraph = doc->getNodeGraph(referenceGraphName);
REQUIRE(referenceNodeGraph != nullptr);
if (referenceNodeGraph)
{
referenceNodeGraph->setNodeDefString(nodeDefName);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Superscede the child nodegraph impl with one that is not a child. As all changes cause a nodedef->impl cache rebuild this allows for this dynamic behaviour.


mx::InterfaceElementPtr implementation = nodeDef->getImplementation();
REQUIRE(implementation != nullptr);
std::string msg = "Testing NodeDef: " + nodeDefName + " with implementation: " + implementation->getName();
INFO(msg);
mx::NodeGraphPtr functionalNodeGraph = implementation->asA<mx::NodeGraph>();
REQUIRE(functionalNodeGraph != nullptr);
if (functionalNodeGraph)
{
// Test the functional node graph is the reference graph
REQUIRE(functionalNodeGraph->getName() == referenceGraphName);
}
}
}
}

void testNodeDefCreationFromGraph(mx::DefinitionOptions options)
{
mx::FileSearchPath searchPath = mx::getDefaultDataSearchPath();
mx::DocumentPtr stdlib = mx::createDocument();
Expand Down Expand Up @@ -714,10 +773,12 @@ TEST_CASE("Node Definition Creation", "[nodedef]")
bool isDefaultVersion = false;
const std::string NODENAME = graph->getName();


// Create a new functional graph and definition from a compound graph
bool addAsChild = options.addImplementationAsChild;
std::string newNodeDefName = doc->createValidChildName("ND_" + graph->getName());
std::string newGraphName = doc->createValidChildName("NG_" + graph->getName());
mx::NodeDefPtr nodeDef = doc->addNodeDefFromGraph(graph, newNodeDefName, NODENAME, newGraphName);
mx::NodeDefPtr nodeDef = doc->addNodeDefFromGraph(graph, newNodeDefName, NODENAME, newGraphName, &options);
REQUIRE(nodeDef != nullptr);
nodeDef->setVersionString(VERSION1);
nodeDef->setDefaultVersion(isDefaultVersion);
Expand Down Expand Up @@ -759,9 +820,16 @@ TEST_CASE("Node Definition Creation", "[nodedef]")
}

// Check validity of new functional nodegraph
mx::NodeGraphPtr newGraph = doc->getNodeGraph(newGraphName);
mx::NodeGraphPtr newGraph = !addAsChild ? doc->getNodeGraph(newGraphName) : nodeDef->getChildOfType<mx::NodeGraph>(newGraphName);
REQUIRE(newGraph != nullptr);
REQUIRE(newGraph->getNodeDefString() == newNodeDefName);
if (!addAsChild)
{
REQUIRE(newGraph->getNodeDefString() == newNodeDefName);
}
else
{
REQUIRE(newGraph->getNodeDefString().empty());
}
mx::ConstInterfaceElementPtr decl = newGraph->getDeclaration();
REQUIRE(decl->getName() == nodeDef->getName());
REQUIRE(doc->validate());
Expand Down Expand Up @@ -826,6 +894,28 @@ TEST_CASE("Node Definition Creation", "[nodedef]")
REQUIRE(doc->validate());
}

TEST_CASE("Node Definition Creation", "[nodedef_create]")
{
mx::DefinitionOptions defOptions;

SECTION("Without implementation as child")
{
defOptions.addImplementationAsChild = false;
testNodeDefCreationFromGraph(defOptions);
}

SECTION("With implementation as child")
{
defOptions.addImplementationAsChild = true;
testNodeDefCreationFromGraph(defOptions);
}

SECTION("Functional NodeDef test")
{
testFunctionalNodeDef();
}
}

TEST_CASE("Set Name Global", "[node, nodegraph]")
{
mx::DocumentPtr doc = mx::createDocument();
Expand Down
5 changes: 4 additions & 1 deletion source/PyMaterialX/PyMaterialXCore/PyDocument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ void bindPyDocument(py::module& mod)
{
mod.def("createDocument", &mx::createDocument);

py::class_<mx::DefinitionOptions>(mod, "DefinitionOptions")
.def_readwrite("addImplementationAsChild", &mx::DefinitionOptions::addImplementationAsChild);

py::class_<mx::Document, mx::DocumentPtr, mx::GraphElement>(mod, "Document")
.def("initialize", &mx::Document::initialize)
.def("copy", &mx::Document::copy)
Expand Down Expand Up @@ -74,7 +77,7 @@ void bindPyDocument(py::module& mod)
.def("removeTypeDef", &mx::Document::removeTypeDef)
.def("addNodeDef", &mx::Document::addNodeDef,
py::arg("name") = mx::EMPTY_STRING, py::arg("type") = mx::DEFAULT_TYPE_STRING, py::arg("node") = mx::EMPTY_STRING)
.def("addNodeDefFromGraph", (mx::NodeDefPtr (mx::Document::*)(mx::NodeGraphPtr, const std::string&, const std::string&, const std::string&)) & mx::Document::addNodeDefFromGraph)
.def("addNodeDefFromGraph", (mx::NodeDefPtr (mx::Document::*)(mx::NodeGraphPtr, const std::string&, const std::string&, const std::string&, mx::DefinitionOptions*)) & mx::Document::addNodeDefFromGraph)
.def("addNodeDefFromGraph", (mx::NodeDefPtr(mx::Document::*)(mx::NodeGraphPtr, const std::string&, const std::string&, const std::string&,
bool, const std::string&, const std::string& )) & PyBindDocument::old_addNodeDefFromGraph)
.def("getNodeDef", &mx::Document::getNodeDef)
Expand Down