`PointInstancer` Prototype Scope Best Practices

Hello. We’re looking to align our best practices for PointInstancer and native instancing prototypes scopes.

With some feedback from ASWF’s assets working group, for native instancing, we aligned on def with ancestral class.

class "prototype_sources" {
    def Xform "source_1" { ... } 
    def Xform "source_2" { ... }
}

Our hope was that we could adopt something similar for PointInstancers.

def PointInstancer "instancer" {
    rel prototypes = [<Prototypes/source_1>, <Prototypes/source_2>]
    class "Prototypes" {
        def Xform "source_1" { ... }
        def Xform "source_2" { ... }
    }
}

The reasoning was two-fold:

  • It seemed nice to provide the same guidance for point instancers and native instances.
  • It might be hard to disambiguate between an intentional source scopes and dangling overs in a script looking to cleanup cruft.

We were a little surprised that class doesn’t work as the Prototypes scope specifier here while over does. It appears to be because of the traversal predicate (OpenUSD/pxr/usdImaging/usdImaging/delegate.cpp at dev · PixarAnimationStudios/OpenUSD).

There’s no HasConcreteDefiningSpecifier only HasDefiningSpecifier which includes abstract (class) and concrete (def). The IsAbstract predicate is required to filter out these class specifiers, but it also filters out any prims with ancestral class specifiers.

Just like it’s useful to define a “concretely defining subgraph” of an over, it seems like it might be useful to be able to form a traversal predicate for a “concretely defining subgraph” of a class.

If there’s agreement that such a traversal predicate is useful, would it be worth expanding display predicate of prototypes to allow for “concretely defining subgraphs” of class in addition to over?

Thanks in advance for the consideration.

A cleanup script should only be pruning overs that are inert (and therefore from the leaves up) so I’m not sure I see a real issue there, but…

The consistency with NI prototype organization is compelling. I’m not familiar with the specifics of the assets group recommendations, but just to put it out there, we think there’s value in reserving global classes for use with inherits and specializes, as it gives DCC’s a clue as to what their use is and how to present/organize them. But given that a) we’re unlikely to add a new specifier for this purpose, b) using class as a container has less friction than using over (because of authoring API’s that attempt to define all ancestors of a newly def’d prim), this seems worthwhile to me.

Are you interested in making a PR (it was, IIRC, an @asluk PR that introduced HasDefiningSpecifier for use with PI prototypes)?

Thanks Spiff for the thoughts. I think we’d be happy to contribute a PR.

Regarding the cleanup example, I think you could imagine different users might have different perspectives on what they might consider cruft. Consider the following spec tree authored on the root layer with Asset_1 being originally introduced (concretely defined) on a sub layer

over "Asset_1"
{
   over "Geometry" (append apiSchemas = "MaterialBindingAPI")
   {
       rel material:binding = <../Materials/OverrideMaterial> (materialBindingStrength = "strongerThanDescendants")
   }
   over "Materials"
   {
       def Material "OverrideMaterial" (references = @material_library.usd@</metal>)
       {
       }
   }
}

It defines a new material and overrides the material binding on the geometry scope. Were Asset_1 to be removed on the sublayer, you could imagine wanting a helper available to remove the entire Asset_1 spec tree on the root layer, even though it contains a concretely defining specifier as a leaf.