Skip to content
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

Definition and NodeGraph Publishing Logic Updates #1303

Closed

Conversation

kwokcb
Copy link
Contributor

@kwokcb kwokcb commented Mar 23, 2023

API Logic Updates

This s a set of fixes to the existing "publishing" / interface API to fix logic errors and conform properly with the current specification. The current interface addNodeDefFromGraph() has been deprecated and replaced with a new createDefinition() interface which takes one argument which contains all possible options.

<nodedef> Updates

  • The logic for creating <nodedefs> has logic errors in it so adding this PR to fix it up and to make sure it matches the current specification. Of note was that inputs were not being removed from functional graphs, inputs were not being published, and attributes which are disallowed on functional graphs or nodedefs were not being filtered out.

<nodegraph> Updates

  • Publishing interfaces is missing compound <nodegraph> (vs functional) support so add it in so input interfaces can be "published" from nodes for both compound and functional graphs.

General

  • Undesired attributes on are now filtered out properly on create. his includes handling positional information (xpos, ypos) which is generated by the MaterialXNodeEditor and sourceURI.
  • Fix so that add / remove interface transfers values from interfaces from and to interior inputs properly when adding and removing interfacename.

Integration

  • This has been hooked up into "create definition" workflow in node editor.
    • For now only the node category and a compound node graph is required with all other arguments being optional.
    • A single document on disk is created in a default "user library" location, unless an explicit file location is specified.
    • The "user library" will be loaded by default on startup if it's exists.
    • New definitions can be manually reloaded within a work session as desired.
    • Users can still add in additional library paths from the command line as before.
Publishing_V1.mp4
  • A new Python script createdefinition.py has been added which will use the current logic if the version is 1.38.8, otherwise
    contains the logic fixes as part of the script. Example syntax:
createdefinition.py --category mymarble --nodegroup texture2d --version 1.0 --defaultversion 1 --namespace myspace --documentation "This is a custom marble definition" resources\Materials\Examples\StandardSurface\standard_surface_marble_solid.mtlx --comment "Custom Marble" --separateDocuments True

Unit Test Update

The publishing and definition creation test has been refactored to test the logic changes.

Input Data

  • New graph created in the MaterialXNodeEditor
<?xml version="1.0"?>
<materialx version="1.38">
  <nodegraph name="test_colorcorrect">
    <multiply name="AlphaGain" type="float" xpos="-17.978260" ypos="1.137931">
      <input name="in1" type="float" nodename="inputAlpha" />
    </multiply>
    <add name="AlphaOffset" type="float" xpos="-13.043478" ypos="0.991379">
      <input name="in1" type="float" nodename="AlphaGain" />
    </add>
    <multiply name="ColorGain" type="color3" xpos="-18.094202" ypos="-0.396552">
      <input name="in1" type="color3" nodename="inputColor" />
    </multiply>
    <add name="ColorOffset" type="color3" xpos="-13.043478" ypos="-0.327586">
      <input name="in1" type="color3" nodename="ColorGain" />
    </add>
    <constant name="inputColor" type="color3" xpos="-21.094202" ypos="-0.517241">
      <input name="value" type="color3" value="0.5, 0.5, 0.5" />
    </constant>
    <constant name="inputAlpha" type="float" xpos="-21.094202" ypos="0.827586">
      <input name="value" type="float" value="1" />
    </constant>
    <output name="out" type="color3" nodename="ColorOffset" xpos="-3.355072" ypos="-0.663793" />
    <output name="out1" type="float" nodename="AlphaOffset" xpos="-3.355072" ypos="0.672414" />
  </nodegraph>
</materialx>

Example 1 : Definition from Nodegraph

  • Input interfaces are first added to the compound nodegraph and then when creating the functional graph, transferred.
  • All "valid" attributes are copied over for the interface inputs and outputs
    • Positional information is kept for the functional graph nodes
    • UI tags on input interfaces and nodedef is now tested.
<?xml version="1.0"?>
<materialx version="1.38" xmlns:xi="http://www.w3.org/2001/XInclude">
  <nodegraph name="NG_test_colorcorrect" nodedef="ND_test_colorcorrect">
    <multiply name="AlphaGain" type="float" xpos="-17.746376" ypos="1.379310">
      <input name="in1" type="float" nodename="inputAlpha" />
      <input name="in2" type="float" interfacename="AlphaGain_in2" />
    </multiply>
    <add name="AlphaOffset" type="float" xpos="-13.065217" ypos="1.689655">
      <input name="in1" type="float" nodename="AlphaGain" />
      <input name="in2" type="float" interfacename="AlphaOffset_in2" />
    </add>
    <multiply name="ColorGain" type="color3" xpos="-17.514492" ypos="-0.801724">
      <input name="in1" type="color3" nodename="inputColor" />
      <input name="in2" type="color3" interfacename="ColorGain_in2" />
    </multiply>
    <add name="ColorOffset" type="color3" xpos="-13.043478" ypos="-0.318966">
      <input name="in1" type="color3" nodename="ColorGain" />
      <input name="in2" type="color3" interfacename="ColorOffset_in2" />
    </add>
    <constant name="inputColor" type="color3" xpos="-21.094202" ypos="-0.508621">
      <input name="value" type="color3" interfacename="inputColor_value" />
    </constant>
    <constant name="inputAlpha" type="float" xpos="-21.094202" ypos="0.818965">
      <input name="value" type="float" interfacename="inputAlpha_value" />
    </constant>
    <output name="out" type="color3" nodename="ColorOffset" xpos="-3.347826" ypos="-0.655172" />
    <output name="out1" type="float" nodename="AlphaOffset" xpos="-3.347826" ypos="0.672414" />
  </nodegraph>
  <nodedef name="ND_test_colorcorrect" node="test_colorcorrect" nodegroup="adjustment" version="1.0" 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_2" nodedef="ND_test_colorcorrect_22">
    <multiply name="AlphaGain" type="float" xpos="-17.746376" ypos="1.379310">
      <input name="in1" type="float" nodename="inputAlpha" />
      <input name="in2" type="float" interfacename="AlphaGain_in2" />
    </multiply>
    <add name="AlphaOffset" type="float" xpos="-13.065217" ypos="1.689655">
      <input name="in1" type="float" nodename="AlphaGain" />
      <input name="in2" type="float" interfacename="AlphaOffset_in2" />
    </add>
    <multiply name="ColorGain" type="color3" xpos="-17.514492" ypos="-0.801724">
      <input name="in1" type="color3" nodename="inputColor" />
      <input name="in2" type="color3" interfacename="ColorGain_in2" />
    </multiply>
    <add name="ColorOffset" type="color3" xpos="-13.043478" ypos="-0.318966">
      <input name="in1" type="color3" nodename="ColorGain" />
      <input name="in2" type="color3" interfacename="ColorOffset_in2" />
    </add>
    <constant name="inputColor" type="color3" xpos="-21.094202" ypos="-0.508621">
      <input name="value" type="color3" interfacename="inputColor_value" />
    </constant>
    <constant name="inputAlpha" type="float" xpos="-21.094202" ypos="0.818965">
      <input name="value" type="float" interfacename="inputAlpha_value" />
    </constant>
    <output name="out" type="color3" nodename="ColorOffset" xpos="-3.347826" ypos="-0.655172" />
    <output name="out1" type="float" nodename="AlphaOffset" xpos="-3.347826" ypos="0.672414" />
  </nodegraph>
  <nodedef name="ND_test_colorcorrect_22" node="test_colorcorrect" nodegroup="adjustment" version="2.0" isdefaultversion="true" uiname="test_colorcorrect Version: 2.0" doc="This is version 2 of the definition for the graph: NG_test_colorcorrect_2">
    <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>

Example 2 : Exposing functional graph inputs

  • The functional graph node inputs are "published" as nodedef inputs.
  • This is pretty when the existing test to ensure no regressions for functional graph interface publishing.
<?xml version="1.0"?>
<materialx version="1.38" xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include href="D:\Work\materialx\bernard_MaterialX\build\bin\libraries\stdlib\stdlib_defs.mtlx" />
  <xi:include href="D:\Work\materialx\bernard_MaterialX\build\bin\libraries\stdlib\stdlib_ng.mtlx" />
  <nodegraph name="NG_test_colorcorrect_3" nodedef="ND_test_colorcorrect_32">
    <multiply name="AlphaGain" type="float" xpos="-17.746376" ypos="1.379310">
      <input name="in1" type="float" nodename="inputAlpha" />
      <input name="in2" type="float" interfacename="NG_test_colorcorrect_3_AlphaGain_in2_renamed" />
    </multiply>
    <add name="AlphaOffset" type="float" xpos="-13.065217" ypos="1.689655">
      <input name="in1" type="float" nodename="AlphaGain" />
      <input name="in2" type="float" interfacename="NG_test_colorcorrect_3_AlphaOffset_in2_renamed" />
    </add>
    <multiply name="ColorGain" type="color3" xpos="-17.514492" ypos="-0.801724">
      <input name="in1" type="color3" nodename="inputColor" />
      <input name="in2" type="color3" interfacename="NG_test_colorcorrect_3_ColorGain_in2_renamed" />
    </multiply>
    <add name="ColorOffset" type="color3" xpos="-13.043478" ypos="-0.318966">
      <input name="in1" type="color3" nodename="ColorGain" />
      <input name="in2" type="color3" interfacename="NG_test_colorcorrect_3_ColorOffset_in2_renamed" />
    </add>
    <constant name="inputColor" type="color3" xpos="-21.094202" ypos="-0.508621">
      <input name="value" type="color3" interfacename="NG_test_colorcorrect_3_inputColor_value_renamed" />
    </constant>
    <constant name="inputAlpha" type="float" xpos="-21.094202" ypos="0.818965">
      <input name="value" type="float" interfacename="NG_test_colorcorrect_3_inputAlpha_value_renamed" />
    </constant>
    <output name="out" type="color3" nodename="ColorOffset" xpos="-3.347826" ypos="-0.655172" />
    <output name="out1" type="float" nodename="AlphaOffset" xpos="-3.347826" ypos="0.672414" />
  </nodegraph>
  <nodedef name="ND_test_colorcorrect_32" node="test_colorcorrect" nodegroup="adjustment" version="3.0">
    <output name="out" type="color3" />
    <output name="out1" type="float" />
    <input name="NG_test_colorcorrect_3_AlphaGain_in2_renamed" type="float" value="0.8" />
    <input name="NG_test_colorcorrect_3_AlphaOffset_in2_renamed" type="float" value="1" />
    <input name="NG_test_colorcorrect_3_ColorGain_in2_renamed" type="color3" value="0.9, 0.9, 0.9" />
    <input name="NG_test_colorcorrect_3_ColorOffset_in2_renamed" type="color3" value="0.379147, 0.0341412, 0.0341412" />
    <input name="NG_test_colorcorrect_3_inputColor_value_renamed" type="color3" value="0.5, 0.5, 0.5" />
    <input name="NG_test_colorcorrect_3_inputAlpha_value_renamed" type="float" value="1" />
  </nodedef>
</materialx>

@kwokcb
Copy link
Contributor Author

kwokcb commented Mar 23, 2023

@jstone-lucasfilm , I was trying to add / remove interfaces and publish nodedefs and found that the current code isn't robust and will result in validation errors. Also this fixes up all the attribute transfer to / from interfaces on nodedefs / functional graphs / compound graphs as far as I can see. There is no place which codifies the "rules" except for here AFAIK.

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Apr 19, 2023

CLA Signed

The committers listed above are authorized under a signed CLA.

@WillsonHG
Copy link

/easycla

@kwokcb
Copy link
Contributor Author

kwokcb commented Jun 16, 2023

@jstone-lucasfilm, I've added in the new interface Document.createDefinition() which takes a DefinitionOptions struct.
Graph editor and createdefinition.py script, unit tests have been updated as well.

@jstone-lucasfilm
Copy link
Member

@kwokcb From a high level, I think that this changelist may be combining too many independent ideas, making it challenging to bring all of them to the required level of polish in a single pull request. What would you think about breaking this out into separate pull requests, perhaps starting with an improved, streamlined version of Document::createDefinition that you can build upon in future changes?

@kwokcb
Copy link
Contributor Author

kwokcb commented Jun 19, 2023

@kwokcb From a high level, I think that this changelist may be combining too many independent ideas, making it challenging to bring all of them to the required level of polish in a single pull request. What would you think about breaking this out into separate pull requests, perhaps starting with an improved, streamlined version of Document::createDefinition that you can build upon in future changes?

Actually, you had requested that there be a workflow for definition creation :) So two logical places were a script and interactive in the node editor. I think this has helped to refine the requirements and signature. I don't think there is much additional in the node editor interface, except that it sets up a convention for naming of the nodegraph and nodedef i.e. ND_category_, and NG_...

For the former point, making the arguments explicit and atomic allows for facilitating unique signature creation as mentioned in the interface comment above.

@jstone-lucasfilm
Copy link
Member

That's a very fair point, @kwokcb, and I think I was hoping that these concrete use cases would help to clarify the motivation for the proposed functionality in MaterialXCore, though so far I'm not quite seeing the benefits. Let me take a closer look at these proposals, and I'll see if there are any specific refinements that might be worthwhile to suggest.

Copy link
Member

@jstone-lucasfilm jstone-lucasfilm left a comment

Choose a reason for hiding this comment

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

I've written up some thoughts on the new use case for createDefinition in the graph editor, since that's a great example to focus on.

options.newNodeDefName = newNodeDefName;
options.newNodeGraphName = newGraphName;

mx::NodeDefPtr nodeDef = doc->createDefinition(options);
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a great use case for the new createDefinition functionality, so let's focus on this one for now. In this situation, I would imagine it's clearer and more straightforward for a client to call the following:

mx::NodeDefPtr nodeDef = doc->createDefinition(graph);
nodeDef->setVersion("1.0");
nodeDef->setIsDefaultVersion(true);

This makes it clear that nearly all of the required data (e.g. name, category) is being derived from the NodeGraph itself, with only the versioning data for the NodeDef being stated explicitly by the client.

What are the benefits that you see in the DefinitionOptions class for this use case? I could see it being useful if a single DefinitionOptions were constructed once and then reused multiple times, but that doesn't seem like a common situation that clients would encounter in practice.

Very interested in your thoughts on this!

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 seems like a great use case for the new createDefinition functionality, so let's focus on this one for now. In this situation, I would imagine it's clearer and more straightforward for a client to call the following:

mx::NodeDefPtr nodeDef = doc->createDefinition(graph);
nodeDef->setVersion("1.0");
nodeDef->setIsDefaultVersion(true);

This makes it clear that nearly all of the required data (e.g. name, category) is being derived from the NodeGraph itself, with only the versioning data for the NodeDef being stated explicitly by the client.

What are the benefits that you see in the DefinitionOptions class for this use case? I could see it being useful if a single DefinitionOptions were constructed once and then reused multiple times, but that doesn't seem like a common situation that clients would encounter in practice.

Very interested in your thoughts on this!

  1. For the first example, the main worry here is what if someone does this and then changes the version. This also involves the unique signature semantics being used. i.e. the proposal is the the identifier includes version. so you'd end up with something like ND/NG_foo_<version>_....etc
    IMO, the user should not need to know where the information resides (whether it's on the graph or definition), though I agree that the options should probably be nodedef specific and not include the graph so it would be more of:
createDefinition(graph, nodedef_options)

I wanted to change this anyways as there is possible reuse when dealing with source code implementations -- i.e. the nodedef options are still valid.

  1. An example for the second case is if you want to create variants on definition. e.g. if you had nodegraphs with float, color3, vector3 etc outputs. Then you'd want consistent version, nodegroup, namespace, etc but the signature would
    differ slightly depending on the nodegraph. e.g. You could end up with:
myspace:ND_foo_v1_float
myspace:ND_foo_v1_vector3
myspace:ND_ foo_v1_color3
etc...

Copy link
Member

Choose a reason for hiding this comment

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

@kwokcb I'm not quite sure what you mean by "someone does this and changes the version".

Since the desired version is being stated just once by the client, with no coupling between the NodeGraph and NodeDef, I don't see the benefit in stating this version inside of a DefinitionOptions class instead of stating it explicitly through a setVersion call.

What is the situation you're imagining, where using the DefinitionOptions structure would be more robust to version changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kwokcb I'm not quite sure what you mean by "someone does this and changes the version".

Since the desired version is being stated just once by the client, with no coupling between the NodeGraph and NodeDef, I don't see the benefit in stating this version inside of a DefinitionOptions class instead of stating it explicitly through a setVersion call.

What is the situation you're imagining, where using the DefinitionOptions structure would be more robust to version changes?

The main issue with the API in general is that users must somehow find out what attributes to set -- mostly by reading the spec. So the options make the inputs explicit and as part of the createDefinition() API -- atomic. Done separately implies that it can be done at any time which should not be the case. True you can't stop folks from making attribute setting calls but I'd think it would have to be a intentional decision to find the attribute and set it.

Maybe the over-arching idea behind explicitly listing options is knowledge and safety (at least at creation time), then ease-of-use.

Copy link
Member

Choose a reason for hiding this comment

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

I see where you're coming from, but I'm not quite as convinced that the DefinitionOptions structure adds clarity or robustness from the user's perspective. To my mind it's just hiding the details of what happens inside of createDefinition, and it seems clearer and more robust for the client to see these explicit steps in their code. My hope is that createDefinition would handle all of the details that are implied by the input NodeGraph itself (e.g. name, category), but not try to handle details that the client needs to explicitly define (e.g. versioning, documentation, and so forth).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also my thinking is the emphasis of the API is on nodeef creation so you want the arguments available with a nodegraph is one source which affects the nodedef's characteristics, not so much that you create "some" nodedef with a nodegraph and then specify it's characteristics afterwards.

I think the flip-side question is how to create consistent definitions if the code logic is explicitly performed in separate steps. For example for "global" consistency the utility below exists. Having all args allows a way to take all the information to produce unique ids which are also used for Element existence checking. It would be difficult to promote this if examples show attributes being added after the definition creation.

string generateIdentifier() const
    {
        if (!compoundGraph || categoryString.empty())
        {
            return EMPTY_STRING;
        }

        std::string identifier = categoryString;

        if (useVersion && !versionString.empty())
        {
            identifier += "_" + versionString;
        }

        for (OutputPtr output : compoundGraph->getOutputs())
        {
            const std::string& outputType = output->getType();
            identifier += "_" + outputType;
        }

        return identifier;
    }

For:

namespace: This needs to be set on the nodedef and nodegraph so the simple reasoning would be to just avoid mistakes.

nodegroup: can be set after, but can change the definitions codegen behaviour, and as far as I've seen nobody thinks about this so will likely be forgotten. So folks can end up with unexpected shader gen results. For example
the houdini nodes are in the houdini group but if they were to add pbr nodes then the behaviour could be unknown.

category, compound nodegraph, & unique signatures for the nodedef and functional nodegraph should be consistent -- and this relies on the identifier generation.

document agreed can be done anytime, but again folks don't seem to think about adding it in, so most nodes are undocumented.

I just don't see anyone ever remembering this or they do how can you promote / enforce producing consistent definitions.

BTW the existing addNodeDefFromGraph() already has most of the "desired" requirements. Perhaps we just keep this
with the logic fixes and add in the unique id generator as a util or embedded inside the API for id generation consistency :). The only outlier is namespace which could be added for the aforementioned reason that I don't think
anyone will remember to set it on the graph and nodedef. Which I think led to the struct creation...

Copy link
Member

Choose a reason for hiding this comment

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

Omitting these changes to addNodeDefFromGraph sounds like a good strategy to me, and from a high level I would recommend that you make this pull request as minimal as it can possibly be. That should definitely help me to provide a useful review with my limited GitHub development time, and hopefully it will make this change easier to maintain in the future as well. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @jstone-lucasfilm
I've created 2 smaller PRs. The original one which was just to update the API logic (no signature change) is #1403.
The UI integration into the graph editor is here #1404.

I will close this one out once I copy over all the PR docs.

@kwokcb kwokcb closed this Jul 11, 2023
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.

None yet

3 participants