diff --git a/lib/mayaUsd/nodes/proxyAccessor.cpp b/lib/mayaUsd/nodes/proxyAccessor.cpp index c0547f1912..19c237f08a 100644 --- a/lib/mayaUsd/nodes/proxyAccessor.cpp +++ b/lib/mayaUsd/nodes/proxyAccessor.cpp @@ -689,7 +689,8 @@ MStatus ProxyAccessor::stageChanged(const MObject& node, const UsdNotice::Object if (_accessorInputItems.size() > 0) { auto findInputItemFn = [this](const SdfPath& changedPath) -> Item* { for (Item& item : _accessorInputItems) { - if (item.path == changedPath || item.path.AppendProperty(item.property) == changedPath) { + if (item.path == changedPath + || item.path.AppendProperty(item.property) == changedPath) { return &item; } } diff --git a/lib/mayaUsd/ufe/UsdHierarchy.cpp b/lib/mayaUsd/ufe/UsdHierarchy.cpp index 3d15d7e26f..73aaa5123c 100644 --- a/lib/mayaUsd/ufe/UsdHierarchy.cpp +++ b/lib/mayaUsd/ufe/UsdHierarchy.cpp @@ -19,7 +19,7 @@ #include "private/Utils.h" #include -#include +#include #include #include @@ -269,13 +269,14 @@ Ufe::AppendedChild UsdHierarchy::appendChild(const Ufe::SceneItem::Ptr& child) std::string childName = uniqueChildName(fItem->prim(), child->path().back().string()); // Set up all paths to perform the reparent. - auto childPrim = usdChild->prim(); - auto stage = childPrim.GetStage(); - auto ufeSrcPath = usdChild->path(); - auto usdSrcPath = childPrim.GetPath(); - auto ufeDstPath = fItem->path() + childName; - auto usdDstPath = prim().GetPath().AppendChild(TfToken(childName)); - SdfLayerHandle layer = MayaUsdUtils::defPrimSpecLayer(childPrim); + auto childPrim = usdChild->prim(); + auto stage = childPrim.GetStage(); + auto ufeSrcPath = usdChild->path(); + auto usdSrcPath = childPrim.GetPath(); + auto ufeDstPath = fItem->path() + childName; + auto usdDstPath = prim().GetPath().AppendChild(TfToken(childName)); + auto primSpec = getDefiningPrimSpec(childPrim); + auto layer = primSpec ? primSpec->GetLayer() : SdfLayerHandle(); if (!layer) { std::string err = TfStringPrintf("No prim found at %s", usdSrcPath.GetString().c_str()); throw std::runtime_error(err.c_str()); diff --git a/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.cpp b/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.cpp index 5010e58ef9..a4d296a8e6 100644 --- a/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.cpp +++ b/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.cpp @@ -22,12 +22,12 @@ #include #include #include +#include #include #ifdef UFE_V2_FEATURES_AVAILABLE #include #include #endif -#include #include #include @@ -59,7 +59,9 @@ UsdUndoDuplicateCommand::UsdUndoDuplicateCommand(const UsdSceneItem::Ptr& srcIte auto newName = uniqueChildName(parentPrim, srcPrim.GetName()); _usdDstPath = parentPrim.GetPath().AppendChild(TfToken(newName)); - _srcLayer = MayaUsdUtils::getDefiningLayerAndPath(srcPrim).layer; + auto primSpec = getDefiningPrimSpec(srcPrim); + if (primSpec) + _srcLayer = primSpec->GetLayer(); } UsdUndoDuplicateCommand::~UsdUndoDuplicateCommand() { } @@ -101,20 +103,19 @@ void UsdUndoDuplicateCommand::execute() // otherwise SdfCopySepc will fail. SdfJustCreatePrimInLayer(_dstLayer, _usdDstPath.GetParentPath()); - // Retrieve the layers where there are opinion and order them from weak - // to strong. We will copy the weakest opinions first, so that they will - // get over-written by the stronger opinions. - using namespace MayaUsdUtils; - std::vector authLayerAndPaths = getAuthoredLayerAndPaths(prim); + // Retrieve the local layers around where the prim is defined and order them + // from weak to strong. That weak-to-strong order allows us to copy the weakest + // opinions first, so that they will get over-written by the stronger opinions. + SdfPrimSpecHandleVector authLayerAndPaths = getDefiningPrimStack(prim); std::reverse(authLayerAndPaths.begin(), authLayerAndPaths.end()); - MergePrimsOptions options; - options.verbosity = MergeVerbosity::None; + MayaUsdUtils::MergePrimsOptions options; + options.verbosity = MayaUsdUtils::MergeVerbosity::None; bool isFirst = true; - for (const LayerAndPath& layerAndPath : authLayerAndPaths) { - const auto layer = layerAndPath.layer; - const auto path = layerAndPath.path; + for (const SdfPrimSpecHandle& layerAndPath : authLayerAndPaths) { + const auto layer = layerAndPath->GetLayer(); + const auto path = layerAndPath->GetPath(); const bool result = isFirst ? SdfCopySpec(layer, path, _dstLayer, _usdDstPath) : mergePrims(stage, layer, path, stage, _dstLayer, _usdDstPath, options); diff --git a/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.h b/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.h index e09b051578..7a13760537 100644 --- a/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.h +++ b/lib/mayaUsd/ufe/UsdUndoDuplicateCommand.h @@ -32,6 +32,17 @@ namespace MAYAUSD_NS_DEF { namespace ufe { //! \brief UsdUndoDuplicateCommand +//! +//! \details The USD duplicate command copies all opinions related the the USD prim +//! that are in the local layer stack of where the prim is first defined into +//! a single target layer, flattened. +//! +//! This means that over opinions in the session layer and any layers in the +//! same local layer stack anchored at the root layer are duplicated. +//! +//! It also means that opinion found in references and payloads are *not* +//! copied, but the references and payloads arcs are, so their opinions +//! are still taken into account. #ifdef UFE_V4_FEATURES_AVAILABLE class MAYAUSD_CORE_PUBLIC UsdUndoDuplicateCommand : public Ufe::SceneItemResultUndoableCommand #else diff --git a/lib/mayaUsd/utils/layers.cpp b/lib/mayaUsd/utils/layers.cpp index eef064d3b6..0ba1953418 100644 --- a/lib/mayaUsd/utils/layers.cpp +++ b/lib/mayaUsd/utils/layers.cpp @@ -16,6 +16,7 @@ #include "layers.h" #include +#include #include @@ -116,44 +117,16 @@ void enforceMutedLayer(const PXR_NS::UsdPrim& prim, const char* command) } } -static SdfPrimSpecHandleVector _GetLocalPrimStack(const UsdPrim& prim) -{ - SdfPrimSpecHandleVector primSpecs; - - UsdStagePtr stage = prim.GetStage(); - if (!stage) - return primSpecs; - - // The goal is to avoid editing non-local layers. This issue is, - // for example, that a rename operation would fail when applied - // to a prim that references a show asset because the rename operation - // would be attempted on the reference and classes it inherits. - // - // Concrete example: - // - Create a test asset that inherits from one or more classes - // - Create a prim within a Maya Usd scene that references this asset - // - Attempt to rename the prim - // - Observe the failure due to Sdf policy - - for (const SdfLayerHandle& layer : stage->GetLayerStack()) { - const SdfPrimSpecHandle primSpec = layer->GetPrimAtPath(prim.GetPath()); - if (primSpec) - primSpecs.push_back(primSpec); - } - - return primSpecs; -} - void applyToAllPrimSpecs(const UsdPrim& prim, const PrimSpecFunc& func) { - const SdfPrimSpecHandleVector primStack = _GetLocalPrimStack(prim); + const SdfPrimSpecHandleVector primStack = getLocalPrimStack(prim); for (const SdfPrimSpecHandle& spec : primStack) func(prim, spec); } void applyToAllLayersWithOpinions(const UsdPrim& prim, PrimLayerFunc& func) { - const SdfPrimSpecHandleVector primStack = _GetLocalPrimStack(prim); + const SdfPrimSpecHandleVector primStack = getLocalPrimStack(prim); for (const SdfPrimSpecHandle& spec : primStack) { const auto layer = spec->GetLayer(); func(prim, layer); @@ -165,7 +138,7 @@ void applyToSomeLayersWithOpinions( const std::set& layers, PrimLayerFunc& func) { - const SdfPrimSpecHandleVector primStack = _GetLocalPrimStack(prim); + const SdfPrimSpecHandleVector primStack = getLocalPrimStack(prim); for (const SdfPrimSpecHandle& spec : primStack) { const auto layer = spec->GetLayer(); if (layers.count(layer) == 0) @@ -243,4 +216,165 @@ SdfLayerHandle getStrongerLayer( return getStrongerLayer(stage->GetRootLayer(), layer1, layer2); } +SdfPrimSpecHandleVector +getPrimStackForLayers(const UsdPrim& prim, const SdfLayerHandleVector& layers) +{ + SdfPrimSpecHandleVector primSpecs; + + for (const SdfLayerHandle& layer : layers) { + const SdfPrimSpecHandle primSpec = layer->GetPrimAtPath(prim.GetPath()); + if (primSpec) + primSpecs.push_back(primSpec); + } + + return primSpecs; +} + +SdfPrimSpecHandleVector getLocalPrimStack(const UsdPrim& prim) +{ + // The goal is to avoid editing non-local layers. This issue is, + // for example, that a rename operation would fail when applied + // to a prim that references a show asset because the rename operation + // would be attempted on the reference and classes it inherits. + // + // Concrete example: + // - Create a test asset that inherits from one or more classes + // - Create a prim within a Maya Usd scene that references this asset + // - Attempt to rename the prim + // - Observe the failure due to Sdf policy + + UsdStagePtr stage = prim.GetStage(); + if (!stage) + return {}; + + return getPrimStackForLayers(prim, stage->GetLayerStack()); +} + +static void addSubLayers(const SdfLayerHandle& layer, std::set& layers) +{ + if (!layer) + return; + + if (layers.count(layer) > 0) + return; + + layers.insert(layer); + for (const std::string layerPath : layer->GetSubLayerPaths()) + addSubLayers(SdfLayer::FindOrOpen(layerPath), layers); +} + +static bool hasSubLayerInSet(const SdfLayerHandle& layer, const std::set& layers) +{ + if (!layer) + return false; + + for (const std::string layerPath : layer->GetSubLayerPaths()) + if (layers.count(SdfLayer::FindOrOpen(layerPath)) > 0) + return true; + + return false; +} + +SdfPrimSpecHandleVector getDefiningPrimStack(const UsdPrim& prim) +{ + UsdStagePtr stage = prim.GetStage(); + if (!stage) + return {}; + + const SdfPrimSpecHandle defPrimSpec = getDefiningPrimSpec(prim); + if (!defPrimSpec) + return {}; + + // Simple case: the prim is defined in the local layer stack of the stage. + { + const SdfLayerHandle defLayer = defPrimSpec->GetLayer(); + const SdfPrimSpecHandleVector primSpecsInStageLayers = getLocalPrimStack(prim); + for (const SdfPrimSpecHandle& primSpec : primSpecsInStageLayers) + if (primSpec->GetLayer() == defLayer) + return primSpecsInStageLayers; + } + + // Complex case: the prim is defined within a reference or payload. + // + // We need to build the layer stack of that payload or reference. + // Note that it could be a reference inside a reference, or a payload + // in a reference, or any deeper such nesting. + // + // We build the defining prim stack by going outward from the defining + // prim spec. We keep other prim spec if their layer is a parent or child + // of the layer that defines the prim. (The code beow starts from all the + // prim specs and removes the ones that are not in the layer hierarchy + // above and below the defining layer.) + + // This keep tracks of layers we know are in the defining layer stack. + // We use this to identify other layer, for example identify a parent + // layer if one of its children in in this set. + std::set definingLayers; + addSubLayers(defPrimSpec->GetLayer(), definingLayers); + + SdfPrimSpecHandleVector primStack = prim.GetPrimStack(); + + const auto defPrimSpecPos = std::find(primStack.begin(), primStack.end(), defPrimSpec); + if (defPrimSpecPos == primStack.end()) + return {}; + + const size_t defPrimSpecIndex = defPrimSpecPos - primStack.begin(); + + // Remove the sub-layers that are not in the local stack of the defining layer. + for (size_t index = defPrimSpecIndex + 1; index < primStack.size(); index += 1) { + const SdfPrimSpecHandle primSpec = primStack[index]; + + // If the prim spec layer is a sub-layer of the defining layer, then we keep + // it and add its children to the defining layers set. + SdfLayerHandle layer = primSpec->GetLayer(); + if (definingLayers.count(layer) > 0) { + addSubLayers(layer, definingLayers); + continue; + } + + // Otherwise, we remove the prim spec from the defining prim stack and + // decrease the index, since we erase it from the vector and the loop + // will increase the index. + primStack.erase(primStack.begin() + index); + index -= 1; + } + + // Remove the parent layers that are not in the local stack of the defining layer. + for (size_t index = defPrimSpecIndex; index > 0;) { + index -= 1; + const SdfPrimSpecHandle primSpec = primStack[index]; + + // If the prim spec layer is a parent layer of the defining layer, then we keep + // it and add its children to the defining layers set. + SdfLayerHandle layer = primSpec->GetLayer(); + if (hasSubLayerInSet(layer, definingLayers)) { + addSubLayers(layer, definingLayers); + continue; + } + + // Otherwise, we remove the prim spec from the defining prim stack. + // We don't need to adjust the index since we are going backward. + primStack.erase(primStack.begin() + index); + } + + return primStack; +} + +SdfPrimSpecHandle getDefiningPrimSpec(const UsdPrim& prim) +{ + SdfPrimSpecHandleVector primSpecs = prim.GetPrimStack(); + for (SdfPrimSpecHandle primSpec : primSpecs) { + if (!primSpec) + continue; + + const SdfSpecifier spec = primSpec->GetSpecifier(); + if (spec != SdfSpecifierDef) + continue; + + return primSpec; + } + + return {}; +} + } // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/utils/layers.h b/lib/mayaUsd/utils/layers.h index cc39af522e..a7c9c48433 100644 --- a/lib/mayaUsd/utils/layers.h +++ b/lib/mayaUsd/utils/layers.h @@ -174,6 +174,36 @@ PXR_NS::SdfLayerHandle getStrongerLayer( const PXR_NS::SdfLayerHandle& layer2, bool compareSessionLayers = false); +//! Return all layers in the given layers where there are opinions about the prim. +MAYAUSD_CORE_PUBLIC +PXR_NS::SdfPrimSpecHandleVector +getPrimStackForLayers(const PXR_NS::UsdPrim& prim, const PXR_NS::SdfLayerHandleVector& layers); + +//! Return all local layers in the stage of the prim where there are opinions about the prim. +// +// The goal is to avoid editing non-local layers. This issue is, +// for example, that a rename operation would fail when applied +// to a prim that references a show asset because the rename operation +// would be attempted on the reference and classes it inherits. +// +// Concrete example: +// - Create a test asset that inherits from one or more classes +// - Create a prim within a Maya Usd scene that references this asset +// - Attempt to rename the prim +// - Observe the failure due to Sdf policy +MAYAUSD_CORE_PUBLIC +PXR_NS::SdfPrimSpecHandleVector getLocalPrimStack(const PXR_NS::UsdPrim& prim); + +//! Return all layers and related paths in the layer stack where the prim is first defined. +// When the prim is in a reference, those paths will not be equal to the path of the input prim. +MAYAUSD_CORE_PUBLIC +PXR_NS::SdfPrimSpecHandleVector getDefiningPrimStack(const PXR_NS::UsdPrim& prim); + +//! Return the layer and path where the prim is defined and the path relative to that layer. +// When the prim is in a reference, that path will not be equal to the path of the input prim. +MAYAUSD_CORE_PUBLIC +PXR_NS::SdfPrimSpecHandle getDefiningPrimSpec(const PXR_NS::UsdPrim& prim); + } // namespace MAYAUSD_NS_DEF #endif diff --git a/lib/usd/utils/util.cpp b/lib/usd/utils/util.cpp index 2184222265..3706dd131c 100644 --- a/lib/usd/utils/util.cpp +++ b/lib/usd/utils/util.cpp @@ -186,105 +186,6 @@ void replacePropertyPath(const UsdPrim& oldPrim, const SdfPath& newPath, UsdProp namespace MayaUsdUtils { -SdfLayerHandle defPrimSpecLayer(const UsdPrim& prim) -{ - // Iterate over the layer stack, starting at the highest-priority layer. - // The source layer is the one in which there exists a def primSpec, not - // an over. - - auto layerStack = prim.GetStage()->GetLayerStack(); - for (auto layer : layerStack) { - SdfPrimSpecHandle primSpec = layer->GetPrimAtPath(prim.GetPath()); - if (!primSpec) - continue; - - const SdfSpecifier spec = primSpec->GetSpecifier(); - if (spec != SdfSpecifierDef) - continue; - - return layer; - } - return {}; -} - -static void addArcLayers(const UsdPrimCompositionQueryArc& arc, std::set& layers) -{ - PcpNodeRef pcpNode = arc.GetTargetNode(); - const PcpLayerStackRefPtr& layerStack = pcpNode.GetLayerStack(); - for (const SdfLayerRefPtr& layer : layerStack->GetLayers()) { - layers.insert(layer); - } -} - -std::vector getAuthoredLayerAndPaths(const UsdPrim& prim, bool includePayloads) -{ - // The logic in thuis function is tailored for its uses, in particular - // in the duplicate command (UsdUndoDuplicateCommand). It is a poor-man, - // rough heuristic for a multi-layers workflow, which might be better - // fleshed out in the future once we start to tackle multi-layers operations. - // - // For now, it makes the duplicate command work better in the presence of - // references and payload, and in particular the difference between the - // defining opinion containing a payload arc vs the defining opinion being - // *in* a payload arc or reference. No claim that we are doing the right thing - // in all possible combinations of opinions, payloads and refs, but this - // handle the simple cases. - - // If we don't want to include data from within the payloads, - // so the record the layers that are under the payloads. - std::set payloadLayers; - if (!includePayloads) { - UsdPrimCompositionQuery query(prim); - for (const UsdPrimCompositionQueryArc& arc : query.GetCompositionArcs()) { - if (arc.GetArcType() == PcpArcTypePayload) { - addArcLayers(arc, payloadLayers); - } else { - SdfLayerHandle intro = arc.GetIntroducingLayer(); - const bool underPayload = payloadLayers.count(intro); - if (underPayload) { - addArcLayers(arc, payloadLayers); - } - } - } - } - - // The defining layer must always be kept. - SdfLayerHandle defLayer = MayaUsdUtils::getDefiningLayerAndPath(prim).layer; - - std::vector layerAndPaths; - - for (SdfPrimSpecHandle primSpec : prim.GetPrimStack()) { - if (!primSpec) - continue; - - SdfLayerHandle layer = primSpec->GetLayer(); - if (layer != defLayer) - if (payloadLayers.count(layer) > 0) - continue; - - layerAndPaths.emplace_back(layer, primSpec->GetPath()); - } - - return layerAndPaths; -} - -LayerAndPath getDefiningLayerAndPath(const UsdPrim& prim) -{ - SdfPrimSpecHandleVector primSpecs = prim.GetPrimStack(); - for (SdfPrimSpecHandle primSpec : primSpecs) { - if (!primSpec) - continue; - - const SdfSpecifier spec = primSpec->GetSpecifier(); - if (spec != SdfSpecifierDef) - continue; - - return LayerAndPath(primSpec->GetLayer(), primSpec->GetPath()); - } - - return LayerAndPath(); -} - SdfPrimSpecHandle getPrimSpecAtEditTarget(const UsdPrim& prim) { auto stage = prim.GetStage(); diff --git a/lib/usd/utils/util.h b/lib/usd/utils/util.h index b498b6de00..03bd4982f3 100644 --- a/lib/usd/utils/util.h +++ b/lib/usd/utils/util.h @@ -24,37 +24,6 @@ PXR_NAMESPACE_USING_DIRECTIVE namespace MayaUsdUtils { -//! Return the highest-priority layer where the prim has a def primSpec. -MAYA_USD_UTILS_PUBLIC -SdfLayerHandle defPrimSpecLayer(const UsdPrim& prim); - -//! Hold a layer a path relative to that layer. -struct LayerAndPath -{ - SdfLayerHandle layer; - SdfPath path; - - LayerAndPath() = default; - LayerAndPath(const LayerAndPath&) = default; - LayerAndPath(const SdfLayerHandle& a_layer, const SdfPath& a_path) - : layer(a_layer) - , path(a_path) - { - } -}; - -//! Return all layers where the prim has opinions and the paths relative to that layer. -// The strongest layer is the first in the list. -// When the prim is in a reference, that path will not be equal to the path of the input prim. -MAYA_USD_UTILS_PUBLIC -std::vector -getAuthoredLayerAndPaths(const UsdPrim& prim, bool includePayloads = false); - -//! Return the layer where the prim is defined and the path relative to that layer. -// When the prim is in a reference, that path will not be equal to the path of the input prim. -MAYA_USD_UTILS_PUBLIC -LayerAndPath getDefiningLayerAndPath(const UsdPrim& prim); - //! Return a PrimSpec for the argument prim in the layer containing the stage's current edit target. MAYA_USD_UTILS_PUBLIC SdfPrimSpecHandle getPrimSpecAtEditTarget(const UsdPrim& prim); diff --git a/test/lib/ufe/testDuplicateCmd.py b/test/lib/ufe/testDuplicateCmd.py index 2f8f239754..97d6e3d82a 100644 --- a/test/lib/ufe/testDuplicateCmd.py +++ b/test/lib/ufe/testDuplicateCmd.py @@ -597,7 +597,33 @@ def testReferencedPrim(self): self.assertIsNotNone(dupItem) dupTrf = ufe.Transform3d.transform3d(dupItem) self.assertEqual(ufe.Vector3d(2., 0., -2.), dupTrf.translation()) - + + def testPrimWithReference(self): + ''' + Test duplicating a prim that contains a reference. + The content of the reference should not become part of the destination prim. + The destination should still simply contain the reference arc. + ''' + + cmds.file(new=True, force=True) + cubeRefFile = testUtils.getTestScene("cubeRef", "cube-root.usda") + cubeRefDagPath, cubeRefStage = mayaUtils.createProxyFromFile(cubeRefFile) + cubeUfePathString = ','.join([cubeRefDagPath, "/RootPrim/PrimWithRef"]) + + cmds.duplicate(cubeUfePathString) + + # Verify that the duplicated item still references the cube. + cubeRefByDupItem = ufeUtils.createUfeSceneItem(cubeRefDagPath, '/RootPrim/PrimWithRef1/CubeMesh') + self.assertIsNotNone(cubeRefByDupItem) + dupTrf = ufe.Transform3d.transform3d(cubeRefByDupItem) + self.assertEqual(ufe.Vector3d(2., 0., -2.), dupTrf.translation()) + + # Make sure the geometry did not get flattened into the duplicate + rootLayer = cubeRefStage.GetRootLayer() + rootLayerText = rootLayer.ExportToString() + self.assertNotIn('"Geom"', rootLayerText) + self.assertNotIn('Mesh', rootLayerText) + def testPrimWithPayload(self): ''' Test duplicating a prim that has a payload.