How to properly reload reference and payloads?

Hello,
I’m looking to implement a simple Reload function (for reference or payload - coming from another usd file). Meaning, given a prim that has a payload or reference in it, what would be the best way to reload (for the cases where the reference has changed outside)?

I tried the simple way:

prim.GetStage()->Load(_prim.GetPath(), UsdLoadWithDescendants);

This didn’t work, my guess is that it can’t resolve that “prim” in this case it’s another file?

I also tried this other way:

for (const auto& child : _prim.GetPrimIndex().GetRootNode().GetChildrenRange()) {
    child.GetLayerStack()->GetIdentifier().rootLayer->Reload();
}

This approach a got the new changes, but this applies to the entire layer, not only a prim? So if I had several prims, it would “reload” all of them.

Would anyone have an idea for a better solution for this?
Thanks in advance

Is UsdStage::Reload() too large a granularity for you? It will find and reload all (and only) the references/payloads/subLayers whose timestamps have changed.

Sorry - missed the end of your post. USD reloads by layer, and layers are cached and shared. So it’s not possible to either:
a) reload a reference and have the new data apply just to a particular prim that references the same layer, but leave other prims that reference the same layer unchanged
b) have just some of the descendant prims defined by the referenced layer be updated, but not others, when reloading the referenced layer (couldn’t tell if you were asking about this case).

Hi @spiff ,
Thanks for the response. Your second replies explains the behavior that I’m seeing. It makes sense now, thank you.

I would use UsdStage::Reload if I could, but it doesn’t seem to work for me. Are there conditions or assumptions that would make it fail?

stage
- prim1 (payload file2.usd)

Calling :

prim1->GetStage()->Reload(prim1.GetPath(), UsdLoadWithDescendants)

Doesn’t seem to reload anything.

Reload() will only do anything if the ArResolver determines the asset has been updated. The ArDefaultResolver uses ArFilesystemAsset::GetModificationTimestamp() to make that determination, comparing with the last modification time when the file asset was initially read (or last Reloaded)

Hi @spiff,
I was able to play a bit more with this.
The stage->Reload() doesn’t work for us. We would like a little bit more control over what the user choose to reload.

The following code works for to reload a certain _prim layer:

for (const auto& child : _prim.GetPrimIndex().GetRootNode().GetChildrenRange()) {
    child.GetLayerStack()->GetIdentifier().rootLayer->Reload();
}

However, if that _prim reference has any other child reference, it won’t reload those. Is that expected?

Hi @barbalt,

I think it will be a little tricky to do what you are asking for with the existing Python API. There are considerations of efficiency (ex: we don’t want to reload each layer one by one and update the scene after each) and correctness (ex: we want to re-try reference arcs to layers that could not be resolved previously, which means they would not be part of the Prim Index).

Would you mind filing a request on the OpenUSD github about this?

Hi @blevin , thank you for the reply!

I’ll make a simple scenario just to clarify that this is the case. Imagine the I open a stage with the following city district file:

city_district.usd:

def Xform "city_district" (
    kind = "assembly"
)
{
    def Xform "skatepark" (
        prepend references = @skatepark.usd@
    )
    {
    }
}

Then skatepark.usd file is:

def Xform "skatepark" (
    kind = "assembly"
)
{
    def Xform "ramp" (
        prepend payload = @ramp.usd@
        variants = {
            string type = "ramp2"
        }
    )
    {
        double3 xformOp:translate = (0, 0, -588.8887136544495)
        uniform token[] xformOpOrder = ["xformOp:translate"]
    }
}

And lastly ramp.usd is just a regular mesh.

In the example above, if I update the ramp.usd file, then reload the skatepark prim on the city_district, it won’t update to the newest changes of ramp.usd.
The same seems to be true for sublayers. If we load city_district as a stage and reload that root layer, it still won’t bring in the changes I’ve made to ramp.usd.
That’s the currently expected behavior?

Thanks once again for your time

if I update the ramp.usd file, then reload the skatepark prim on the city_district, it won’t update to the newest changes of ramp.usd

If you mean running the for() loop code above with _prim set to the skatepark prim, yes, that is correct.

The reason is that the prim index for </city_district/skatepark> does not contain any details about child prims, such as </city_district/skatepark/ramp>.

So, one of the things a fuller implementation of a “reload prim” or “reload model” API would do is to also traverse child prims to discover additional scene description used by them. This could use UsdPrim::GetDescendants().

You would also want to reload all layers in each used layer stack, not only root layer (PcpLayerStack::GetLayers()).

Last, you would want to do this as two steps: a first pass to collect a set of layers (ex: a std::set<SdfLayerHandle>), then a pass to reload them all in one batch with SdfLayer::ReloadLayers(layerSet).

That should get you close, but it wouldn’t handle the case of re-trying reference arcs that failed to resolve previously, such as if there was a typo and it had been accidentally an uppercase Ramp.usd.

If we load city_district as a stage and reload that root layer, it still won’t bring in the changes I’ve made to ramp.usd

Yes, that is correct. Reloading a layer only refreshes the contents of that layer; any other layers that had already been loaded will not be updated. If the new content of the city_district.usd layer includes references to new layers that had not previously been used, then those will be loaded.

hope this helps,
-B

This a great and detailed explanation.
Thank you very much.

Hello @blevin ,
Once again thank you so much for help. I tried following the steps you suggested and still could only get the immediate references to reload. Anything that were being referenced by those references stay as they were after reload.

I figured I would share some of the things I tried:

I tried using UsdPrimCompositionQuery

UsdPrimCompositionQuery::Filter   filter;
filter.arcTypeFilter = UsdPrimCompositionQuery::ArcTypeFilter::ReferenceOrPayload;
filter.dependencyTypeFilter = UsdPrimCompositionQuery::DependencyTypeFilter::Direct;

auto query = UsdPrimCompositionQuery(_prim);
query.SetFilter(filter);
for (auto arc : query.GetCompositionArcs()) {
    layersHandle.emplace(arc.GetTargetLayer());
}

SdfLayer::ReloadLayers(layersHandle);

Then I tried with descendants

for (const UsdPrim& descendant : _prim.GetDescendants()) {
    for (auto l : descendant.GetPrimIndex().GetRootNode().GetLayerStack()->GetLayers()) {
        layersHandle.emplace(l);
    }
}

SdfLayer::ReloadLayers(layersHandle);

I’m willing to try any other suggestions, but if none is available, I appreciate all the help.

Thank you

Hi @barbalt, in your second snippet looping over descendants, you’ll need to use a full PrimIndex traversal, and insert every layer in each node’s layer stack.

In addition, the range returned by UsdPrim::GetDescendants() does not include the given prim, so you’ll need to handle that as well.

Something like this:

std::set<SdfLayerHandle> layerSet;

// Handle prim:
// For each prim index node (i.e., composition arc) in prim...
for (const auto& node : _prim.GetPrimIndex().GetNodeRange()) {
    // For each layer in the layer stack used by the composition arc...
    for (const auto& layer: node.GetLayerStack()->GetLayers()) {
        // ...add it to the set
        layerSet.insert(layer);
    }
}

// Next, handle prim's descendants the same way
for (const UsdPrim& descendant : _prim.GetDescendants()) {
    for (const auto& node : descendant.GetPrimIndex().GetNodeRange()) {
        for (const auto& layer: node.GetLayerStack()->GetLayers()) {
            layerSet.insert(layer);
        }
    }
}

// Reload all layers discovered to be in-use by this prim and its descendants
SdfLayer::ReloadLayers(layerSet);

If you prefer, it should be possible to use UsdPrimCompositionQuery instead of traversing prim.GetPrimIndex().GetNodeRange() here, but note that you’ll still need to access the node in each arc to pull on its full layer stack; GetTargetLayer() only returns the root layer of the layer stack, not any sublayers.

hope this helps,
-B

This worked flawlessly! Thank you so much for the help.