Query whether stage edit target is targeting a particular variant

Is there any way to check whether the current stage edit target targets a particular variant set? :slightly_smiling_face:
It seems that in this check:

variant_set.GetVariantSelection() == variant_name
and stage.GetEditTarget() == variant_set.GetVariantEditTarget()

The second statement never returns True.
That’s even the case if I explicitly pass it a layer on setting it and detecting it after?

For example:

edit_target = variant_set.GetVariantEditTarget(layer)
stage.SetEditTarget(edit_target)
variant_set.GetVariantEditTarget(layer) == edit_target 
# False

Even though:

self._variant_set.GetVariantEditTarget(layer) == self._variant_set.GetVariantEditTarget(layer)
# True

So it is capable of comparing unique instances.

Say I wanted to validate whether I’m currently targeting a particular variant in a variant set how would I do so?

I figured this out with a little bit of reading the C++ source code. In short, it’s the case that the second call to Usd.VariantSet.GetVariantEditTarget after having set the edit target on the stage will now generate a “nested” edit target with the variant set path mapping.

Let’s give this a go. Here’s a reproducable stage, single prim with some variants:

from pxr import Usd

# Create a stage with a root xform
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/root", "Xform")

# Add variant set
variant_sets = prim.GetVariantSets()
variant_set = variant_sets.AddVariantSet("model")

# Add some variants
variant_set.AddVariant("main")
variant_set.AddVariant("damaged")
variant_set.AddVariant("ice")

# Set the variant selection
variant_set.SetVariantSelection("main")

Now, as we have a selected variant for the variant set we can define the edit target:

edit_target = variant_set.GetVariantEditTarget()
stage.SetEditTarget(edit_target)

Now let’s debug that edit target:

print(edit_target.layer())
# as expected, will be the stage's root layer
mapping = edit_target.GetMapFunction()
print(mapping.sourceToTargetMap)
# {Sdf.Path('/'): Sdf.Path('/'), Sdf.Path('/root{model=main}'): Sdf.Path('/root')}

As we can see it understands the variant set’s mapping in the edit target.

Now however if I would call the variant_set.GetVariantEditTarget() again I’m appending that variant selection for the path mapping:

edit_target = variant_set.GetVariantEditTarget()
mapping = edit_target.GetMapFunction()
print(mapping.sourceToTargetMap)
# {Sdf.Path('/'): Sdf.Path('/'), Sdf.Path('/root{model=main}{model=main}'): Sdf.Path('/root')}

Which might be a bit unexpected for this particular case but I suppose it so that one could edit e.g. another variant set’s variant within the first variant. So that you could add an opinion that if in the {model=main} variant then apply this opinion to e.g. {look=main}?

Either way, the difference in path mapping explains why the edit targets were indeed not equal.


So for my initial use case - what did I want? No matter what I want to set the target to the root layer, and to (non-nested) variant selection. Less flexible, more limited in scope but easier to grasp - and best of all, was my intended use case.

How?

Well, we can use Usd.EditTarget.ForLocalDirectVariant.

layer = stage.GetRootLayer()
path = prim.GetPath().AppendVariantSelection("model", "main")
edit_target = Usd.EditTarget.ForLocalDirectVariant(layer, path)

As we can see - running that again was my intended edit target and what I expected to be “equal” in the comparison:

edit_target = Usd.EditTarget.ForLocalDirectVariant(layer, path)
mapping = edit_target.GetMapFunction()
print(mapping.sourceToTargetMap)
# {Sdf.Path('/'): Sdf.Path('/'), Sdf.Path('/root{model=main}'): Sdf.Path('/root')}

stage.SetEditTarget(edit_target)
edit_target = Usd.EditTarget.ForLocalDirectVariant(layer, path)
mapping = edit_target.GetMapFunction()
print(mapping.sourceToTargetMap)
# {Sdf.Path('/'): Sdf.Path('/'), Sdf.Path('/root{model=main}'): Sdf.Path('/root')}

And the comparison is equal:

stage.GetEditTarget() == Usd.EditTarget.ForLocalDirectVariant(layer, path)
# True
2 Likes

Very resourceful, @BigRoyNL ! FYI, you can forego having to manually deal with selection paths by leveraging a UsdEditContext to temporarily reset the stage back to a “simple” EditTarget that just targets the same layer as the current EditTarget. Then construct the EditTarget for the variant you care about, and once you leave the scoping of the EditContext, you should be able to compare it to the stage’s “original” EditTarget successfully.

Also note that you can only ever target a VariantSet’s selected Variant.

Thanks - I wanted avoid redundant extra Tf.Notice signals being sent on edit targets being changed during contexts; or do those not emit if for edit contexts?

@BigRoyNL Thanks for posting some of this stuff. I’m learning a lot of great things on this forum and I appreciate your attention to detail and follow-up on topics.

Cheers.

I think there’s a good argument to be made that the use of UsdEditContext should (at least optionally) suppress the sending of TargetChanged notices, because it’s often used for “implementation-level” decisions about where to put some edits, and not something that the user needs to be made aware of. Would you mind filing that as well, @BigRoyNL ?

Are you saying that UsdEditContext would in itself also keep a SdfChangeBlock context or is holding of notice signals different from that? Like is there a “hold notices” context one can already use that is not holding off all SDF changes?

Anyway, I’ve created an issue referencing your comment: UsdEditContext should (at least optionally) suppress the sending of TargetChanged notices · Issue #2845 · PixarAnimationStudios/OpenUSD · GitHub

An SdfChangeBlock does suppress certain notices, but not the one you mentioned. I’m proposing only blocking the EditTargetChanged notice, by telling the Stage not to send it.