From 84338319322b74e26ba8c03c50d166a31512d127 Mon Sep 17 00:00:00 2001 From: pixar-oss Date: Mon, 7 Oct 2024 09:33:35 -0700 Subject: [PATCH] UsdNamespaceEditor now supports adding dependent stages and processing all edits necessary to fix up downstream namespace dependencies on those stages. Also fixed an issue in PcpGatherDependentNamespaceEdits that wasn't accounting for newly authored relocates when determining if unedited specs would be conflict in ancestor nodes. (Internal change: 2343533) --- pxr/usd/pcp/dependentNamespaceEditUtils.cpp | 103 +++++++- pxr/usd/pcp/dependentNamespaceEditUtils.h | 8 + pxr/usd/usd/namespaceEditor.cpp | 275 +++++++++++++------- pxr/usd/usd/namespaceEditor.h | 68 +++-- pxr/usd/usd/stage.h | 1 + pxr/usd/usd/wrapNamespaceEditor.cpp | 4 + 6 files changed, 336 insertions(+), 123 deletions(-) diff --git a/pxr/usd/pcp/dependentNamespaceEditUtils.cpp b/pxr/usd/pcp/dependentNamespaceEditUtils.cpp index 107e0dc872..d389531d05 100644 --- a/pxr/usd/pcp/dependentNamespaceEditUtils.cpp +++ b/pxr/usd/pcp/dependentNamespaceEditUtils.cpp @@ -235,8 +235,10 @@ class _PrimIndexDependentNodeEditProcessor void AddProcessEditsAtNodeTask( const PcpNodeRef &node, const SdfPath &oldPath, - const SdfPath &newPath) { - _InsertNodeTask({node, oldPath, newPath}); + const SdfPath &newPath, + bool willBeRelocated) { + _InsertNodeTask({node, oldPath, newPath, + /* isImpliedClassTask = */ false, willBeRelocated}); } // Processes all tasks producing all dependent edits for the prim index @@ -252,6 +254,7 @@ class _PrimIndexDependentNodeEditProcessor SdfPath oldPath; SdfPath newPath; bool isImpliedClassTask = false; + bool willBeRelocated = false; }; friend std::ostream & @@ -262,6 +265,9 @@ class _PrimIndexDependentNodeEditProcessor if (nodeTask.isImpliedClassTask) { out << " (isImpliedClassTask)"; } + if (nodeTask.willBeRelocated) { + out << " (willBeRelocated)"; + } return out; } @@ -658,7 +664,14 @@ _PrimIndexDependentNodeEditProcessor::_AddSpecMoveEdits( // move the specs at this node and log a warning as we only want to move // these specs if it constitutes moving the entire composed prim stack at // this node. - if (_HasUneditedUpstreamSpecConflicts(node, oldSpecPath)) { + // + // We ignore this check if the node has new relocates that will be applied + // to it by the initial edit. Relocates are used specifically for "moving" + // specs from weaker nodes without editing their specs so we actually expect + // unedited spec conflicts in this case and have used relocates to handle + // them. + if (!nodeTask.willBeRelocated && + _HasUneditedUpstreamSpecConflicts(node, oldSpecPath)) { return; } @@ -1552,6 +1565,8 @@ PcpGatherDependentNamespaceEdits( const SdfPath &oldPrimPath, const SdfPath &newPrimPath, const SdfLayerHandleVector &affectedLayers, + const PcpLayerStackRefPtr &affectedRelocatesLayerStack, + const SdfLayerHandle &addRelocatesToLayerStackEditLayer, const std::vector &dependentCaches) { TRACE_FUNCTION(); @@ -1562,6 +1577,75 @@ PcpGatherDependentNamespaceEdits( // Scratch space for spec move edits. _LayerSpecMovesScratch layerSpecMovesScratch; + // We don't author new relocates for the dependent prim indexes outside of + // the explicit new relocates that we will determine for the + // affectedRelocatesLayerStack if it is provided. Because of this, at each + // dependent node we look for conflicting specs in its subtree that will not + // be edited (and otherwise would require something like relocates) in order + // to log a warning that the composed prim stack won't be fully maintained + // by the edit. However, we won't have conflicting specs in nodes that are + // affected by the relocates edits above even if its subtree has unedited + // conflicting specs as the new relocates will effectively move those specs + // for us. All of this is to say that we need to pass the fact that the + // layer will have a relocates edit to the layer's dependent nodes so that + // they know to skip the conflicting subtree specs check. + // + // XXX: Note that this is actually a little simplified as what we really + // need to know at each node is whether its layer stack's compose relocates + // will, after the above relocates are applied, effectively relocate the + // subtree specs that would've otherwise had to be moved. But that is much + // more complex and this simpler method gets the job done for the vast + // majority of cases. + // + // Create a new list of each of the input affected layers paired with + // whether it has a relocates edit (which we initialize to false for all to + // start.) + std::vector> + affectedLayersAndHasRelocatesEdits; + affectedLayersAndHasRelocatesEdits.reserve(affectedLayers.size()); + for (const auto &layer : affectedLayers) { + affectedLayersAndHasRelocatesEdits.push_back({layer, false}); + } + + // If we were passed a layer stack to add relocates to, we'll use the + // relocates edit builder to process those now. + if (affectedRelocatesLayerStack) { + PcpLayerRelocatesEditBuilder builder( + affectedRelocatesLayerStack, addRelocatesToLayerStackEditLayer); + std::string error; + if (!builder.Relocate(oldPrimPath, newPrimPath, &error)) { + TF_CODING_ERROR("Cannot get relocates edits because: %s", + error.c_str()); + } + // For each initial relocates edit, we do three things: + // 1. Make sure the layer is put in the list of affected layers if it + // isn't already. Adding a relocate is the same as moving a spec as + // far as needing to update dependent prim indexes is concerned. + // 2. Marking the affected layer as having a relocates edit for when we + // add the initial dependent node tasks. + // 3. Move the edit into relocates edit results that is returned at the + // end. + for (auto &relocatesEdit : builder.GetEdits()) { + // Scoping for safety/clarity as the const reference of layer to + // relocatesEdit.first would become invalid after the relocatesEdit + // is inserted by rvalue reference. + { + const SdfLayerHandle &layer = relocatesEdit.first; + auto foundIt = std::find_if( + affectedLayersAndHasRelocatesEdits.begin(), + affectedLayersAndHasRelocatesEdits.end(), + [&](const auto &entry) { + return layer == entry.first;}); + if (foundIt == affectedLayersAndHasRelocatesEdits.end()) { + affectedLayersAndHasRelocatesEdits.push_back({layer, true}); + } else { + foundIt->second = true; + } + } + edits.dependentRelocatesEdits.insert(std::move(relocatesEdit)); + } + } + for (const PcpCache *cache : dependentCaches) { _PRINT_DEBUG_SCOPE( "Computing dependent namespace edits for PcpCache %s", @@ -1571,7 +1655,10 @@ PcpGatherDependentNamespaceEdits( // that depend on the old prim site in this layer and determine what // additional edits are necesssary to propagate edits to composition // dependencies as best as possible. - for (const auto &layer : affectedLayers) { + for (const auto &entry : affectedLayersAndHasRelocatesEdits) { + + const SdfLayerHandle &layer = entry.first; + const bool hasRelocatesEdits = entry.second; // Find all prim indexes which depend on the old prim path in this // layer. We recurse on site because moving or deleting a prim spec @@ -1637,8 +1724,14 @@ PcpGatherDependentNamespaceEdits( Pcp_ForEachDependentNode( dep.sitePath, layer, dep.indexPath, *cache, [&](const SdfPath &depIndexPath, const PcpNodeRef &node) { + // If the dependent layer is was affected by the + // initial relocates edit, indicate in the node task + // that the node task has a relocates edit. + // XXX: This is the part that is a little oversimplified + // as was mentioned earlier in this function. dependentNodeProcessor.AddProcessEditsAtNodeTask( - node, oldPrimPath, newPrimPath); + node, oldPrimPath, newPrimPath, + /*willBeRelocated = */ hasRelocatesEdits); }); dependentNodeProcessor.ProcessTasks(); } diff --git a/pxr/usd/pcp/dependentNamespaceEditUtils.h b/pxr/usd/pcp/dependentNamespaceEditUtils.h index 7d01aba877..92d492fb57 100644 --- a/pxr/usd/pcp/dependentNamespaceEditUtils.h +++ b/pxr/usd/pcp/dependentNamespaceEditUtils.h @@ -83,12 +83,20 @@ class PcpDependentNamespaceEdits { /// that would be affected by these edits and computes a full set of edits that /// would be required to maintain these dependent prim indexes' composed prim /// stacks, possibly moving the prim index to a new prim path if necessary. +/// If \p addRelocatesToLayerStack is provided, this will also add a new +/// relocates edit to the necessary layers in the layer stack that moves +/// oldPrimPath to newPrimPath. The layer \p addRelocatesToLayerStackEditLayer +/// provided is only relevant when the relocates layer stack is also provided as +/// it determines which specific layer in the layer stack will have a new +/// relocates entry added to it (see PcpLayerRelocatesEditBuilder). PCP_API PcpDependentNamespaceEdits PcpGatherDependentNamespaceEdits( const SdfPath &oldPrimPath, const SdfPath &newPrimPath, const SdfLayerHandleVector &affectedLayers, + const PcpLayerStackRefPtr &addRelocatesToLayerStack, + const SdfLayerHandle &addRelocatesToLayerStackEditLayer, const std::vector &dependentCaches); /// Gathers the list of layers that need to be edited to perform the spec move diff --git a/pxr/usd/usd/namespaceEditor.cpp b/pxr/usd/usd/namespaceEditor.cpp index ff2187a4df..922df3f5a4 100644 --- a/pxr/usd/usd/namespaceEditor.cpp +++ b/pxr/usd/usd/namespaceEditor.cpp @@ -294,6 +294,31 @@ UsdNamespaceEditor::UsdNamespaceEditor( { } +void +UsdNamespaceEditor::AddDependentStage(const UsdStageRefPtr &stage) +{ + if (!stage || stage == _stage) { + return; + } + _ClearProcessedEdits(); + _dependentStages.insert(stage); +} + +void +UsdNamespaceEditor::RemoveDependentStage(const UsdStageRefPtr &stage) +{ + _ClearProcessedEdits(); + _dependentStages.erase(stage); +} + +void +UsdNamespaceEditor::SetDependentStages(const UsdStageRefPtrVector &stages) +{ + for (const auto &stage : stages) { + AddDependentStage(stage); + } +} + bool UsdNamespaceEditor::DeletePrimAtPath( const SdfPath &path) @@ -560,12 +585,14 @@ class UsdNamespaceEditor::_EditProcessor { // Creates a processed edit from an edit description. static UsdNamespaceEditor::_ProcessedEdit ProcessEdit( const UsdStageRefPtr &stage, + const _StageSet &dependentStages, const UsdNamespaceEditor::_EditDescription &editDesc, const UsdNamespaceEditor::EditOptions &editOptions); private: _EditProcessor( const UsdStageRefPtr &stage, + const _StageSet &_dependentStages, const UsdNamespaceEditor::_EditDescription &editDesc, const UsdNamespaceEditor::EditOptions &editOptions, _ProcessedEdit *processedEdit); @@ -581,8 +608,11 @@ class UsdNamespaceEditor::_EditProcessor { void _GatherLayersToEdit(); void _GatherTargetListOpEdits(); - + + void _GatherDependentStageEdits(); + const UsdStageRefPtr & _stage; + const _StageSet &_dependentStages; const UsdNamespaceEditor::_EditDescription & _editDesc; const UsdEditTarget &_editTarget; const UsdNamespaceEditor::EditOptions & _editOptions; @@ -601,7 +631,7 @@ UsdNamespaceEditor::_ProcessEditsIfNeeded() const return; } _processedEdit = UsdNamespaceEditor::_EditProcessor::ProcessEdit( - _stage, _editDescription, _editOptions); + _stage, _dependentStages, _editDescription, _editOptions); } static @@ -747,20 +777,23 @@ _IsValidNewParentPath( UsdNamespaceEditor::_ProcessedEdit UsdNamespaceEditor::_EditProcessor::ProcessEdit( const UsdStageRefPtr &stage, + const _StageSet &dependentStages, const _EditDescription &editDesc, const EditOptions &editOptions) { _ProcessedEdit processedEdit; - _EditProcessor(stage, editDesc, editOptions, &processedEdit); + _EditProcessor(stage, dependentStages, editDesc, editOptions, &processedEdit); return processedEdit; } UsdNamespaceEditor::_EditProcessor::_EditProcessor( const UsdStageRefPtr &stage, + const _StageSet &dependentStages, const UsdNamespaceEditor::_EditDescription &editDesc, const UsdNamespaceEditor::EditOptions &editOptions, _ProcessedEdit *processedEdit) : _stage(stage) + , _dependentStages(dependentStages) , _editDesc(editDesc) , _editTarget(stage->GetEditTarget()) , _editOptions(editOptions) @@ -771,11 +804,8 @@ UsdNamespaceEditor::_EditProcessor::_EditProcessor( return; } - // Add the edit to the processed SdfBatchNamespaceEdit. We use the index of - // "Same" specifically so renames don't move the object (it has no effect - // for any edits other than rename) - _processedEdit->edits.Add( - editDesc.oldPath, editDesc.newPath, SdfNamespaceEdit::Same); + // Copy the edit description. + _processedEdit->editDescription = _editDesc; // Validate whether the stage has the prim or property at the original path // that can be namespace edited. @@ -813,6 +843,10 @@ UsdNamespaceEditor::_EditProcessor::_EditProcessor( // to be edited when the edits are applied. _GatherLayersToEdit(); + // Gather all edits that need to be performed on dependent stages for prim + // indexes that would be affected by the initial layer edits. + _GatherDependentStageEdits(); + // Gather all the edits that need to be made to target path listOps in // property specs in order to "fix up" properties that have connections or // relationship targets targeting the namespace edited object. @@ -837,19 +871,12 @@ UsdNamespaceEditor::_EditProcessor::_ProcessNewPath() // For reparenting we have additional behaviors and validation to perform. if (_editDesc.editType == _EditType::Reparent) { - // For each layer we edit, we may need to create new overs for the new - // parent path and delete inert ancestor overs after moving a prim or - // property from its original parent, so add this info to the processed - // edit. - _processedEdit->createParentSpecIfNeededPath = - _editDesc.newPath.GetParentPath(); - _processedEdit->removeInertAncestorOvers = true; // Validate that the stage does have a prim at the new parent path to // reparent to. std::string whyNot; if (!_IsValidNewParentPath(_stage, _editDesc.oldPath, - _processedEdit->createParentSpecIfNeededPath, &whyNot)) { + _editDesc.newPath.GetParentPath(), &whyNot)) { _processedEdit->errors.push_back(std::move(whyNot)); return false; } @@ -966,16 +993,9 @@ UsdNamespaceEditor::_EditProcessor::_ProcessPrimEditRequiresRelocates( return; } - // Otherwise, use the relocates builder to get all the relocates metadata - // that needs to be authored in each layer to move old path to new path. - PcpLayerRelocatesEditBuilder builder( - _nodeForEditTarget.GetLayerStack(), _editTarget.GetLayer()); - std::string error; - if (!builder.Relocate(_editDesc.oldPath, _editDesc.newPath, &error)) { - TF_CODING_ERROR("Cannot get relocates edits because: %s", - error.c_str()); - } - _processedEdit->relocatesEdits = builder.GetEdits(); + // Otherwise, log that we will author relocates so that this will be + // accounted for when we compute the dependent stage namespace edits. + _processedEdit->willAuthorRelocates = true; } void @@ -1063,35 +1083,9 @@ UsdNamespaceEditor::_EditProcessor::_GatherLayersToEdit() return; } - // Collect every prim spec that exists for this prim path in the layer - // stack's layers. - for (const SdfLayerRefPtr &layer : layers) { - if (layer->HasSpec(_editDesc.oldPath)) { - _processedEdit->layersToEdit.push_back(layer); - } - } - - // Validate whether the necessary spec edits can actually be performed on - // each layer that needs to be edited. - for (const auto &layer : _processedEdit->layersToEdit) { - // The layer itself needs to be editable - if (!layer->PermissionToEdit()) { - _processedEdit->errors.push_back(TfStringPrintf("The spec @%s@<%s> " - "cannot be edited because the layer is not editable", - layer->GetIdentifier().c_str(), - _editDesc.oldPath.GetText())); - } - // If we're moving an object to a new path, the layer cannot have a - // spec already at the new path. - if (!_editDesc.newPath.IsEmpty() && layer->HasSpec(_editDesc.newPath)) { - _processedEdit->errors.push_back(TfStringPrintf("The spec @%s@<%s> " - "cannot be moved to <%s> because a spec already exists at " - "the new path", - layer->GetIdentifier().c_str(), - _editDesc.oldPath.GetText(), - _editDesc.newPath.GetText())); - } - } + _processedEdit->layersToEdit = PcpGatherLayersToEditForSpecMove( + _nodeForEditTarget.GetLayerStack(), + _editDesc.oldPath, _editDesc.newPath, &_processedEdit->errors); } void @@ -1178,10 +1172,10 @@ UsdNamespaceEditor::_EditProcessor::_GatherTargetListOpEdits() } } - // If we added relocates for this edit already, then the target paths - // authored across composition arcs will also be mapped by the + // If the edit will author relocates for the primary edit, then the target + // paths authored across composition arcs will also be mapped by the // relocation. - if (!_processedEdit->relocatesEdits.empty()) { + if (_processedEdit->willAuthorRelocates) { continue; } @@ -1259,52 +1253,51 @@ UsdNamespaceEditor::_ProcessedEdit::CanApply(std::string *whyNot) const return true; } -bool -UsdNamespaceEditor::_ProcessedEdit::Apply() +static bool +_ApplyLayerSpecMove( + const SdfLayerHandle &layer, const SdfPath &oldPath, const SdfPath &newPath) { - // This is to try to preemptively prevent partial edits when if any of the - // necessary specs can't be renamed. - if (std::string errorMsg; !CanApply(&errorMsg)) { - TF_CODING_ERROR(TfStringPrintf("Failed to apply edits to the stage " - "because of the following errors: %s", errorMsg.c_str())); - return false; - } + // Create an SdfBatchNamespaceEdit for the path move. We use the index of + // "Same" specifically so renames don't move the object out of its original + // order (it has no effect for any edits other than rename) + SdfBatchNamespaceEdit batchEdit; + batchEdit.Add(oldPath, newPath, SdfNamespaceEdit::Same); // Implementation function as this is optionally called with a cleanup // enabler depending on the edit type. - auto applyEditsToLayersFn = [&]() { - for (const auto &layer : layersToEdit) { - // While we do require that the new parent exists on the composed - // stage when doing a reparent operation, that doesn't guarantee - // that parent spec exists on every layer in which we have to move - // the source spec. Thus we need to ensure the parent spec of the - // new location exists by adding required overs if necessary. - if (!createParentSpecIfNeededPath.IsEmpty() && - !SdfJustCreatePrimInLayer( - layer, createParentSpecIfNeededPath)) { - TF_CODING_ERROR("Failed to find or create new parent spec " - "at path '%s' on layer '%s' which is necessary to " - "apply edits. The edit will be incomplete.", - createParentSpecIfNeededPath.GetText(), - layer->GetIdentifier().c_str()); - return false; - } + auto applyEditsToLayersFn = [&](const SdfPath &createParentSpecIfNeededPath) { + // While we do require that the new parent exists on the composed + // stage when doing a reparent operation, that doesn't guarantee + // that parent spec exists on every layer in which we have to move + // the source spec. Thus we need to ensure the parent spec of the + // new location exists by adding required overs if necessary. + if (!createParentSpecIfNeededPath.IsEmpty() && + !SdfJustCreatePrimInLayer( + layer, createParentSpecIfNeededPath)) { + TF_CODING_ERROR("Failed to find or create new parent spec " + "at path '%s' on layer '%s' which is necessary to " + "apply edits. The edit will be incomplete.", + createParentSpecIfNeededPath.GetText(), + layer->GetIdentifier().c_str()); + return false; + } - // Apply the namespace edits to the layer. - if (!layer->Apply(edits)) { - TF_CODING_ERROR("Failed to apply batch edit '%s' on layer '%s' " - "which is necessary to apply edits. The edit will be " - "incomplete.", - TfStringify(edits.GetEdits()).c_str(), - layer->GetIdentifier().c_str()); - return false; - } + // Apply the namespace edits to the layer. + if (!layer->Apply(batchEdit)) { + TF_CODING_ERROR("Failed to apply batch edit '%s' on layer '%s' " + "which is necessary to apply edits. The edit will be " + "incomplete.", + TfStringify(batchEdit.GetEdits()).c_str(), + layer->GetIdentifier().c_str()); + return false; } + return true; }; - SdfChangeBlock changeBlock; - if (removeInertAncestorOvers) { + const bool isReparent = !newPath.IsEmpty() && + newPath.GetParentPath() != oldPath.GetParentPath(); + if (isReparent) { // Moving a spec may leave the ancnestor specs as an inert overs. This // could easily be caused by reparenting a prim back to its original // parent (essentially an "undo") after a reparent that needed to create @@ -1313,18 +1306,58 @@ UsdNamespaceEditor::_ProcessedEdit::Apply() // moved path so that a reparent plus an "undo" can effectively leave // layers in their original state. SdfCleanupEnabler cleanupEnabler; - if (!applyEditsToLayersFn()) { + if (!applyEditsToLayersFn(newPath.GetParentPath())) { return false; } } else { - if (!applyEditsToLayersFn()) { + if (!applyEditsToLayersFn(SdfPath::EmptyPath())) { return false; } } - // Set any necessary relocates. - for (const auto &[layer, relocatesValue] : relocatesEdits) { - layer->SetRelocates(relocatesValue); + return true; +} + +bool +UsdNamespaceEditor::_ProcessedEdit::Apply() +{ + // This is to try to preemptively prevent partial edits when if any of the + // necessary specs can't be renamed. + if (std::string errorMsg; !CanApply(&errorMsg)) { + TF_CODING_ERROR(TfStringPrintf("Failed to apply edits to the stage " + "because of the following errors: %s", errorMsg.c_str())); + return false; + } + + SdfChangeBlock changeBlock; + + if (editDescription.IsPropertyEdit()) { + // For a property edit, we just have to move the specs in the layers to + // edit. + for (const auto &layer : layersToEdit) { + _ApplyLayerSpecMove(layer, + editDescription.oldPath, editDescription.newPath); + } + } else { + // For prim edits, the dependent stage edits are always computed for + // at least the primary stage so all necessary edits will be contained + // in those computed edits. + for (const auto &[layer, editVec] : + dependentStageNamespaceEdits.layerSpecMoves) { + for (const auto &edit : editVec) { + _ApplyLayerSpecMove(layer, edit.oldPath, edit.newPath); + } + } + + for (const auto &edit : + dependentStageNamespaceEdits.compositionFieldEdits) { + edit.layer->SetField(edit.path, edit.fieldName, edit.newFieldValue); + } + + for (const auto &[layer, relocates] : + dependentStageNamespaceEdits.dependentRelocatesEdits) { + layer->SetRelocates(relocates); + } } // Perform any target path listOp fixups necessary now that the namespace @@ -1349,5 +1382,47 @@ UsdNamespaceEditor::_ProcessedEdit::Apply() return true; } +void +UsdNamespaceEditor::_EditProcessor::_GatherDependentStageEdits() +{ + // Composition dependencies are only relevant for prim namespace edits. + if (_editDesc.IsPropertyEdit()) { + return; + } + + // Get the PcpCaches for each dependent stage. The primary stage is always + // a dependent so put its cache at the front. Note that _dependentStages + // are a uniqued set and should never contain the primary stage. + std::vector dependentCaches; + dependentCaches.reserve(_dependentStages.size() + 1); + dependentCaches.push_back(_stage->_GetPcpCache()); + for (const auto &stage : _dependentStages) { + dependentCaches.push_back(stage->_GetPcpCache()); + } + + // If we need and allow relocates for the primary edit, then we pass the + // layer stack where we'll author them to the dependent edits function + // which will compute the layer stack's relocates edits for us. + const PcpLayerStackRefPtr &addRelocatesToLayerStack = + _processedEdit->willAuthorRelocates ? + _nodeForEditTarget.GetLayerStack() : PcpLayerStackRefPtr(); + + // Gather all the dependent edits for all stage PcpCaches. + _processedEdit->dependentStageNamespaceEdits = + PcpGatherDependentNamespaceEdits( + _editDesc.oldPath, _editDesc.newPath, _processedEdit->layersToEdit, + addRelocatesToLayerStack, _editTarget.GetLayer(), + dependentCaches); + + // XXX: We may want an option to allow users to treat warnings as errors or + // to return warning as part of calling CanApplyEdits. But for now we just + // emit the warnings. + if (!_processedEdit->dependentStageNamespaceEdits.warnings.empty()) { + TF_WARN("Encountered warnings processing dependent namespace edits: %s", + TfStringJoin(_processedEdit->dependentStageNamespaceEdits.warnings, + "\n ").c_str()); + } +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/usd/usd/namespaceEditor.h b/pxr/usd/usd/namespaceEditor.h index 25a7e56beb..aebb2115a9 100644 --- a/pxr/usd/usd/namespaceEditor.h +++ b/pxr/usd/usd/namespaceEditor.h @@ -13,10 +13,9 @@ #include "pxr/usd/usd/api.h" #include "pxr/usd/usd/common.h" #include "pxr/usd/usd/stage.h" -#include "pxr/usd/pcp/layerRelocatesEditBuilder.h" +#include "pxr/usd/pcp/dependentNamespaceEditUtils.h" #include "pxr/usd/sdf/namespaceEdit.h" - PXR_NAMESPACE_OPEN_SCOPE /// @warning @@ -52,6 +51,41 @@ class UsdNamespaceEditor const UsdStageRefPtr &stage, const EditOptions &editOptions); + /// \name Dependent Stages + /// + /// Dependent stages are additional stages that may have composition + /// dependencies on the layer edits made for the editor's primary stage. + /// By adding dependent stages, the editor can make additional edits so that + /// affected composition arcs and specs that depend on affected composition + /// in composed prims on these stages are updated to compose with the moved + /// prim specs or, in the case of deletions, removed when the specs they + /// depend on are removed. + /// + /// Dependencies in the dependent stages are based only what is currently + /// loaded for those stages. In other words, the editor cannot find and + /// edit dependencies from unloaded payloads, inactive prim children, + /// prims that are load mask filtered, unselected variants, etc. The primary + /// stage of this editor is always a dependent stage, meaning that edits + /// will always be made to maintain affected composition dependencies in the + /// primary stage. + /// + /// @{ + + /// Adds the given \p stage as a dependent stage of this namespace editor. + USD_API + void AddDependentStage(const UsdStageRefPtr &stage); + + /// Removes the given \p stage as a dependent stage of this namespace editor. + USD_API + void RemoveDependentStage(const UsdStageRefPtr &stage); + + /// Sets the list of dependent stages for this namespace editor to + /// \p stages. + USD_API + void SetDependentStages(const UsdStageRefPtrVector &stages); + + /// @} + /// Adds an edit operation to delete the composed prim at the given \p path /// from this namespace editor's stage. /// @@ -243,17 +277,15 @@ class UsdNamespaceEditor // edit of the composed stage object from being completed successfully. std::vector errors; - // The Sdf batch namespace edit that needs to be applied to each layer - // with specs. - SdfBatchNamespaceEdit edits; - + // The edit description of the primary edit. + _EditDescription editDescription; + // The list of layers that have specs that need to have the Sdf // namespace edit applied. SdfLayerHandleVector layersToEdit; - // The list of relocates edits that need to be made to layers in order - // to relocate a prim. - PcpLayerRelocatesEditBuilder::LayerRelocatesEdits relocatesEdits; + // Whether performing the edit will author new relocates. + bool willAuthorRelocates = false; // Layer edits that need to be performed to update connection and // relationship targets of other properties in order to keep them @@ -274,20 +306,16 @@ class UsdNamespaceEditor }; std::vector targetPathListOpEdits; + // Full set of namespace edits that need to be performed for all the + // dependent stages of this editor as a result of dependencies on the + // initial spec move edits. + PcpDependentNamespaceEdits dependentStageNamespaceEdits; + // List of errors encountered that would prevent connection and // relationship target edits from being performed in response to the // namespace edits. std::vector targetPathListOpErrors; - // Reparent edits may require overs to be created for the new parent if - // a layer doesn't have any specs for the parent yet. This specifies the - // path of the parent specs to create if need. - SdfPath createParentSpecIfNeededPath; - - // Some edits want to remove inert ancestor overs after a prim is - // removed from its parent spec in a layer. - bool removeInertAncestorOvers = false; - // Applies this processed edit, performing the individual edits // necessary to each layer that needs to be updated. bool Apply(); @@ -321,6 +349,10 @@ class UsdNamespaceEditor class _EditProcessor; UsdStageRefPtr _stage; + // Dependent stage order should be arbitrary but we want don't want + // duplicates which can cause unnecessary work. + using _StageSet = std::unordered_set; + _StageSet _dependentStages; EditOptions _editOptions; _EditDescription _editDescription; mutable std::optional<_ProcessedEdit> _processedEdit; diff --git a/pxr/usd/usd/stage.h b/pxr/usd/usd/stage.h index d087278058..4aff716918 100644 --- a/pxr/usd/usd/stage.h +++ b/pxr/usd/usd/stage.h @@ -2362,6 +2362,7 @@ class UsdStage : public TfRefBase, public TfWeakBase { friend class UsdAttributeQuery; friend class UsdEditTarget; friend class UsdInherits; + friend class UsdNamespaceEditor; friend class UsdObject; friend class UsdPrim; friend class UsdProperty; diff --git a/pxr/usd/usd/wrapNamespaceEditor.cpp b/pxr/usd/usd/wrapNamespaceEditor.cpp index b02a0b04fa..5a03c4f2eb 100644 --- a/pxr/usd/usd/wrapNamespaceEditor.cpp +++ b/pxr/usd/usd/wrapNamespaceEditor.cpp @@ -57,6 +57,10 @@ void wrapUsdNamespaceEditor() .def(init()) .def(init()) + .def("AddDependentStage", &This::AddDependentStage) + .def("RemoveDependentStage", &This::RemoveDependentStage) + .def("SetDependentStages", &This::SetDependentStages) + .def("DeletePrimAtPath", &This::DeletePrimAtPath) .def("MovePrimAtPath", &This::MovePrimAtPath)