-
Notifications
You must be signed in to change notification settings - Fork 437
Add support for nested NodeGraphs : Core Traversal, Validation and ShaderGen #2999
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
base: main
Are you sure you want to change the base?
Changes from all commits
def8c0d
10015af
d7e11f5
d2bafc0
44cdecd
15d8ff9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?xml version="1.0"?> | ||
| <materialx version="1.38" colorspace="lin_rec709"> | ||
| <nodegraph name="parentNG"> | ||
| <input name="parentNGInput" type="color3" value="1, 1, 0.2" /> | ||
| <input name="parentNGInput2" type="color3" value="0.2, 1, 1" /> | ||
| <input name="parentNGInput3" type="color3" value="1, 0.2, 1" /> | ||
| <nodegraph name="childNG"> | ||
| <input name="childNGInput" type="color3" interfacename="parentNGInput" /> | ||
| <input name="childNGInput2" type="color3" interfacename="parentNGInput2" /> | ||
| <multiply name="multiplyNode" type="color3"> | ||
| <input name="in1" type="color3" interfacename="childNGInput" /> | ||
| <input name="in2" type="color3" interfacename="childNGInput2" /> | ||
| </multiply> | ||
| <output name="childNGOutput" type="color3" nodename="multiplyNode" /> | ||
| </nodegraph> | ||
| <multiply name="parentmultiplyNode" type="color3"> | ||
| <input name="in1" type="color3" nodegraph="childNG" output="childNGOutput" /> | ||
| <input name="in2" type="color3" interfacename="parentNGInput3" /> | ||
| </multiply> | ||
| <output name="parentNGOutput1" type="color3" nodegraph="childNG" /> | ||
| <output name="parentNGOutput2" type="color3" nodename="parentmultiplyNode" /> | ||
| </nodegraph> | ||
| <standard_surface name="nested_graph_shader" type="surfaceshader"> | ||
| <input name="base_color" type="color3" output="parentNGOutput1" nodegraph="parentNG" /> | ||
| </standard_surface> | ||
| <surfacematerial name="nested_graph_material" type="material"> | ||
| <input name="surfaceshader" type="surfaceshader" nodename="nested_graph_shader" /> | ||
| <input name="displacementshader" type="displacementshader" value="" /> | ||
| </surfacematerial> | ||
| <standard_surface name="nested_graph_shader2" type="surfaceshader"> | ||
| <input name="base_color" type="color3" nodegraph="parentNG" output="parentNGOutput2" /> | ||
| </standard_surface> | ||
| <surfacematerial name="nested_graph_material2" type="material"> | ||
| <input name="surfaceshader" type="surfaceshader" nodename="nested_graph_shader2" /> | ||
| <input name="displacementshader" type="displacementshader" value="" /> | ||
| </surfacematerial> | ||
| </materialx> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <?xml version="1.0"?> | ||
| <materialx version="1.39"> | ||
| <nodegraph name="level1_graph"> | ||
| <nodegraph name="level2_graph"> | ||
| <nodegraph name="level3_graph"> | ||
| <input name="level3_input" type="color3" interfacename="level2_input2" /> | ||
| <output name="level3_output" type="color3" nodename="invert_color3" /> | ||
| <invert name="invert_color3" type="color3"> | ||
| <input name="in" type="color3" interfacename="level3_input" /> | ||
| </invert> | ||
| </nodegraph> | ||
| <input name="level2_input" type="color3" interfacename="level1_input" /> | ||
| <input name="level2_input2" type="color3" interfacename="level1_input2" /> | ||
| <output name="level2_output" type="color3" nodename="color3" /> | ||
| <add name="color3" type="color3"> | ||
| <input name="in1" type="color3" interfacename="level2_input2" /> | ||
| <input name="in2" type="color3" interfacename="level2_input" /> | ||
| </add> | ||
| <output name="level2_output2" type="color3" nodegraph="level3_graph" output="level3_output" /> | ||
| </nodegraph> | ||
| <input name="level1_input" type="color3" value="0.216726, 0.0162733, 0.128939" /> | ||
| <input name="level1_input2" type="color3" value="0.366403, 0.691541, 0.251566" /> | ||
| <multiply name="color3" type="color3"> | ||
| <input name="in1" type="color3" nodegraph="level2_graph" output="level2_output" /> | ||
| </multiply> | ||
| <output name="level1_output" type="color3" nodegraph="level2_graph" /> | ||
| <output name="level1_output2" type="color3" output="level2_output2" nodegraph="level2_graph" /> | ||
| </nodegraph> | ||
| <output name="doc_output" type="color3" nodegraph="level1_graph" output="level1_output2" /> | ||
| <surface_unlit name="surface_unlit" type="surfaceshader"> | ||
| <input name="emission_color" type="color3" output="level1_output" nodegraph="level1_graph" /> | ||
| </surface_unlit> | ||
| <surface_unlit name="surface_unlit2" type="surfaceshader"> | ||
| <input name="emission_color" type="color3" output="level1_output2" nodegraph="level1_graph" /> | ||
| </surface_unlit> | ||
| </materialx> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,13 +87,17 @@ OutputPtr PortElement::getConnectedOutput() const | |
| ConstElementPtr parent = getParent(); | ||
| ConstElementPtr scope = parent ? parent->getParent() : nullptr; | ||
|
|
||
| // Look for a nodegraph output. | ||
| // Look for a nodegraph output. Need to check parent, grandparent and document (root) scope. | ||
| if (hasNodeGraphString()) | ||
| { | ||
| NodeGraphPtr nodeGraph = resolveNameReference<NodeGraph>(getNodeGraphString(), scope); | ||
| if (!nodeGraph) | ||
| { | ||
| nodeGraph = resolveNameReference<NodeGraph>(getNodeGraphString()); | ||
| nodeGraph = resolveNameReference<NodeGraph>(getNodeGraphString(), parent); | ||
| if (!nodeGraph) | ||
| { | ||
| nodeGraph = resolveNameReference<NodeGraph>(getNodeGraphString()); | ||
| } | ||
| } | ||
| if (nodeGraph) | ||
| { | ||
|
|
@@ -110,6 +114,12 @@ OutputPtr PortElement::getConnectedOutput() const | |
| } | ||
| } | ||
| } | ||
|
|
||
| // Handle direct nodegraph -> nodegraph output connections | ||
| while (result && result->hasNodeGraphString()) | ||
| { | ||
| result = result->getConnectedOutput(); | ||
| } | ||
| } | ||
| // Look for a node output. | ||
| else if (hasNodeName()) | ||
|
|
@@ -149,42 +159,60 @@ bool PortElement::validate(string* message) const | |
| { | ||
| bool res = true; | ||
|
|
||
| NodePtr connectedNode = getConnectedNode(); | ||
| if (hasNodeName() || hasOutputString()) | ||
| const string& outputString = getOutputString(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rework validation a bit so that you can have upstream nodegraph references |
||
| InterfaceElementPtr connectedElement = nullptr; | ||
| NodePtr connectedNode = nullptr; | ||
| NodeGraphPtr connectedGraph = nullptr; | ||
| if (hasNodeName()) | ||
| { | ||
| NodeGraphPtr nodeGraph = resolveNameReference<NodeGraph>(getNodeName()); | ||
| if (!nodeGraph) | ||
| connectedElement = connectedNode = getConnectedNode(); | ||
| } | ||
| else if (hasNodeGraphString()) | ||
| { | ||
| const string& nodeGraphString = getNodeGraphString(); | ||
| connectedGraph = resolveNameReference<NodeGraph>(nodeGraphString); | ||
| if (!connectedGraph) | ||
| { | ||
| validateRequire(connectedNode != nullptr, res, message, "Invalid port connection"); | ||
| ConstElementPtr parent = getParent(); | ||
| if (parent) | ||
| { | ||
| connectedGraph = resolveNameReference<NodeGraph>(nodeGraphString, parent); | ||
| if (!connectedGraph) | ||
| { | ||
| ConstElementPtr grandparent = parent->getParent(); | ||
| connectedGraph = resolveNameReference<NodeGraph>(nodeGraphString, grandparent); | ||
| } | ||
| } | ||
| } | ||
| connectedElement = connectedGraph; | ||
| validateRequire(connectedGraph != nullptr, res, message, | ||
| "Nodegraph '" + nodeGraphString + "' not found for connection"); | ||
| } | ||
| if (connectedNode) | ||
|
|
||
| if (connectedElement) | ||
| { | ||
| const string& outputString = getOutputString(); | ||
| if (!outputString.empty()) | ||
| { | ||
| OutputPtr output; | ||
| if (hasNodeName()) | ||
| OutputPtr output = nullptr; | ||
| if (connectedNode) | ||
| { | ||
| NodeDefPtr nodeDef = connectedNode->getNodeDef(); | ||
| output = nodeDef ? nodeDef->getOutput(outputString) : OutputPtr(); | ||
| if (output) | ||
| { | ||
| validateRequire(connectedNode->getType() == MULTI_OUTPUT_TYPE_STRING, res, message, | ||
| "Multi-output type expected in port connection"); | ||
| "Multi-output type expected in port connection'"); | ||
| } | ||
| } | ||
| else if (hasNodeGraphString()) | ||
| else if (connectedGraph) | ||
| { | ||
| NodeGraphPtr nodeGraph = resolveNameReference<NodeGraph>(getNodeGraphString()); | ||
| if (nodeGraph) | ||
| output = connectedGraph->getOutput(outputString); | ||
| validateRequire(output != nullptr, res, message, | ||
| "Nodegraph output '" + outputString + "' not found for connection"); | ||
| if (connectedGraph->getNodeDef()) | ||
| { | ||
| output = nodeGraph->getOutput(outputString); | ||
| if (nodeGraph->getNodeDef()) | ||
| { | ||
| validateRequire(nodeGraph->getOutputCount() > 1, res, message, | ||
| "Multi-output type expected in port connection"); | ||
| } | ||
| validateRequire(connectedGraph->getOutputCount() > 1, res, message, | ||
| "Multi-output type expected in port connection"); | ||
| } | ||
| } | ||
| else | ||
|
|
@@ -196,14 +224,44 @@ bool PortElement::validate(string* message) const | |
|
|
||
| if (output) | ||
| { | ||
| validateRequire(getType() == output->getType(), res, message, "Mismatched types in port connection"); | ||
| validateRequire(getType() == output->getType(), res, message, "Mismatched types in port connection:" + | ||
| getType() + " versus " + output->getType()); | ||
| } | ||
| } | ||
| else if (connectedNode->getType() != MULTI_OUTPUT_TYPE_STRING) | ||
|
|
||
| // Check first output. Special case multioutput nodes which are not supposed to have an output | ||
| // versus nodegraphs which must have an output specified. | ||
| else | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra logic to handle special casing nodes which may or may not have an output. It would be great to get rid of dis-allowing output name if not multioutput. |
||
| { | ||
| validateRequire(getType() == connectedNode->getType(), res, message, "Mismatched types in port connection"); | ||
| OutputPtr output = nullptr; | ||
| string outputType = EMPTY_STRING; | ||
|
|
||
| if (connectedNode) | ||
| { | ||
| outputType = connectedNode->getType(); | ||
| } | ||
| else if (connectedGraph) | ||
| { | ||
| std::vector<OutputPtr> outputs = connectedGraph->getOutputs(); | ||
| validateRequire(!outputs.empty(), res, message, "Nodegraph has no outputs for port connection"); | ||
| if (!outputs.empty()) | ||
| { | ||
| output = outputs[0]; | ||
| outputType = output->getType(); | ||
| } | ||
| } | ||
|
|
||
| if (!outputType.empty()) | ||
| { | ||
| if (outputType != MULTI_OUTPUT_TYPE_STRING) | ||
| { | ||
| validateRequire(getType() == outputType, res, message, "Mismatched types in port connection:" + | ||
| getType() + " versus " + outputType); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return ValueElement::validate(message) && res; | ||
| } | ||
|
|
||
|
|
@@ -275,11 +333,50 @@ InputPtr Input::getInterfaceInput() const | |
| { | ||
| if (hasInterfaceName()) | ||
| { | ||
| const string& interfaceName = getInterfaceName(); | ||
| ConstGraphElementPtr graph = getAncestorOfType<GraphElement>(); | ||
| if (graph) | ||
| if (graph && graph == getParent()) | ||
| { | ||
| // This element is a direct child of a graph element, so its | ||
| // interface name references a value element in the parent scope. | ||
| ConstElementPtr graphParent = graph->getParent(); | ||
| graph = graphParent ? graphParent->getAncestorOfType<GraphElement>() : nullptr; | ||
| } | ||
| ConstNodeGraphPtr nodeGraph = graph ? graph->asA<NodeGraph>() : nullptr; | ||
| if (!nodeGraph) | ||
| { | ||
| return graph->getInput(getInterfaceName()); | ||
| } | ||
| if (nodeGraph) | ||
| { | ||
| // If this is a functional nodegraph, the input is defined by the nodegraph's nodedef | ||
| NodeDefPtr nodedef = nodeGraph->getNodeDef(); | ||
| if (nodedef) | ||
| { | ||
| return nodedef->getInput(interfaceName); | ||
| } | ||
| // Otherwise traverse through interface element connections recursivley. | ||
| else | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This allow traversal through interface / interface connections properly. |
||
| { | ||
| InputPtr returnVal = nodeGraph->getInput(interfaceName); | ||
| if (returnVal) | ||
| { | ||
| if (returnVal->hasInterfaceName()) | ||
| { | ||
| InputPtr val = returnVal->getInterfaceInput(); | ||
| if (val) | ||
| { | ||
| returnVal = val; | ||
| } | ||
| return returnVal; | ||
| } | ||
| else | ||
| { | ||
| return returnVal; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -294,6 +294,38 @@ class MX_CORE_API GraphElement : public InterfaceElement | |
| removeChildOfType<Backdrop>(name); | ||
| } | ||
|
|
||
| /// @} | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a migration from Document to GraphElement to allow exposure beyond the root level for graph operations. |
||
| /// @name NodeGraph Elements | ||
| /// @{ | ||
|
|
||
| /// Add a NodeGraph to the document. | ||
| /// @param name The name of the new NodeGraph. | ||
| /// If no name is specified, then a unique name will automatically be | ||
| /// generated. | ||
| /// @return A shared pointer to the new NodeGraph. | ||
| NodeGraphPtr addNodeGraph(const string& name = EMPTY_STRING) | ||
| { | ||
| return addChild<NodeGraph>(name); | ||
| } | ||
|
|
||
| /// Return the NodeGraph, if any, with the given name. | ||
| NodeGraphPtr getNodeGraph(const string& name) const | ||
| { | ||
| return getChildOfType<NodeGraph>(name); | ||
| } | ||
|
|
||
| /// Return a vector of all NodeGraph elements in the document. | ||
| vector<NodeGraphPtr> getNodeGraphs() const | ||
| { | ||
| return getChildrenOfType<NodeGraph>(); | ||
| } | ||
|
|
||
| /// Remove the NodeGraph, if any, with the given name. | ||
| void removeNodeGraph(const string& name) | ||
| { | ||
| removeChildOfType<NodeGraph>(name); | ||
| } | ||
|
|
||
| /// @} | ||
| /// @name Utility | ||
| /// @{ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No change. ( I had changes but the parent scope additions by Jonathan is a better fix. )