Quirk with Value Resolution

While developing a pipeline for version management of lookdev within a shot we ran into a peculiar behaviour where a variant selection was being made in a referenced layer, Usd reports the correct variant selection in the composed stage, but the values being set by the variant are not coming through.

I’ve managed to reproduce the problem with the following simplified layer (this is using usd-23.08):

#usda 1.0

def "Element" (
    add references = [
        </LookSelectionLayer/Element>,
        </LookdevLayer/main>,
    ]
)
{
}

def "LookSelectionLayer"
{
    over "Element" (
        variants = {
            string look = "variantA"
        }
    )
    {
    }
}

def "LookdevLayer"
{
    def "main" (
        prepend references = </LookdevLayer/__BASE_LOOK__>
        prepend variantSets = "look"
    )
    {
        variantSet "look" = {
            "variantA"
            {
                def "LookdevInterface"
                {
                    string foo = "variantA value"
                }
            }
        }
    }

    def "__BASE_LOOK__"
    {
        def Scope "mtl"
        {
            def Material "MyMaterial" (
                prepend inherits = </LookdevLayer/__BASE_LOOK__/LookdevInterface>
            )
            {
            }

        }

        def "LookdevInterface"
        {
            string foo = "base value"
        }
    }
}

If you query the selected look variant in the composed stage for /Element it reports “variantA” like I expect:

>>> print(stage.GetPrimAtPath("/Element").GetVariantSet("look").GetVariantSelection())
variantA

However, when you get the value of /Element/mtl/MyMaterial.foo it returns “base value” where I’m expecting it to be “variantA value”:

>>> print(stage.GetObjectAtPath("/Element/mtl/MyMaterial.foo").Get())
base value

I’ve looked at the PrimIndex dot graph and the variant opinion is listed as #6 and the base look’s LookdevInterface opinion is listed as #9 so the variant opinion should be winning. However if you inspect the property stack for the attr, it only contains a single entry from the base look’s LookdevInterface:

>>> stage.GetObjectAtPath("/Element/mtl/MyMaterial.foo").GetPropertyStack()
[Sdf.Find(..., '/LookdevLayer/__BASE_LOOK__/LookdevInterface.foo')]

So somehow the variant’s AttributeSpec is getting lost.

Doing any of the following will give the expected “variantA value” value:

  • select look="variantA" directly on /Element rather than via a reference
>>> stage.GetPrimAtPath("/Element").GetVariantSet("look").SetVariantSelection("variantA")
True
>>> print(stage.GetObjectAtPath("/Element/mtl/MyMaterial.foo").Get())
variantA value
  • remove the LookdevInterface indirection and just set foo directly on MyMaterial
  • remove the intermediate “mtl” prim from the layer (this one was quite surprising!)

Am I misunderstanding something about value resolution or is this a bug?

Out of curiosity, how are you seeing the PrimIndex dot graph?

I believe this is because in LIVRPS, the Inheritance will win out over the Variant. (I before V).

prim_index = prim.ComputeExpandedPrimIndex()
dot_graph_file_path = ...
prim_index.DumpToDotGraph(dot_graph_file_path )

That dumps the graph to a text file which you can then convert to a .svg using the dot command line tool from graphviz: Command Line | Graphviz

This is only true if there were a direct opinion on /Element/LookdevInterface. LIVRPS is much more complex than “I before V” when dealing with nested composition arcs. If you look at the PrimIndex you can see that the /Element/LookdevInterface entry at #1 has no spec which means it is not contributing any opinions. The /LookdevLayer/__BASE_LOOK__/LookdevInterface entry (where the “base value” value is coming from) is #9 in the prim index which is weaker than the variant entry at #6.

Also, even if “I before V” were the reason, I would still expect to see the variant opinion in the PropertyStack but it’s just completely missing which leads me to believe it’s a bug.

It might be worth uploading the dot graph here (you might need to zip it)

But yeah it does sound like a bug. Might be worth filing an issue on the repo as well just for tracking purposes

@Tymon , that does defy my expectation, as well. There has been some substantial work (including bugfixes involving ancestral variants) in the past year, so could I possibly request you test in 24.08 before filing a bug? You should be able to use the latest pypi usd-core package if it’s inconvenient to build.

Thanks!

Ah! It does work in 24.08. I should have tried it there first.

@spiff, if we wanted to backport the bug fix to 23.08 ourselves, do you happen to know any specific commits we could look at and how difficult that might be? I suppose if there have been large refactors it could be quite involved…

Would need to research to find the specific commits, which are all in pxr/usd/pcp, but I’d estimate the difficulty of selectively pulling to be reasonably high. We’ve been doing alot of work in pcp, and IIRC, there were two “fixes” related to ancestral variantSets (the second one fixing a previously untested case that the first broke), but inbetween those two fixes was a pretty significant change for dynamic payloads to be able to take variantSets (and other composition) into account when computing the parameters to the payload.

I suspected as much. Moving to 24.08 isn’t an option for us in the short term unfortunately so we’ll discuss internally and see whether we think it’s worth it to try and backport any fixes. We’ll probably just work around the issue for now by sublayering the LookSelectionLayer rather than referencing it (we were probably going to end up doing that anyway).

Thanks for your help!