Skip to content

Conversation

@kwokcb
Copy link
Contributor

@kwokcb kwokcb commented May 21, 2025

Implementation

This are the basic changes to handle the current "functional nodedef" proposal: #2355 as an
new way to form associations between a definition and it's nodegraph implementation.
This new variant is purely additive for the data model and code logic and API interface and internal logic.

Modifications.

  • Update node definition dictionary in the document cache to consider nodedefs with child nodegraphs (implementations). Non-child implementations have higher precedence.
  • Update definition query for implementations / nodegraphs to check for parent nodedef first.
  • Add DefintionOptions options class to which contains the option to add as child or not. Default is false. Additional arguments can be added here vs a list of args in the previous interface.
  • Update C++ Document::addNodeDefFromGraph() interface to take DefinitionsOptions as a new optional argument.
  • Add in interface to check for shared implementations in an inheritance hierarchy:
    • NodeDef::getMatchingDefinitions() and NodeDef::hasSharedImplementation().
  • Add in conversion utility to "inline" implementations. General enough to handle any implementation but has a firewall check for now to only allow "functional nodegraphs". Very easy to remove this to accept any implementation variant.
    • NodeDef::inlineImplementation()
  • Add in Python and Javascript wrappers.

Tests

  • Add a functional_nodedef.mtlx test used for unit and codegen / render tests.
  • Update unit tests to run definition creation test for both sibling (current) and child (new) options as defined by DefinitionOptions.
  • Update Javascript unit tests. Expected:
    ✔ Build document
    ✔ Create NodeDef from NodeGraph with child implementation
    ✔ Create NodeDef from NodeGraph with referencing implementation
  • Add unit tests for inlining, looking for shared implementations and inheritance queries.
  • Manual testing in graph editor.
    • Value modifications
    • Show definition

Unit Test Results

Definition Created with Sibling Reference

Details
<?xml version="1.0"?>
<materialx version="1.39">
 <nodegraph name="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" />
   <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">
   <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>
 <nodegraph name="NG_test_colorcorrect" nodedef="ND_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>
</materialx>

Definition Created with Child

Details
<?xml version="1.0"?>
<materialx version="1.39">
  <nodegraph name="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" />
    <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>
</materialx>

Graph Editor : Std Library Definition Using Child Nodegraph

func_nodedef_safepower.mp4

Graph Editor: Created Definition Using Child Nodegraph

  • Instance
image
  • Definition Expanded
image

// So append them to the end of the implementation list assocaited
// with any existing nodedef entry (or create a new one if does not exist).
//
for (const auto& [nodedefKey, appendImplementations] : nonFuncNodeDefMap)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Search order is strictly set here for definitions: functional defs first, all others after. Usage priority is decided elsewhere but generally it's a "first found" logic (per target).

For this initial check-in, we only look for child nodegraphs. Child implementations could be added if / when desired.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should add the child implementations as well - so we retain the symmetry between nodegraph and implementation elements.

Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm understanding your comments here correctly, I think you're saying that nodegraph element defined inside a nodedef (a functional def), would be used ahead of any externally defined nodegraph element.

If so, I might suggest that we reverse this ordering. I think I would like to be able to append an additional file to my search path, or create a new nodegraph element locally in my document to replace the existing behavior of a given node definition. But if we're giving priority to the internal nodegraph, then I think I would never be able to override the implementation.

Hopefully that makes sense, or else sorry if I misunderstood what you're thinking about order of precedence.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a remaining question, and I think your logic makes sense. It also addresses a previous concern from Doug about how to extend implementation associations.

  • If there is no overlap then order does not matter.
  • If there is then we can give non-embedded nodegraph implementation a higher priority by adding them to the implementation list first (so that they are found first when looking for implementations).

Note that this does no affect being able to get all the implementations back.

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 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.

<input name="in2" type="float" value="1.0" />
<output name="out" type="float" defaultinput="in1" />

<nodegraph name="NG_safepower_float">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any / or all graphs could reside here. Both new and old mechanism still work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will revert this to not break for anyone parsing this directly.

@kwokcb kwokcb changed the title Function NodeDef Prototype Add Function NodeDef Support Oct 23, 2025
@kwokcb kwokcb marked this pull request as ready for review October 23, 2025 17:24
Copy link
Contributor

@ld-kerley ld-kerley left a comment

Choose a reason for hiding this comment

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

I think this looks good to me.

+1 to reverting the data library change for now - until we have socialized this change and validated its not going to inconvenience anyone if we start to update the data library.

I left one outstanding question about order of precedence - but maybe Ive got the wrong end of the stick....

// So append them to the end of the implementation list assocaited
// with any existing nodedef entry (or create a new one if does not exist).
//
for (const auto& [nodedefKey, appendImplementations] : nonFuncNodeDefMap)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should add the child implementations as well - so we retain the symmetry between nodegraph and implementation elements.

// So append them to the end of the implementation list assocaited
// with any existing nodedef entry (or create a new one if does not exist).
//
for (const auto& [nodedefKey, appendImplementations] : nonFuncNodeDefMap)
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm understanding your comments here correctly, I think you're saying that nodegraph element defined inside a nodedef (a functional def), would be used ahead of any externally defined nodegraph element.

If so, I might suggest that we reverse this ordering. I think I would like to be able to append an additional file to my search path, or create a new nodegraph element locally in my document to replace the existing behavior of a given node definition. But if we're giving priority to the internal nodegraph, then I think I would never be able to override the implementation.

Hopefully that makes sense, or else sorry if I misunderstood what you're thinking about order of precedence.

@kwokcb
Copy link
Contributor Author

kwokcb commented Oct 24, 2025

I think this looks good to me.

+1 to reverting the data library change for now - until we have socialized this change and validated its not going to inconvenience anyone if we start to update the data library.

I left one outstanding question about order of precedence - but maybe Ive got the wrong end of the stick....

I will put in a unit test for precedence, remove the stdlib change and add a specific test file for codegen / render tests. Thx.

- Update precedence logic so that non-functional implementations have higher priority.
-  Revert std lib sample changes.
- Add in functional_nodedef.mtlx test file
   - Tests for implementation search of child nodegraph
   - Tests for precedence by adding a non-functional implementation graph afterwards.
@kwokcb kwokcb changed the title Add Function NodeDef Support Add Functional NodeDef Support Oct 25, 2025
@kwokcb kwokcb removed the request for review from ashwinbhat October 25, 2025 04:19
Copy link
Contributor Author

@kwokcb kwokcb left a comment

Choose a reason for hiding this comment

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

Review changes made.

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.

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.

@kwokcb kwokcb requested a review from ld-kerley October 25, 2025 04:24
@kwokcb kwokcb requested a review from ashwinbhat December 13, 2025 17:36
@kwokcb
Copy link
Contributor Author

kwokcb commented Dec 13, 2025

FYI: @ashwinbhat, after talking with @ld-kerley, I've made it so the logic is easy to extend to handling non nodegraph implementation encapsulation as a future next step. A new spec proposal issue will cover what is required there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants