We’re starting to experiment with the UsdNamespaceEditor in our USD workflows and are curious how people are tracking changes for Undos etc.
e.g we’re finding that its of course very helpful in relocating prims, and reflowing rels etc…
However, it’s not always clear how to undo its operation.
One could apply the inverse transformation, but it’s not necessarily guaranteed that it returns you to the same prior state in case there are now multiple specs that contribute to a specific path. (ie if I find and replace all instances of a word in a text document, I can’t just find and replace back since that could grab more than I originally intended)
Is there any facility in the namespace editor to get a list of operation changes that the namespace editor did so that a DCC or the like can undo those operations? Is there anything else folks would recommend?
If nothing exists, would there be an appetite for such an addition?
Hi Dhruv. For Undo, you might want to consider capturing operations via a SdfLayerStateDelegate. Anything the namespace editor is doing should bottom out to the setting of a handful of fields.
Its core is an SdfLayerStateDelegate that captures the inverse functions of each Sdf modification and adds them to a stack inside an “undo block”. When the undo block is undone, each inverse function is called in the reverse order to reset the state. On the client side, bulk changes happen inside undo blocks so that all the changes are reversed at once.
We added some extra handling for redo support, plus undoing the deletion of hierarchies and capturing all the deleted child fields and not just the exact deleted prim spec, among other things.
Obviously this is only for one layer at a time, which works for us since we are just undoing changes on the session layer. Coordinating undoing changes across multiple layers would be an interesting exercise though, if needed.
That’s an interesting idea! If we want to keep track of all the changes made to a single layer, I’d have to create a subclass of SdfLayerStateDelegateBase and override all its methods. That way, I can store the information and make it accessible. I could then reverse those operations. Is that right? If so, that’s a great step in the right direction! Thanks for pointing that out!
However, we’re using the Usd namespace editor, not the Sdf one, and the Usd namespace editor can target many different layers, depending on the edits we’re making and the prims that are affected. Are you attaching that Sdf delegate to every layer that is brought in?
I think that’s roughly what you’re doing, Jonathan?
Yup, you have the right idea of how to get the SdfLayerStateDelegate approach working.
Unfortunately yes if you’re targeting an arbitrary number of layers then you’d need to create a delegate for each layer and have each feed into the same undo block. How may layers are you typically dealing with? I suspect this would be the most efficient approach though. We tried a few different methods before settling on the layer state delegate
Depending on the size of the stages you’re working with you might want to try a snapshot-based method instead (what we used to do). When an undo block started we’d call SdfLayer::TransferContent to copy the old layer state, which meant undoing was just copying the old layer back to the composed layer. If your stages are fairly small this might work for you, though you’d effectively be making copies of the entire stage instead of just one layer.
I took a closer look at the implementation of UsdNamespaceEditor and it actually seems like undo could be implemented there, since like the SdfLayerStateDelegate it’s applying changes to layers through Sdf directly, although I suspect figuring out what the public API would be for undo would likely be at least half the challenge.
Presto has built an Undo System on top of SdfLayerStateDelegate, which tracks edits across all open layers in transactions called “Interactions”. It also can rtack other (non-layer-based) kinds of transactions, does logging, and other things. We’re reluctant to adapt (which would be a project) this part of the Presto application framework to OpenUSD both because a) it was designed for Presto’s needs/workflows, b) we think each DCC would want to integrate USD mutations into its own undo management in possibly different ways.
But we do believe building on top of LayerStateDelegate is a practical way to do transaction management and Undo. We don’t want to start adding API or policy around Undo at the Usd object level, so I don’t think we’ll be considering such changes to UsdNamespaceEditor, @JonathanPenner . You will see, in the coming releases, more specific UsdNotice support for broadcasting that a namespace-editing operation has just happened, for clients who can do something smarter without trying to pair otherwise-unrelated prim resync notices…
We’ll also, eventually, provide an opt-in ability to track UsdPrim identity across namespace editing changes (including undoing such changes, if a client holds on to the identities). We’ve found this useful in Presto GUI and other contexts.
@spiff I totally get that you wouldn’t want to open that can of worms! Even internally at Netflix Animation Studios (formerly Animal Logic) our undo system was designed for our lighting DCC Filament, and different departments had very different requirements for what undo would look like (such as Maya).
My thought of adding undo-like support to UsdNamespaceEditor would be that during ApplyChanges() it would build the stack of inverse changes to the layers that could be retrieved with something like GetInverseChanges() that could be applied at a later time by whatever system needed to do so. But again, I understand wanting to keep the concern of undo separate.