Stage Variables

We’re investigating how to integrate stage variables into Houdini’s USD workflows, and I’m looking at how to best help users author and debug expressions that depend on stage variables. The original proposal talks about having usdview show the evaluated or unevaluated expressions on attributes. A standard UsdAttribute.Get() presumably returns the evaluated expression, but what is the API to get the raw expression?

But beyond this, I’m wondering about how to integrate this with the set of stage variables that are “active” at that point in the scene graph tree. As I understand it stage variables can come into existence through a reference, in which case that variable is only usable in the parts of the hierarchy under that reference. So at any given prim location, I assume there is a dictionary of “active” values? Is there an API to get at this dictionary, or is it buried in some inaccessible corner of Pcp for performance reasons?

I found the SdfVariableExpression class which is going to be very handy for debugging purposes, but the trick there is you have to provide the explicit dictionary or values, which again I would like to get from the active USD Stage at the location in the scene graph tree where the user wants to place the expression. So again, I’m wondering how I can get at this “active” dictionary of values for a given prim?

Thanks,
Mark

Another question/possible bug here… The UsdUtilsFlattenLayerStack method loses stage variable expressions. The output layer contains the evaluated expressions, which doesn’t seem right to me. When flattening a layer stack I expect the source layer opinions to be preserved to the greatest extent possible… Stage variable expressions in variant opinions survive this function, FWIW. But expressions in reference and payload arcs do not (obviously sublayer arcs don’t survive this method at all). I’ll do some digging to see if I can figure out where/why this is happening.

Thanks,
Mark

2 Likes

Regarding the layer stack flattening, I see UsdFlattenResolveAssetPathAdvancedFn in usd/flattenUtils.h may be the answer here? But using this method with layer flattening isn’t exposed through the UsdUtils method (UsdUtilsFlattenLayerStack). Maybe I should bypass UsdUtils and call UsdFlattenLayerStack diretly with an “AdvancedFn” implemented in a way that doesn’t evaluate expressions. I have always thought it was a bit weird to have UsdUtils wrapping this layer flattening code so thinly. But then again maybe there is a good reason why expressions need to be flattened as part of this process that I’m not understanding?

Any “anchored” asset paths that are authored in layers that are melting away need to be fully resolved, and therefore have any expression variables baked in, unfortunately. But if it’s happening on non-anchored paths, I’d consider that an oversight. You shouldn’t need to use an advanced api to get that behavior… the UsdUtils wrapper should do the right thing, as should the core function.

Maybe a thing that could be an option on the UsdUtils function is a param that disables the special (safe!) treatment of anchored paths, making a promise that the root layer will always have resolved to the same place as all the sublayers with anchored paths, regardless of variable substitutions? Pretty easy to inadvertently foil that, though…

A standard UsdAttribute.Get() presumably returns the evaluated expression, but what is the API to get the raw expression?

We currently do not have any USD API to get to the raw expression. There are a couple different ideas for exposing this:

  • Add a new field to the SdfAssetPath object
  • Add a separate function on UsdAttribute like GetRawAssetPath (name TBD) that would only be applicable for asset path-valued attributes.
  • Add an optional separate parameter to UsdAttribute::Get for controlling what gets put in the resulting SdfAssetPath object.

We haven’t come to any conclusions here though, so any feedback would certainly be welcome!

So again, I’m wondering how I can get at this “active” dictionary of values for a given prim?

Like you said, the “active” variables that apply depend on what site in the composed prim the expression that uses those variables is authored: e.g., is it authored just in the root layer of the stage – in which case the variables in the root and session layers would apply – or is it authored across some series of references, each of which may introduce other variables.

For an attribute, you should be able to get the the applicable dictionary of values via its UsdResolveInfo. Something like:

UsdAttribute attr = stage->GetAttributeAtPath(...);
UsdResolveInfo resolveInfo = attr.GetResolveInfo();
const VtDictionary expressionVars = resolveInfo.GetNode().GetLayerStack()->GetExpressionVariables();

We could definitely consider adding this as first-class API on UsdAttribute itself if that’s useful.

When flattening a layer stack I expect the source layer opinions to be preserved to the greatest extent possible… Stage variable expressions in variant opinions survive this function, FWIW. But expressions in reference and payload arcs do not

During flattening, we have to anchor any authored asset paths to the layer they were originally authored in to ensure that they resolve to the same location when they’re written to the flattened layer. For example, if you have /test/root.usda with a sublayer ./foo/bar/sub.usda, and in the sublayer you have a prim that references ./ref.usda, flattening has to anchor and write out /test/foo/bar/ref.usda for that reference. That way, the client is guaranteed that that reference will resolve to the same layer it did originally, no matter where you write the flattened layer out. And if that reference is specified using an expression, that expression has to be evaluated t do the anchoring.

I wonder if one thing we could do here though is to add an anchor function to the expression language. So for example, if you had:

asset a = @`"./foo/${VAR}/baz.png"`@

flattening could detect that a is an expression and author:

asset a = @`anchor(<source layer path>, "./foo/${VAR}/baz.png")`@

That would keep the original expression but still ensure that evaluating and resolving it later on would give you the same result as before.

My first instinct is that adding a field to SdfAssetPath would be the way to go, because expressions may show up in composition arcs and maybe metadata too? A proliferation of new APIs for each of those situations seems like it would be a lot messier than just including the raw expression (if available) on any SdfAssetPath that USD can return through any API. As long as it’s not too expensive (in time or memory).

This is perfect, thank you! I don’t see a need for a single-line API for this, though something in the expression-related docs with this sample code would be really helpful.

Right, thank you, this certainly explains the current behavior.

That sounds like it might be a good general solution. In my case, Solaris is always writing out anonymous layers to disk, so the <source layer path> argument is not really applicable, and this is not how we would want things to work.

In general Houdini expects users to author absolute paths everywhere, and we convert them to relative paths when saving to disk using “output processors”. In many cases handing the raw expression to those output processors would probably work fine, so our first crack at this will probably just involve passing through the raw expressions.

I’m not sure what other options might be useful at the UsdUtils level for other authoring scenarios, but for our case I just want to make sure there is a way for Houdini to have full control over how expressions are handled. And I think I already have using the Usd APIs directly instead of going through UsdUtils, so I actually think I’m fine with how the flattening works already.

Thanks,
Mark