Closed kwokcb closed 3 weeks ago
@kwokcb This definitely sounds like it's a design choice, since the order in which input
elements are specified will affect the display of a document in an editor. Since we make a point of preserving the order of child elements in MaterialX, it seems consistent that our equivalence operator would respect this order as well.
This makes sense for node
s but not so much for nodegraph
s especially as they can be dynamically added / removed. Also order of nodes in a graph shouldn't make any different, nor the order of attributes as this should have no affect on display. Perhaps node inputs should be strict on ordering but the rest I don't see any reason for having to match ordering.
What do you think ?
A couple of additional thoughts:
input
elements in a nodegraph
affects the display of its interface, and the order of nodes in a nodegraph
affects the behavior of auto-layout logic.The MaterialX document serves multiple purposes.
In the first case I would certainly hope we're not dependent on any ordering of child elements of attributes, but certainly as @jstone-lucasfilm points out the second case has some places in the document where the order is important.
I can very much imagine a world where I want to compare two MaterialX documents to know if they will render the same image, and thus don't care if inputs are defined in a different order. As well as also wanting to know the documents are actually identical.
Perhaps this suggest we need more than one document comparison function?
That's a good note, @ld-kerley, and I can certainly imagine a future method that aims to determine whether two MaterialX documents generate identical renders, independent of whether they would behave identically in an artist UI.
That is basically what I need.
This issue can be rejigged from an issue to a be a proposal for a new interface if that's agreeable.
@kwokcb Got it, and that's definitely a fundamentally different task than comparing whether two documents are identical. Just to suggest an approach, what if you were to sort all of the nodegraphs topologically before comparing the documents in your framework?
The existing topological sort method cannot be used as it's just connection order sort. (GraphElement.topogicalSort
).
This is what I tried out:
def getSortedChildren(graphElement, useTopologicalSort=True):
sortedNames = []
if useTopologicalSort:
sorted = graphElement.topologicalSort()
else:
sorted = graphElement.getChildren()
for node in sorted:
sortedNames.append(node.getNamePath())
# sort the names
if not useTopologicalSort:
sortedNames.sort()
return sortedNames
def printSorted(doc, topologicalSort=True):
sorted = getSortedChildren(doc, topologicalSort)
sorted2 = []
sortedPaths = []
for (i, nodeName) in enumerate(sorted):
print(str(i) + ": " + nodeName)
sortedPaths.append(nodeName)
node = doc.getChild(nodeName)
if node.getCategory() == "nodegraph":
sorted2 = getSortedChildren(node, topologicalSort)
for j, nodeName2 in enumerate(sorted2):
print(" " + str(j) + ": " + nodeName2)
sortedPaths.append(nodeName)
return sortedPaths
print('----------------------')
print('Use topological sort. Is not name sorted')
print('----------------------')
sorted1 = printSorted(doc)
print('- vs -')
sorted2 = printSorted(doc2)
print('Is equivalent:', sorted1 == sorted2)
print('----------------------')
print('Use getChild + sort. Is name sorted')
print('----------------------')
sorted1 = printSorted(doc, False)
print('- vs -')
sorted2 = printSorted(doc2, False)
print('Is equivalent:', sorted1 == sorted2)
with test files:
<?xml version="1.0"?>
<materialx version="1.39" colorspace="lin_rec709">
<nodegraph name="unlit_graph">
<output name="output_color4" type="color3" nodename="ramplr_color4" />
<ramplr name="ramplr_color4" type="color3">
<input name="valuel" type="color3" value="1,0.8,0" />
<input name="valuer" type="color3" value="0,0.5,1" />
<input name="texcoord" type="vector2" value="0,0" />
</ramplr>
</nodegraph>
<surface_unlit name="unlitshader" type="surfaceshader">
<input name="emission_color" type="color3" nodegraph="unlit_graph" />
</surface_unlit>
<surfacematerial name="MAT_unlitshader" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="unlitshader" />
</surfacematerial>
</materialx>
<?xml version="1.0"?>
<materialx version="1.39" colorspace="lin_rec709">
<surface_unlit name="unlitshader" type="surfaceshader">
<input name="emission_color" type="color3" nodegraph="unlit_graph" />
</surface_unlit>
<nodegraph name="unlit_graph">
<ramplr name="ramplr_color4" type="color3">
<input name="valuel" type="color3" value="1,0.8,0" />
<input name="valuer" type="color3" value="0,0.5,1" />
<input name="texcoord" type="vector2" value="0,0" />
</ramplr>
<output name="output_color4" type="color3" nodename="ramplr_color4" />
</nodegraph>
<surfacematerial name="MAT_unlitshader" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="unlitshader" />
</surfacematerial>
</materialx>
gives this result:
----------------------
Use topological sort. Is not name sorted
----------------------
0: unlit_graph
0: unlit_graph/ramplr_color4
1: unlit_graph/output_color4
1: unlitshader
2: MAT_unlitshader
- vs -
0: unlitshader
1: unlit_graph
0: unlit_graph/ramplr_color4
1: unlit_graph/output_color4
2: MAT_unlitshader
Is equivalent: False
----------------------
Use getChild + sort. Is name sorted
----------------------
0: MAT_unlitshader
1: unlit_graph
0: unlit_graph/output_color4
1: unlit_graph/ramplr_color4
2: unlitshader
- vs -
0: MAT_unlitshader
1: unlit_graph
0: unlit_graph/output_color4
1: unlit_graph/ramplr_color4
2: unlitshader
Is equivalent: True
Thus I think I'll refactor the operator== code into a new function to work in-place as it's the fastest way without revisiting the document in multiple passes. Just requires a attribute name sort and a node child name sort and can reuse the string normalization utility in place as well (or as a pre-pass).
Thanks for this proposal, @kwokcb, as well as for the implementation in https://github.com/AcademySoftwareFoundation/MaterialX/pull/2003!
Proposal
The current equivalence operator (operator==) will fail if:
This is the desired logic.
However, if there can easily be a case where the attributes or children can be added in different order -- in which case the logic fails if you want to perform a functionally equivalent comparison.
The proposal is support such a comparison.
Workflows
Proposal
Current Logic
Key logic in code commented in code
Example
File 1
File 2: