Kinds and Models
Kind metadata in OpenUSD, what's it for? What should you set it to?
In USD a Model is not a prim type. Instead a prim can be marked as a Model using a metadata field called kind
. Any type of prim can be marked as a Model, but there are some rules about how they should be structured.
First let's quickly define Models for this article. In the mental model for USD Models can contain multiple meshes, materials, guides, and any other arbitrary data you'd like to place inside them. Models are collections of prims nested under a common root.
At the end of this article there's a section titled "Why Set Kind?" If you're on the fence about whether you care about this concept and want a quick tl;dr, read that first ;)
Kind Metadata
The kind
metadata field looks like this in text format when authored on a prim.
def Xform "MyHero" (
kind = "component"
)
{
... model contents go here ...
}
In this case MyHero is a model. Why is the kind "component" and not "model"? This is because of how kind is defined in USD.
The built in kinds are (see also the USD docs)
- model
- component
- group
- assembly
- subcomponent
They are hierarchical, so component and group are types of models. An assembly is a group and a model. A subcomponent is it's own thing, not a model or anything else.
If prims are defined with kind
set to model
it isn't clear if they contain groups of models or not. It removes the expressiveness of the model hierarchy, so it is considered incorrect to use model
directly. It's better to use the more specific types.
The component
kind is intended for Models that don't contain other Models. For the sake of argument let's say we're building a collection of assets for simulating a city street. If we made a street light model, with geometry, maybe a light, etc., we could nest all that under a transform prim and set the kind of the transform to component
. Then other software would be able to tell the Xform
named StreetLight
is a Model. In other words, it's an important collection of content I want to treat as a single unit.
The subcomponent
kind is for important groups inside that single unit. If we have a light inside the StreetLight
model, and we have a bit of extra data there that says what time the light should be turned on and off, or maybe a variant that sets it on or off, we could group all of that inside a prim with kind = "subcomponent"
. This way we indicate that this is an important group of stuff inside my Model, but it is not a separate Model.
The assembly
kind is for important groups of model
s. For our street example, if we create a street scene by referencing in buildings, cars, pedestrians, instances of our StreetLight, and any other content, the root prim of that street scene could be a transform called StreetScene with kind = "assembly"
. This would indicate that I'd like StreetScene treated as a Model itself, but it contains other Models inside it. The contained Models could be more assembly
, group
or component
prims.
The group
kind is also for collections of models. This is more of an organizational concept, and is important for the "model hierarchy" which I'll go into in the next section. But for the sake of this example, let's say I put all the StreetLights in StreetScene under a common parent prim. I could set that prim's kind to group
to indicate, StreetLights is a group of Models inside the assembly
, and you might think of it as a Model, but it is not as important as the StreetScene Model that contains it.
Model Hierarchy
I mentioned the "model hierarchy" above, what is that? The USD source code makes some assumptions about valid locations where it can find models so that searching for them and enumerating them is fast. This puts some constraints on us as authors of USD data.
Essentially, if a prim's parent is not a Model, it can't be a Model. If a prim is a Model, its parent has to be a Model.
You can set kind metadata on anything though and USD won't give errors. Some features might even work, especially if you're defining your own kinds (again see even further below 😄)
When creating USD content with models it's very common to make all parent primitives in your files have kind = "group"
. That way you're indicating this prim might contain child components or child assemblies, but it itself is not a component or an assembly, and the model hierarchy is maintained.
Let's fake up a couple quick examples. The StreetLight might look like this.
#usda 1.0
def Xform "World" (
kind = "group"
)
{
def Xform "StreetLight" (
kind = "component"
)
{
... streetlight geometry ...
def Xform "Light" (
kind = "subcomponent"
)
{
... light stuff ...
}
}
}
Since StreetLight is set to component
software would be able to tell that this is a single model, and its contents can be treated as a unit if treating it as a model.
The StreetScene might look like this.
#usda 1.0
def Xform "World" (
kind = "group"
)
{
def Xform "StreetScene" (
kind = "assembly"
)
{
... street scene content ...
def Xform "StreetLightGroup" (
kind = "group"
)
{
def Xform "LightA" (
prepend references = @./streetlight.usda@</World/StreetLight>
)
{
}
... more lights ...
}
}
}
The composed stage would have these kinds
kind | Prim Path |
---|---|
group | /World |
assembly | /World/StreetScene |
group | /World/StreetScene/StreetLightGroup |
component | /World/StreetScene/StreetLightGroup/LightA |
subcomponent | /World/StreetScene/StreetLightGroup/LightA/Light |
Notice that StreetScene is an assembly
, so software would be able to tell that this is an "important" group in this content. Also notice that LightA does not set a kind
. That's because it gets the kind (and all the rest of the streetlight content) from the light it references. You could set a new kind here if you wanted to, and it would override the kind from the reference.
In each of these files every prim that's a Model has a Model prim as a parent, so the model hierarchy is good and USD should be able to quickly find them when looking for Models.
One more note, it may seem strange that everything is an Xform
here. This is pretty common practice in USD, Xform
s are often used for organization of geometry content. There's no extra cost to making these transforms and not setting any transformation, and in the future it's always available if you need it.
Extending Kind with New Types
You can define your own kind
metadata values, and your new kinds can derive from the existing ones. So you can make new model
or group
types, and software that knows about USD models will be able to see that your new kinds are models or groups.
Doing this requires making some changes in the USD installation. USD is configured with files called pluginfo.json
. These files are usually inside the USD install directory located at lib/usd
. Inside that directory you'll see a root pluginfo.json
file and a lot of subdirectories. A deep dive on these files might be a good topic for another whole article, for now let's just look at one quick way to add new kind
values.
Open the file at lib/usd/usd/resources/pluginfo.json
in a text editor, and look at the beginning. The first few lines will look something like this.
# Portions of this file auto-generated by usdGenSchema.
# Edits will survive regeneration except for comments and
# changes to types with autoGenerated=true.
{
"Plugins": [
{
"Info": {
"SdfMetadata": {
At the start of the "Info"
section, before "SdfMetadata"
we can add the following text.
"Kinds": {
"scene": {
"baseKind": "assembly",
"description": "A simulation scene"
}
},
This will add a new kind
value named scene
. Scenes will act like assemblies in this case. So now we could change StreetScene to have kind = "scene"
in that file, and software that knows about USD models would know to treat scenes as important collections of models.
This is mostly useful if you have a software team writing code that could look for scene
and do special things with it. Your own kinds don't need to have a baseKind
set at all, and baseKind
s can be your custom defined values. That way you can create your own hierarchy that doesn't use model
or group
at all and it wouldn't need to follow model hierarchy rules. Doing so might only be valuable if you can write new code to use your kind values though.
Why Set Kind?
Not every artist tool makes use of USD kind. In my experience most commercial tools ignore it, though I often wish I could use selection modes to choose assemblies, components and subcomponents. If you extend kinds with your own values support is even more narrow. So, is this worth worrying about?
I think it depends on your pipeline and project. As someone who writes code to process graphics data I find it helpful to have the model hierarchy. It makes it easier for me to know what I'm dealing with as I traverse a scene. If you have a nerd like me writing scripts or creating pipeline it can really help to author kind correctly. If not, and if your editing tools ignore it, it might not be worth it at all.
Personally in my own work I try to always set it, even though it takes a little extra effort in most of the tools I use. I do not extend the kinds available using pluginfo.json files. I use a lot of different USD installs with my data, and I don't want to have to update the pluginfos in each USD build to get my preferred kinds.