Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
96 changes: 96 additions & 0 deletions javascript/MaterialXTest/document.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,100 @@ describe('Document', () =>
nodeGraph.delete();
doc.delete();
});

// Sample node graph for defintion tests
function create_sample_graph()
{
const doc = mx.createDocument();

// Create a node graph with a single image node and output.
const nodeGraph = doc.addNodeGraph('NG_wrapper');
expect(doc.getNodeGraphs().length).to.equal(1);
const image = nodeGraph.addNode('image');
const nodes = nodeGraph.getNodes();
expect(nodes.length).to.equal(1);
expect(nodes[0]).to.eql(image);

image.setInputValueString('file', 'image1.png', 'filename');
const input = image.getInput('file');
expect(input).to.not.be.null;

const output = nodeGraph.addOutput();
const outputs = nodeGraph.getOutputs();
expect(outputs.length).to.equal(1);
expect(outputs[0]).to.eql(output);

// Verify the graph is valid
expect(doc.validate()).to.be.true;
return { doc, nodeGraph };
}

it('Create NodeDef from NodeGraph with child implementation', () =>
{
const { doc, nodeGraph } = create_sample_graph();

// Create DefinitionOptions with addImplementationAsChild set to true
const options = new mx.DefinitionOptions();
options.addImplementationAsChild = true;

// Create NodeDef from the NodeGraph with embedded implementation
const nodeDef = doc.addNodeDefFromGraph(
nodeGraph,
'ND_wrapper',
'texture',
'NG_wrapper',
options
);

expect(nodeDef).to.exist;

// Verify the implementation was created as a child of the nodedef
const impl = nodeDef.getImplementation();
expect(impl).to.exist;
expect(impl.isANodeGraph())
expect(impl.getName()).to.equal('NG_wrapper');

// Verify the implementation matches the original node graph
let diff_options = new mx.ElementEquivalenceOptions();
let differences = {};
options.performValueComparisons = false;
let result = impl.isEquivalent(nodeGraph, diff_options, differences);
expect(result).to.be.true;

// Cleanup
diff_options.delete();
options.delete();
doc.delete();
});

it('Create NodeDef from NodeGraph with referencing implementation', () =>
{
const { doc, nodeGraph } = create_sample_graph();

// Create DefinitionOptions with addImplementationAsChild set to true
const options = new mx.DefinitionOptions();
options.addImplementationAsChild = false

const nodeDef2 = doc.addNodeDefFromGraph(
nodeGraph,
'ND_wrapper_2',
'texture',
'NG_wrapper_2',
options
);

expect(nodeDef2).to.exist;

// Verify the implementation was created as a referenced implementation
const impl2 = nodeDef2.getImplementation();
expect(impl2).to.exist;
expect(impl2.isANodeGraph())
expect(impl2.getName()).to.equal('NG_wrapper_2');
const nodedef_string = impl2.getNodeDefString();
expect(nodedef_string).to.equal('ND_wrapper_2');

// Cleanup
options.delete();
doc.delete();
});
});
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>
9 changes: 7 additions & 2 deletions source/JsMaterialX/JsMaterialXCore/JsDocument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ namespace mx = MaterialX;
EMSCRIPTEN_BINDINGS(document)
{
ems::function("createDocument", &mx::createDocument);

ems::class_<mx::DefinitionOptions>("DefinitionOptions")
.constructor<>()
.property("addImplementationAsChild", &mx::DefinitionOptions::addImplementationAsChild);

ems::class_<mx::Document, ems::base<mx::GraphElement>>("Document")
.smart_ptr_constructor("Document", &std::make_shared<mx::Document, mx::ElementPtr, const std::string &>)
.smart_ptr<std::shared_ptr<const mx::Document>>("Document")
Expand Down Expand Up @@ -62,8 +67,8 @@ EMSCRIPTEN_BINDINGS(document)
.function("getTypeDefs", &mx::Document::getTypeDefs)
.function("removeTypeDef", &mx::Document::removeTypeDef)
BIND_MEMBER_FUNC("addNodeDef", mx::Document, addNodeDef, 0, 3, stRef, stRef, stRef)
BIND_MEMBER_FUNC("addNodeDefFromGraph", mx::Document, addNodeDefFromGraph, 4, 4, mx::NodeGraphPtr,
const std::string&, const std::string&, const std::string&)
BIND_MEMBER_FUNC_RAW_PTR("addNodeDefFromGraph", mx::Document, addNodeDefFromGraph, 4, 5, mx::NodeGraphPtr,
const std::string&, const std::string&, const std::string&, mx::DefinitionOptions*)
.function("getNodeDef", &mx::Document::getNodeDef)
.function("getNodeDefs", &mx::Document::getNodeDefs)
.function("removeNodeDef", &mx::Document::removeNodeDef)
Expand Down
88 changes: 88 additions & 0 deletions source/MaterialXCore/Definition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include <MaterialXCore/Document.h>

#include <MaterialXCore/Node.h>

MATERIALX_NAMESPACE_BEGIN

const string COLOR_SEMANTIC = "color";
Expand Down Expand Up @@ -156,6 +158,86 @@ ConstInterfaceElementPtr NodeDef::getDeclaration(const string&) const
return getSelf()->asA<InterfaceElement>();
}

StringVec NodeDef::getMatchingDefinitions() const
{
StringVec result = { getName() };
ElementPtr base = getInheritsFrom();
while (base)
{
result.push_back(base->getName());
base = base->getInheritsFrom();
}
return result;
}

bool NodeDef::hasSharedImplementation(const string& target) const
{
bool hasSharedImplementations = false;

StringVec definitionNames = getMatchingDefinitions();
StringSet implementationNames;
for (const string& definitionName : definitionNames)
{
NodeDefPtr definition = getDocument()->getNodeDef(definitionName);
InterfaceElementPtr implementation = definition ? definition->getImplementation(target) : nullptr;
if (implementation)
{
const string& implementationName = implementation->getName();
if (implementationNames.find(implementationName) != implementationNames.end())
{
hasSharedImplementations = true;
break;
}
else
{
implementationNames.insert(implementationName);
}
}
}
return hasSharedImplementations;
}

InterfaceElementPtr NodeDef::inlineImplementation(const string& target, bool requireExclusive)
{
// Do not allow inlining if shared implementations exist and exclusivity is required.
if (requireExclusive && hasSharedImplementation(target))
{
return nullptr;
}

InterfaceElementPtr impl = getImplementation(target);
if (!impl)
{
return nullptr;
}

// Firewall check to only support functional nodegraphs for now.
StringSet supportedCategories = { Implementation::NODE_GRAPH_ATTRIBUTE };
if (supportedCategories.find(impl->getCategory()) == supportedCategories.end())
{
return nullptr;
}

string newImplName = impl->getName();
newImplName = createValidChildName(newImplName);
ElementPtr newImpl= addChildOfCategory(impl->getCategory(), newImplName);
if (!newImpl)
{
return nullptr;
}

InterfaceElementPtr newInterface = newImpl->asA<InterfaceElement>();
if (newInterface)
{
// Remove the definition back-reference otherwise the existing implementation will be
// used instead of the new child implementation.
impl->removeAttribute(InterfaceElement::NODE_DEF_ATTRIBUTE);

newImpl->copyContentFrom(impl);
}
return newInterface;
}

//
// Implementation methods
//
Expand All @@ -174,6 +256,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
20 changes: 20 additions & 0 deletions source/MaterialXCore/Definition.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,26 @@ class MX_CORE_API NodeDef : public InterfaceElement
/// by the given target name.
ConstInterfaceElementPtr getDeclaration(const string& target = EMPTY_STRING) const override;

/// Get definitions that this definition inherits from.
StringVec getMatchingDefinitions() const;

/// Check to see if an implementation is reused across more than one definition
/// @param target Target name. If empty will use the first matching implementation.
bool hasSharedImplementation(const string& target = EMPTY_STRING) const;

/// For the given target, this method locates associated implementation,
/// creates an copy of each implementation as a child of this definition,
/// and removes the original association between the implementation and the definition.
/// The original implementation element remain unchanged elsewhere in the document.
///
/// Currently only implementations which are functional node graphs are supported.
///
/// @param target Target name. If empty will use the first matching implementation.
/// @param requireExclusive If true and a shared implementation is encountered,
/// the method will not inline that implementation.
/// @return The reference to the child graph, or nullptr if no implementation was found.
InterfaceElementPtr inlineImplementation(const string& target = EMPTY_STRING, bool requireExclusive = false);

/// @}

public:
Expand Down
Loading