Ordering of list operations across arcs

Hello-- we wanted to get some clarification on the following behavior. There are two layers in this example.

component.usda

#usda 1.0

class "class_a" {}

def "child" (inherits = </class_a>) {}

assembly.usda

#usda 1.0

class "class_a" {}
class "class_b" {}

def "append" (
        append inherits = </class_b>
        references = @./component.usda@</child>) {}

def "explicit_remove" (
        inherits = </class_b>
        references = @./component.usda@</child>) {}

def "explicit_order" (
         inherits = [</class_a>, </class_b>]
         references = @./component.usda@</child>) {}

If I open the assembly.usda in python as stage s, I get the following results.

>>> s.GetPrimAtPath('/append').GetPrimStack()
[Sdf.Find('assembly.usda', '/append'), Sdf.Find('assembly.usda', '/class_b'), Sdf.Find('assembly.usda', '/class_a'), Sdf.Find('component.usda', '/child'), Sdf.Find('component.usda', '/class_a')]

Even though we use append, the opinions in /class_b are stronger than those in class_a on the assembly.usda layer.

>>> s.GetPrimAtPath('/explicit_remove').GetPrimStack()
[Sdf.Find('assembly.usda', '/explicit_remove'), Sdf.Find('assembly.usda', '/class_b'), Sdf.Find('assembly.usda', '/class_a'), Sdf.Find('component.usda', '/child'), Sdf.Find('component.usda', '/class_a')]

Even though we explicitly set the inherits opinion to /class_b, /class_a still contributes opinions from assembly.usda.

>>> s.GetPrimAtPath('/explicit_order').GetPrimStack()
[Sdf.Find('assembly.usda', '/explicit_order'), Sdf.Find('assembly.usda', '/class_a'), Sdf.Find('assembly.usda', '/class_b'), Sdf.Find('component.usda', '/child'), Sdf.Find('component.usda', '/class_a')]

However, if we include class_a and class_b explicitly, the order changes.

We’re trying to reason about this-- is class_a just taking on the strength order of its referencing arc in the prim stack (which is always weaker than inherits) unless explicitly specified?

nice post and I always wanted to find the exact answer to this.

And now I’m actually relying on this behaviour, in what we are building :slight_smile:
Starting with the idea that “all edits should be coming from inherits” so that they work nicely with instanceable.

From what I understand, there are a few things happening here.

List-edits are not there after crossing composition arcs, they mostly apply with direct sublayering, as a behaviour I’ve seen on core metadata (not sure about relationships), and probably due to how it composes

(side note: is this link broken?)
https://openusd.org/release/api/_usd__page__object_model.html#Usd_OM_ListOps

When referencing component.usda, you are not really bringing class_a prim from it, you get the embedded result of the inherit from it, but not the class itself, which means that you are not going to be able to remove that inheritance from the referenced component.usda with anything from the assembly.usda (not even with an explicit delete, via api sure, but artists don’t do that :slight_smile: ).

But you do “inherit” the fact that, in a weaker “layer in the stack” it is “inheriting” from “class_a”, so that lets you add “class_a” in assembly.usda and still “inherit from that”, again, no matter what you do (like delete again), it will always inherit from class_a, because that applies only to the inherits before the reference is applied.

And this gives you the ability to say that no matter in which way the inherit is coming with the assembly.usda (class_b, pre or app) you can always have your stronger opinion in the file that is referencing your component.usda.

component.usda could have a cube that inherits its “red” color from its class_a.
assembly.usda can’t change the fact that the cube is “red” (as inheriting from its embedded class_a).
But assembly.usda can re-define class_a with a new color, and nothing else needed (no new inherits) and now the new color comes from the class_a in assembly.usda.
Or you can also have the new color-value coming from class_b, and no matter how you inherit it in assembly.usda (pre/app), class_b will always be stronger.

This means that “city-blocks with their own inherits” can be referenced in “cityA” with new classes and no new overs for new inherits and you can provide your new “classes edits”, plus having extra edits from more explicit new inherits in cityA if needed.

Sorry for the long reply…

If my assumptions are based on the wrong understanding, please correct me, but don’t tell me it is a bug and it shouldn’t behave like it is…please… :sweat_smile:

Now that I think about this a bit more… I remember some different behaviour in older usd versions… or am I just remembering this wrong ?! Maybe there was a purpose involved… I think I need to test this further before we fully go with it… just to make sure I’m not relying on something that’s not supposed to be happening.
Sorry to hijack your initial question, but I assume you also expect this behaviour, and just wanted to confirm why this is happening, right ?

I’ve got a few example files to support what I described earlier.

Drop those files at the root of your assets-repo folder, so that they can pick up the Teapot asset and load “teapot_city.usda”.

teapot_asset.usda (406 Bytes)
teapot_block.usda (1.8 KB)
teapot_city.usda (1.4 KB)
teapot_no_lid.usda (2.0 KB)