Hey!
Currently hitting a problem I’m not 100% where the responsibility lies with regards to composition arcs, namespace editing and material encapsulation rules.
The scene overview: We have two materials. Material0, and Material1. Material0 has a NodeGraph prim which has its properties “promoted” to its material interface (i.e its input property connects to its direct parent material input property). This matches proper encapsulation rules and is valid.
Now on Material1 we use a composition arc (doesn’t matter which at this point, I’ll use specialize), to specialize that NodeGraph into it. The internals of that NodeGraph, are handled correctly and appropriately by the Namespace editing (because it and its children fit within the encapsulation rules). However, the input on this inherited NodeGraph prim still connects to the original Material0. This feels counterintuitive. This actively breaks material encapsulation and causes all kinds of issues.
Is this expected behaviour? I can see two other outcomes:
- NamespaceEditing should apply the same local editing to ensure it targets its new parent.
- Namespace editing remove connections which break direct encapsulation. Clearing the connection
(Personally I prefer the clean removal/clearing of theinvalidconnection)
I’ve created a small example scene to hope to explain the problem
Example Flattened Layer
def Cube "cube0" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </materials/material0>
double3 xformOp:translate:UsdCubeCreate = (-5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate:UsdCubeCreate"]
}
def Cube "cube1" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </materials/material1>
double3 xformOp:translate:UsdCubeCreate = (5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate:UsdCubeCreate"]
}
def "materials"
{
def Material "material0"
{
#red
color3f inputs:diffuseColor = (1, 0, 0)
token outputs:surface.connect = </materials/material0/nodegraph.outputs:surface>
def NodeGraph "nodegraph"
{
#cyan
color3f inputs:diffuseColor = (0, 1, 1)
color3f inputs:diffuseColor.connect = </materials/material0.inputs:diffuseColor>
token outputs:surface.connect = </materials/material0/nodegraph/usdpreviewsurface.outputs:surface>
def Shader "usdpreviewsurface" ()
{
#green
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0, 1, 0)
color3f inputs:diffuseColor.connect = </materials/material0/nodegraph.inputs:diffuseColor>
token outputs:surface
}
}
}
def Material "material1"
{
#white
color3f inputs:diffuseColor = (1.0, 1.0, 1.0)
token outputs:surface.connect = </materials/material1/nodegraph1.outputs:surface>
def "nodegraph1" (
prepend specializes = </materials/material0/nodegraph>
)
{
#pink
color3f inputs:diffuseColor = (1, 0, 1)
token outputs:surface
over "usdpreviewsurface"
{
#blue
color3f inputs:diffuseColor = (0, 0, 1)
}
}
}
}
In this example I would expect /cube0 to be Red (and it is). Then I’d expect /cube1 to be either white or pink.
White if the namespace editing rewires the connection to the new parent.
Pink if the encapsulation breaking namespace edit is removed.
The actual result is that both are red, because the (although technically an invalid connection), the Material1, NodeGraph is still connected to the promoted value on Material0.
Result
#usda 1.0
(
doc = """Generated from Composed Stage of root layer
"""
)
def Cube "cube0" (
apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </materials/material0>
double3 xformOp:translate:UsdCubeCreate = (-5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate:UsdCubeCreate"]
}
def Cube "cube1" (
apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </materials/material1>
double3 xformOp:translate:UsdCubeCreate = (5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate:UsdCubeCreate"]
}
def "materials"
{
def Material "material0"
{
#red
color3f inputs:diffuseColor = (1, 0, 0)
token outputs:surface.connect = </materials/material0/nodegraph.outputs:surface>
def NodeGraph "nodegraph"
{
#cyan
color3f inputs:diffuseColor = (0, 1, 1)
color3f inputs:diffuseColor.connect = </materials/material0.inputs:diffuseColor>
token outputs:surface.connect = </materials/material0/nodegraph/usdpreviewsurface.outputs:surface>
def Shader "usdpreviewsurface" ()
{
#green
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0, 1, 0)
color3f inputs:diffuseColor.connect = </materials/material0/nodegraph.inputs:diffuseColor>
token outputs:surface
}
}
}
def Material "material1"
{
#white
color3f inputs:diffuseColor = (1, 1, 1)
token outputs:surface.connect = </materials/material1/nodegraph1.outputs:surface>
def NodeGraph "nodegraph1"
{
#pink
color3f inputs:diffuseColor = (1, 0, 1)
color3f inputs:diffuseColor.connect = </materials/material0.inputs:diffuseColor>
token outputs:surface.connect = </materials/material1/nodegraph1/usdpreviewsurface.outputs:surface>
def Shader "usdpreviewsurface" ()
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0, 0, 1)
color3f inputs:diffuseColor.connect = </materials/material1/nodegraph1.inputs:diffuseColor>
token outputs:surface
}
}
}
}
Worth noting this is looking at things from a pure Material Encapsulation view of things, and doesn’t take account of namespace editing for connections not within Materials.