From 52800ba5adaad74a2ea34e6fb65e9696850a098c Mon Sep 17 00:00:00 2001 From: pixar-oss Date: Wed, 28 Sep 2022 10:10:34 -0700 Subject: [PATCH] Adding UsdResolveTarget which can be used to perform attribute value resolution using only a subrange of a prim's prim stack. A UsdResolveTarget holds a prim index and iterator ranges determining the start node and layer and stop node and layer that a Usd_Resolver should use when iterating over a prim index for value resolution. UsdResolveTargets can only be consumed by UsdAttributeQuery(s) during construction. Proving an attribute query will limit value resolution for all its methods to the prim stack subrange of the resolve target. UsdResolverTargets can only be created by the following methods UsdPrimCompositionQueryArc::MakeResolveTargetUpTo UsdPrimCompositionQueryArc::MakeResolveTargetStrongerThan UsdPrim::MakeResolveTargetUpToEditTarget UsdPrim::MakeResolveTargetStrongerThanEditTarget The query arc methods produce resolve targets that limit value resolution to either "up to" or "stronger than" that particular composition arc. The UsdPrim methods produce resolve targets that limit value resolution to "up to" or "stronger than" the spec that would be authored by a given UsdEditTarget for the prim. Fixes #1483 (Internal change: 2250352) --- pxr/usd/usd/CMakeLists.txt | 35 + pxr/usd/usd/attributeQuery.cpp | 61 +- pxr/usd/usd/attributeQuery.h | 26 +- pxr/usd/usd/module.cpp | 1 + pxr/usd/usd/prim.cpp | 82 ++ pxr/usd/usd/prim.h | 31 + pxr/usd/usd/primCompositionQuery.cpp | 37 + pxr/usd/usd/primCompositionQuery.h | 27 + pxr/usd/usd/resolveTarget.cpp | 113 +++ pxr/usd/usd/resolveTarget.h | 147 ++++ pxr/usd/usd/resolver.cpp | 80 +- pxr/usd/usd/resolver.h | 19 +- pxr/usd/usd/stage.cpp | 92 +- pxr/usd/usd/stage.h | 53 +- pxr/usd/usd/testenv/testUsdResolveTarget.cpp | 789 ++++++++++++++++++ .../resolveTarget/ref.usda | 20 + .../resolveTarget/ref_sub1.usda | 26 + .../resolveTarget/ref_sub2.usda | 18 + .../resolveTarget/root.usda | 30 + .../resolveTarget/sub1.usda | 23 + .../resolveTarget/sub2.usda | 21 + pxr/usd/usd/testenv/testUsdResolveTargetPy.py | 297 +++++++ pxr/usd/usd/wrapAttributeQuery.cpp | 3 + pxr/usd/usd/wrapPrim.cpp | 6 + pxr/usd/usd/wrapPrimCompositionQuery.cpp | 29 +- pxr/usd/usd/wrapResolveTarget.cpp | 45 + 26 files changed, 2051 insertions(+), 60 deletions(-) create mode 100644 pxr/usd/usd/resolveTarget.cpp create mode 100644 pxr/usd/usd/resolveTarget.h create mode 100644 pxr/usd/usd/testenv/testUsdResolveTarget.cpp create mode 100644 pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref.usda create mode 100644 pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub1.usda create mode 100644 pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub2.usda create mode 100644 pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/root.usda create mode 100644 pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub1.usda create mode 100644 pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub2.usda create mode 100644 pxr/usd/usd/testenv/testUsdResolveTargetPy.py create mode 100644 pxr/usd/usd/wrapResolveTarget.cpp diff --git a/pxr/usd/usd/CMakeLists.txt b/pxr/usd/usd/CMakeLists.txt index 5676adc256..2fd23d0dca 100644 --- a/pxr/usd/usd/CMakeLists.txt +++ b/pxr/usd/usd/CMakeLists.txt @@ -52,6 +52,7 @@ pxr_library(usd references relationship resolveInfo + resolveTarget resolver schemaBase schemaRegistry @@ -132,6 +133,7 @@ pxr_library(usd wrapReferences.cpp wrapRelationship.cpp wrapResolveInfo.cpp + wrapResolveTarget.cpp wrapSchemaBase.cpp wrapSchemaRegistry.cpp wrapSpecializes.cpp @@ -234,6 +236,7 @@ pxr_test_scripts( testenv/testUsdPrims.py testenv/testUsdReferences.py testenv/testUsdRelationships.py + testenv/testUsdResolveTargetPy.py testenv/testUsdSchemaBasePy.py testenv/testUsdSchemaRegistry.py testenv/testUsdSpecializes.py @@ -407,6 +410,17 @@ pxr_build_test(testUsdSchemaBase testenv/testUsdSchemaBase.cpp ) +pxr_build_test(testUsdResolveTarget + LIBRARIES + pcp + sdf + tf + usd + vt + CPPFILES + testenv/testUsdResolveTarget.cpp +) + pxr_build_test(testUsdTimeValueAuthoringCpp LIBRARIES arch @@ -538,6 +552,16 @@ pxr_install_test_dir( DEST testUsdResolverChanged ) +pxr_install_test_dir( + SRC testenv/testUsdResolveTarget.testenv + DEST testUsdResolveTarget +) + +pxr_install_test_dir( + SRC testenv/testUsdResolveTarget.testenv + DEST testUsdResolveTargetPy +) + pxr_install_test_dir( SRC testenv/testUsdNotices.testenv DEST testUsdNotices @@ -1002,6 +1026,17 @@ pxr_register_test(testUsdModel EXPECTED_RETURN_CODE 0 ) +pxr_register_test(testUsdResolveTarget + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdResolveTarget" + EXPECTED_RETURN_CODE 0 +) + +pxr_register_test(testUsdResolveTargetPy + PYTHON + COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdResolveTargetPy" + EXPECTED_RETURN_CODE 0 +) + pxr_register_test(testUsdSpecializes PYTHON COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdSpecializes" diff --git a/pxr/usd/usd/attributeQuery.cpp b/pxr/usd/usd/attributeQuery.cpp index 9acf606823..d63371bd56 100644 --- a/pxr/usd/usd/attributeQuery.cpp +++ b/pxr/usd/usd/attributeQuery.cpp @@ -36,9 +36,10 @@ PXR_NAMESPACE_OPEN_SCOPE UsdAttributeQuery::UsdAttributeQuery( - const UsdAttribute& attr) + const UsdAttribute& attr) : + _attr(attr) { - _Initialize(attr); + _Initialize(); } UsdAttributeQuery::UsdAttributeQuery( @@ -47,6 +48,14 @@ UsdAttributeQuery::UsdAttributeQuery( { } +UsdAttributeQuery::UsdAttributeQuery( + const UsdAttribute &attr, + const UsdResolveTarget &resolveTarget) : + _attr(attr) +{ + _Initialize(resolveTarget); +} + std::vector UsdAttributeQuery::CreateQueries( const UsdPrim& prim, const TfTokenVector& attrNames) @@ -65,16 +74,45 @@ UsdAttributeQuery::UsdAttributeQuery() } void -UsdAttributeQuery::_Initialize(const UsdAttribute& attr) +UsdAttributeQuery::_Initialize() { TRACE_FUNCTION(); - if (attr) { - const UsdStage* stage = attr._GetStage(); - stage->_GetResolveInfo(attr, &_resolveInfo); + if (_attr) { + const UsdStage* stage = _attr._GetStage(); + stage->_GetResolveInfo(_attr, &_resolveInfo); } +} + +void +UsdAttributeQuery::_Initialize( + const UsdResolveTarget &resolveTarget) +{ + TRACE_FUNCTION(); + + if (resolveTarget.IsNull()) { + _Initialize(); + return; + } + + if (!_attr) { + return; + } + + // Validate that the resolve target is for this attribute's prim path. + if (_attr.GetPrimPath() != resolveTarget.GetPrimIndex()->GetPath()) { + TF_CODING_ERROR("Invalid resolve target for attribute '%s'. The " + "given resolve target is only valid for attributes on the prim " + "'%s'.", + _attr.GetPrimPath().GetText(), + resolveTarget.GetPrimIndex()->GetPath().GetText()); + return; + } + + const UsdStage* stage = _attr._GetStage(); + stage->_GetResolveInfoWithResolveTarget(_attr, resolveTarget, &_resolveInfo); - _attr = attr; + _resolveTarget = resolveTarget; } const UsdAttribute& @@ -98,8 +136,13 @@ UsdAttributeQuery::_Get(T* value, UsdTimeCode time) const static const UsdTimeCode defaultTime = UsdTimeCode::Default(); UsdResolveInfo defaultResolveInfo; - _attr._GetStage()->_GetResolveInfo( - _attr, &defaultResolveInfo, &defaultTime); + if (_resolveTarget.IsNull()) { + _attr._GetStage()->_GetResolveInfo( + _attr, &defaultResolveInfo, &defaultTime); + } else { + _attr._GetStage()->_GetResolveInfoWithResolveTarget( + _attr, _resolveTarget, &defaultResolveInfo, &defaultTime); + } return _attr._GetStage()->_GetValueFromResolveInfo( defaultResolveInfo, defaultTime, _attr, value); } diff --git a/pxr/usd/usd/attributeQuery.h b/pxr/usd/usd/attributeQuery.h index 1f3eae1c4f..021b3e55c3 100644 --- a/pxr/usd/usd/attributeQuery.h +++ b/pxr/usd/usd/attributeQuery.h @@ -30,6 +30,7 @@ #include "pxr/usd/usd/common.h" #include "pxr/usd/usd/prim.h" #include "pxr/usd/usd/resolveInfo.h" +#include "pxr/usd/usd/resolveTarget.h" #include "pxr/usd/usd/timeCode.h" #include "pxr/base/tf/token.h" @@ -52,7 +53,16 @@ PXR_NAMESPACE_OPEN_SCOPE /// the attribute \em is affected by Value Clips, the performance gain will /// just be less. /// -/// \section Thread safety +/// \section Resolve_targets Resolve targets +/// An attribute query can also be constructed for an attribute along with a +/// UsdResolveTarget. A resolve target allows value resolution to consider only +/// a subrange of the prim stack instead of the entirety of it. All of the methods +/// of an attribute query created with a resolve target will perform value +/// resolution within that resolve target. This can be useful for finding the +/// value of an attribute resolved up to a particular layer or for determining +/// if a value authored on layer would be overridden by a stronger opinion. +/// +/// \section Thread_safety Thread safety /// This object provides the basic thread-safety guarantee. Multiple threads /// may call the value accessor functions simultaneously. /// @@ -79,6 +89,15 @@ class UsdAttributeQuery USD_API UsdAttributeQuery(const UsdPrim& prim, const TfToken& attrName); + /// Construct a new query for the attribute \p attr with the given + /// resolve target \p resolveTarget. + /// + /// Note that a UsdResolveTarget is associated with a particular prim so + /// only resolve targets for the attribute's owning prim are allowed. + USD_API + UsdAttributeQuery(const UsdAttribute &attr, + const UsdResolveTarget &resolveTarget); + /// Construct new queries for the attributes named in \p attrNames under /// the prim \p prim. The objects in the returned vector will line up /// 1-to-1 with \p attrNames. @@ -242,7 +261,9 @@ class UsdAttributeQuery /// @} private: - void _Initialize(const UsdAttribute& attr); + void _Initialize(); + + void _Initialize(const UsdResolveTarget &resolveTarget); template USD_API @@ -251,6 +272,7 @@ class UsdAttributeQuery private: UsdAttribute _attr; UsdResolveInfo _resolveInfo; + UsdResolveTarget _resolveTarget; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/usd/usd/module.cpp b/pxr/usd/usd/module.cpp index 8c98f83d1d..642c83317f 100644 --- a/pxr/usd/usd/module.cpp +++ b/pxr/usd/usd/module.cpp @@ -50,6 +50,7 @@ TF_WRAP_MODULE TF_WRAP(UsdPrimFlags); TF_WRAP(UsdPrimTypeInfo); TF_WRAP(UsdReferences); + TF_WRAP(UsdResolveTarget); TF_WRAP(UsdSchemaRegistry); TF_WRAP(UsdSpecializes); TF_WRAP(UsdPrimRange); diff --git a/pxr/usd/usd/prim.cpp b/pxr/usd/usd/prim.cpp index 435fa1dce0..912e8ffd93 100644 --- a/pxr/usd/usd/prim.cpp +++ b/pxr/usd/usd/prim.cpp @@ -25,6 +25,7 @@ #include "pxr/usd/usd/prim.h" #include "pxr/usd/usd/apiSchemaBase.h" +#include "pxr/usd/usd/editTarget.h" #include "pxr/usd/usd/inherits.h" #include "pxr/usd/usd/instanceCache.h" #include "pxr/usd/usd/payloads.h" @@ -32,6 +33,7 @@ #include "pxr/usd/usd/relationship.h" #include "pxr/usd/usd/references.h" #include "pxr/usd/usd/resolver.h" +#include "pxr/usd/usd/resolveTarget.h" #include "pxr/usd/usd/schemaBase.h" #include "pxr/usd/usd/schemaRegistry.h" #include "pxr/usd/usd/specializes.h" @@ -1446,6 +1448,86 @@ UsdPrim::ComputeExpandedPrimIndex() const return outputs.primIndex; } +static PcpNodeRef +_FindStrongestNodeMatchingEditTarget( + const PcpPrimIndex& index, const UsdEditTarget &editTarget) +{ + // Use the edit target to map the prim's path to the path we expect to find + // a node for. + const SdfPath &rootPath = index.GetRootNode().GetPath(); + const SdfPath mappedPath = editTarget.MapToSpecPath(rootPath); + + if (mappedPath.IsEmpty()) { + return PcpNodeRef(); + } + + // We're looking for the first (strongest) node that would be affected by + // an edit to the prim using the edit target which means we are looking for + // the following criteria to be met: + // 1. The node's path matches the prim path mapped through the edit target. + // 2. The edit target's layer is in the node's layer stack. + for (const PcpNodeRef &node : index.GetNodeRange()) { + if (node.GetPath() != mappedPath) { + continue; + } + + if (node.GetLayerStack()->HasLayer(editTarget.GetLayer())) { + return node; + } + } + + return PcpNodeRef(); +} + +UsdResolveTarget +UsdPrim::_MakeResolveTargetFromEditTarget( + const UsdEditTarget &editTarget, + bool makeAsStrongerThan) const +{ + // Need the expanded prim index to find nodes and layers that may have been + // culled out in the cached prim index. + PcpPrimIndex expandedPrimIndex = ComputeExpandedPrimIndex(); + if (!expandedPrimIndex.IsValid()) { + return UsdResolveTarget(); + } + + const PcpNodeRef node = _FindStrongestNodeMatchingEditTarget( + expandedPrimIndex, editTarget); + if (!node) { + return UsdResolveTarget(); + } + + // The resolve target needs to hold on to the expanded prim index. + std::shared_ptr resolveIndex = + std::make_shared(std::move(expandedPrimIndex)); + + if (makeAsStrongerThan) { + // Return a resolve target starting at the root node and stopping at the + // edit node and layer. + return UsdResolveTarget(resolveIndex, + node.GetRootNode(), nullptr, node, editTarget.GetLayer()); + } else { + // Return a resolve target starting at the edit node and layer. + return UsdResolveTarget(resolveIndex, node, editTarget.GetLayer()); + } +} + +UsdResolveTarget +UsdPrim::MakeResolveTargetUpToEditTarget( + const UsdEditTarget &editTarget) const +{ + return _MakeResolveTargetFromEditTarget( + editTarget, /* makeAsStrongerThan = */ false); +} + +UsdResolveTarget +UsdPrim::MakeResolveTargetStrongerThanEditTarget( + const UsdEditTarget &editTarget) const +{ + return _MakeResolveTargetFromEditTarget( + editTarget, /* makeAsStrongerThan = */ true); +} + UsdPrim UsdPrim::GetPrimAtPath(const SdfPath& path) const{ const SdfPath absolutePath = path.MakeAbsolutePath(GetPath()); diff --git a/pxr/usd/usd/prim.h b/pxr/usd/usd/prim.h index 9920155464..4ae961d57a 100644 --- a/pxr/usd/usd/prim.h +++ b/pxr/usd/usd/prim.h @@ -57,9 +57,11 @@ class UsdPrimRange; class Usd_PrimData; class UsdAttribute; +class UsdEditTarget; class UsdRelationship; class UsdPayloads; class UsdReferences; +class UsdResolveTarget; class UsdSchemaBase; class UsdAPISchemaBase; class UsdInherits; @@ -1671,6 +1673,28 @@ class UsdPrim : public UsdObject USD_API PcpPrimIndex ComputeExpandedPrimIndex() const; + /// Creates and returns a resolve target that, when passed to a + /// UsdAttributeQuery for one of this prim's attributes, causes value + /// resolution to only consider weaker specs up to and including the spec + /// that would be authored for this prim when using the given \p editTarget. + /// + /// If the edit target would not affect any specs that could contribute to + /// this prim, a null resolve target is returned. + USD_API + UsdResolveTarget MakeResolveTargetUpToEditTarget( + const UsdEditTarget &editTarget) const; + + /// Creates and returns a resolve target that, when passed to a + /// UsdAttributeQuery for one of this prim's attributes, causes value + /// resolution to only consider specs that are stronger than the spec + /// that would be authored for this prim when using the given \p editTarget. + /// + /// If the edit target would not affect any specs that could contribute to + /// this prim, a null resolve target is returned. + USD_API + UsdResolveTarget MakeResolveTargetStrongerThanEditTarget( + const UsdEditTarget &editTarget) const; + /// @} private: @@ -1742,6 +1766,13 @@ class UsdPrim : public UsdObject // for testing and debugging purposes. const PcpPrimIndex &_GetSourcePrimIndex() const { return _Prim()->GetSourcePrimIndex(); } + + // Helper function for MakeResolveTargetUpToEditTarget and + // MakeResolveTargetStrongerThanEditTarget. + UsdResolveTarget + _MakeResolveTargetFromEditTarget( + const UsdEditTarget &editTarget, + bool makeAsStrongerThan) const; }; #ifdef doxygen diff --git a/pxr/usd/usd/primCompositionQuery.cpp b/pxr/usd/usd/primCompositionQuery.cpp index 0a8d784c12..9a3391c4b8 100644 --- a/pxr/usd/usd/primCompositionQuery.cpp +++ b/pxr/usd/usd/primCompositionQuery.cpp @@ -23,6 +23,7 @@ // #include "pxr/pxr.h" #include "pxr/usd/usd/primCompositionQuery.h" +#include "pxr/usd/usd/resolveTarget.h" #include "pxr/usd/usd/stage.h" #include "pxr/usd/pcp/layerStack.h" @@ -134,6 +135,42 @@ UsdPrimCompositionQueryArc::GetTargetPrimPath() const return _node.GetPath(); } +UsdResolveTarget +UsdPrimCompositionQueryArc::MakeResolveTargetUpTo( + const SdfLayerHandle &subLayer) const +{ + if (subLayer) { + if (_node.GetLayerStack()->HasLayer(subLayer)) { + return UsdResolveTarget(_primIndex, _node, subLayer); + } else { + TF_CODING_ERROR("Layer '%s' is not a layer in the layer stack of " + "the node site '%s'", + subLayer->GetIdentifier().c_str(), + TfStringify(_node.GetSite()).c_str()); + } + } + return UsdResolveTarget(_primIndex, _node, nullptr); +} + +UsdResolveTarget +UsdPrimCompositionQueryArc::MakeResolveTargetStrongerThan( + const SdfLayerHandle &subLayer) const +{ + const PcpNodeRef rootNode = _node.GetRootNode(); + if (subLayer) { + if (_node.GetLayerStack()->HasLayer(subLayer)) { + return UsdResolveTarget( + _primIndex, rootNode, nullptr, _node, subLayer); + } else { + TF_CODING_ERROR("Layer '%s' is not a layer in the layer stack of " + "the node site '%s'", + subLayer->GetIdentifier().c_str(), + TfStringify(_node.GetSite()).c_str()); + } + } + return UsdResolveTarget(_primIndex, rootNode, nullptr, _node, nullptr); +} + SdfLayerHandle UsdPrimCompositionQueryArc::GetIntroducingLayer() const { diff --git a/pxr/usd/usd/primCompositionQuery.h b/pxr/usd/usd/primCompositionQuery.h index b0f42b0df7..240e388ddb 100644 --- a/pxr/usd/usd/primCompositionQuery.h +++ b/pxr/usd/usd/primCompositionQuery.h @@ -100,6 +100,33 @@ class UsdPrimCompositionQueryArc USD_API SdfPath GetTargetPrimPath() const; + /// Creates and returns a resolve target that, when passed to a + /// UsdAttributeQuery for one of this prim's attributes, causes value + /// resolution to only consider node sites weaker than this arc, up to and + /// and including this arc's site itself. + /// + /// If \p subLayer is provided, it must be a layer in this arc's layer stack + /// and it will further limit value resolution to only the weaker layers up + /// to and including \p subLayer within this layer stack. (This is only with + /// respect to this arc; all layers will still be considered in the arcs + /// weaker than this arc). + USD_API + UsdResolveTarget MakeResolveTargetUpTo( + const SdfLayerHandle &subLayer = nullptr) const; + + /// Creates and returns a resolve target that, when passed to a + /// UsdAttributeQuery for one of this prim's attributes, causes value + /// resolution to only consider node sites stronger than this arc, not + /// including this arc itself (unless \p subLayer is provided). + /// + /// If \p subLayer is provided, it must be a layer in this arc's layer stack + /// and it will cause value resolution to additionally consider layers in + /// this arc but only if they are stronger than subLayer within this arc's + /// layer stack. + USD_API + UsdResolveTarget MakeResolveTargetStrongerThan( + const SdfLayerHandle &subLayer = nullptr) const; + /// @} /// \name Arc Editing diff --git a/pxr/usd/usd/resolveTarget.cpp b/pxr/usd/usd/resolveTarget.cpp new file mode 100644 index 0000000000..d2e662f2f0 --- /dev/null +++ b/pxr/usd/usd/resolveTarget.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2022 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +#include "pxr/pxr.h" +#include "pxr/usd/usd/resolveTarget.h" + +#include "pxr/usd/pcp/layerStack.h" +#include "pxr/usd/pcp/layerStackIdentifier.h" + +PXR_NAMESPACE_OPEN_SCOPE + +static SdfLayerRefPtrVector::const_iterator +_GetLayerIteratorInNode(const PcpNodeRef &node, const SdfLayerHandle &layer) +{ + // Null layer means we want the root layer of the node's layer stack. + const SdfLayerRefPtrVector& layers = node.GetLayerStack()->GetLayers(); + if (!layer) { + return layers.begin(); + } + + SdfLayerRefPtrVector::const_iterator layerIt = layers.begin(); + for (; layerIt != layers.end(); ++layerIt) { + if (*layerIt == layer) { + return layerIt; + } + } + + // We expect the call sites that can construct resolve targets to only + // provide layers that are in the node's layer stack. + TF_CODING_ERROR("Layer not present in node"); + return layers.begin(); +} + +UsdResolveTarget::UsdResolveTarget( + const std::shared_ptr &index, + const PcpNodeRef &node, + const SdfLayerHandle &layer) : + _expandedPrimIndex(index), + _nodeRange(index->GetNodeRange()) +{ + // Always stop at the end of the prim index graph since no stop node is + // provided. + _stopNodeIt = _nodeRange.second; + + _startNodeIt = index->GetNodeIteratorAtNode(node); + if (_startNodeIt != _nodeRange.second) { + _startLayerIt = _GetLayerIteratorInNode(*_startNodeIt, layer); + } +} + +UsdResolveTarget::UsdResolveTarget( + const std::shared_ptr &index, + const PcpNodeRef &node, + const SdfLayerHandle &layer, + const PcpNodeRef &stopNode, + const SdfLayerHandle &stopLayer) : + _expandedPrimIndex(index), + _nodeRange(index->GetNodeRange()) +{ + _stopNodeIt = stopNode ? + index->GetNodeIteratorAtNode(stopNode) : _nodeRange.second; + if (_stopNodeIt != _nodeRange.second) { + _stopLayerIt = _GetLayerIteratorInNode(*_stopNodeIt, stopLayer); + } + + _startNodeIt = index->GetNodeIteratorAtNode(node); + if (_startNodeIt != _nodeRange.second) { + _startLayerIt = _GetLayerIteratorInNode(*_startNodeIt, layer); + } +} + +PcpNodeRef +UsdResolveTarget::GetStartNode() const { + return _startNodeIt != _nodeRange.second ? *_startNodeIt : PcpNodeRef(); +} + +PcpNodeRef +UsdResolveTarget::GetStopNode() const { + return _stopNodeIt != _nodeRange.second ? *_stopNodeIt : PcpNodeRef(); +} + +SdfLayerHandle +UsdResolveTarget::GetStartLayer() const { + return _startNodeIt != _nodeRange.second ? *_startLayerIt : nullptr; +} + +SdfLayerHandle +UsdResolveTarget::GetStopLayer() const { + return _stopNodeIt != _nodeRange.second ? *_stopLayerIt : nullptr; +} + +PXR_NAMESPACE_CLOSE_SCOPE + diff --git a/pxr/usd/usd/resolveTarget.h b/pxr/usd/usd/resolveTarget.h new file mode 100644 index 0000000000..2208edbb70 --- /dev/null +++ b/pxr/usd/usd/resolveTarget.h @@ -0,0 +1,147 @@ +// +// Copyright 2022 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +#ifndef PXR_USD_USD_RESOLVE_TARGET_H +#define PXR_USD_USD_RESOLVE_TARGET_H + +#include "pxr/pxr.h" +#include "pxr/usd/usd/api.h" +#include "pxr/usd/pcp/node.h" +#include "pxr/usd/pcp/primIndex.h" +#include "pxr/usd/sdf/declareHandles.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +SDF_DECLARE_HANDLES(SdfLayer); + +/// \class UsdResolveTarget +/// +/// Defines a subrange of nodes and layers within a prim's prim index to +/// consider when performing value resolution for the prim's attributes. +/// A resolve target can then be passed to UsdAttributeQuery during its +/// construction to have all of the queries made by the UsdAttributeQuery use +/// the resolve target's subrange for their value resolution. +/// +/// Resolve targets can be created via methods on UsdPrimCompositionQueryArc to +/// to limit value resolution to a subrange of the prim's composed specs that +/// are \ref UsdPrimCompositionQueryArc::MakeResolveTargetUpTo "no stronger that arc", +/// or a subrange of specs that is +/// \ref UsdPrimCompositionQueryArc::MakeResolveTargetStrongerThan "strictly stronger than that arc" +/// (optionally providing a particular layer within the arc's layer stack to +/// further limit the range of specs). +/// +/// Alternatively, resolve targets can also be created via methods on UsdPrim +/// that can limit value resolution to either +/// \ref UsdPrim::MakeResolveTargetUpToEditTarget "up to" or +/// \ref UsdPrim::MakeResolveTargetStrongerThanEditTarget "stronger than" +/// the spec that would be edited when setting a value for the prim using the +/// given UsdEditTarget. +/// +/// Unlike UsdEditTarget, a UsdResolveTarget is only relevant to the prim it +/// is created for and can only be used in a UsdAttributeQuery for attributes +/// on this prim. +/// +/// \section Invalidation +/// This object does not listen for change notification. If a consumer is +/// holding on to a UsdResolveTarget, it is their responsibility to dispose +/// of it in response to a resync change to the associated prim. +/// Failing to do so may result in incorrect values or crashes due to +/// dereferencing invalid objects. +/// +class UsdResolveTarget { + +public: + UsdResolveTarget() = default; + + /// Get the prim index of the resolve target. + const PcpPrimIndex *GetPrimIndex() const { + return _expandedPrimIndex.get(); + } + + /// Returns the node that value resolution with this resolve target will + /// start at. + USD_API + PcpNodeRef GetStartNode() const; + + /// Returns the layer in the layer stack of the start node that value + /// resolution with this resolve target will start at. + USD_API + SdfLayerHandle GetStartLayer() const; + + /// Returns the node that value resolution with this resolve target will + /// stop at when the "stop at" layer is reached. + USD_API + PcpNodeRef GetStopNode() const; + + /// Returns the layer in the layer stack of the stop node that value + /// resolution with this resolve target will stop at. + USD_API + SdfLayerHandle GetStopLayer() const; + + /// Returns true if this is a null resolve target. + bool IsNull() const { + return !bool(_expandedPrimIndex); + } + +private: + // Non-null UsdResolveTargets can only be created by functions in UsdPrim + // and UsdPrimCompositionQueryArc. + friend class UsdPrim; + friend class UsdPrimCompositionQueryArc; + + // Usd_Resolver wants to access the iterators provided by this target. + friend class Usd_Resolver; + + USD_API + UsdResolveTarget( + const std::shared_ptr &index, + const PcpNodeRef &node, + const SdfLayerHandle &layer); + + USD_API + UsdResolveTarget( + const std::shared_ptr &index, + const PcpNodeRef &node, + const SdfLayerHandle &layer, + const PcpNodeRef &stopNode, + const SdfLayerHandle &stopLayer); + + // Resolve targets are created with an expanded prim index either from + // a composition query (which owns and holds it) or from a UsdPrim (which + // creates it solely to create the resolve target). The expanded prim index + // is not otherwise cached, so we have to hold on to it during the lifetime + // of the resolve target. + std::shared_ptr _expandedPrimIndex; + PcpNodeRange _nodeRange; + + PcpNodeIterator _startNodeIt; + SdfLayerRefPtrVector::const_iterator _startLayerIt; + PcpNodeIterator _stopNodeIt; + SdfLayerRefPtrVector::const_iterator _stopLayerIt; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // PXR_USD_USD_RESOLVE_TARGET_H diff --git a/pxr/usd/usd/resolver.cpp b/pxr/usd/usd/resolver.cpp index f034d6cbc7..0352417c67 100644 --- a/pxr/usd/usd/resolver.cpp +++ b/pxr/usd/usd/resolver.cpp @@ -25,6 +25,7 @@ #include "pxr/usd/usd/resolver.h" #include "pxr/usd/usd/debugCodes.h" +#include "pxr/usd/usd/resolveTarget.h" #include "pxr/usd/pcp/cache.h" #include "pxr/usd/pcp/errors.h" @@ -32,9 +33,10 @@ PXR_NAMESPACE_OPEN_SCOPE - -void -Usd_Resolver::_Init() { +Usd_Resolver::Usd_Resolver(const PcpPrimIndex* index, bool skipEmptyNodes) + :_index(index) + , _skipEmptyNodes(skipEmptyNodes) +{ PcpNodeRange range = _index->GetNodeRange(); _curNode = range.first; _endNode = range.second; @@ -43,12 +45,66 @@ Usd_Resolver::_Init() { // The entire stage may be empty, so we need to check IsValid here. if (IsValid()) { - const SdfLayerRefPtrVector& layers = _curNode->GetLayerStack()->GetLayers(); + const SdfLayerRefPtrVector& layers = + _curNode->GetLayerStack()->GetLayers(); _curLayer = layers.begin(); _endLayer = layers.end(); } } +Usd_Resolver::Usd_Resolver( + const UsdResolveTarget &resolveTarget, bool skipEmptyNodes) + : _index(resolveTarget.GetPrimIndex()) + , _skipEmptyNodes(skipEmptyNodes) +{ + _curNode = resolveTarget._startNodeIt; + _stopAtNode = resolveTarget._stopNodeIt; + _endNode = _index->GetNodeRange().second; + + // If the resolve target provided a node to stop at before the end of the + // prim index graph, we have to figure out the end iterators. + if (_stopAtNode != _endNode) { + // First assume we end as soon as we reach the stop node. + _endNode = _stopAtNode; + + // Check if the stop layer is past the beginning of the stop node layer + // stack. If so, we'll need to iterate into the stop node to catch those + // layers, so move the end node forward and store which layer to stop + // at when we reach the stop node layer stack. + const SdfLayerRefPtrVector& layers = + _stopAtNode->GetLayerStack()->GetLayers(); + if (resolveTarget._stopLayerIt != layers.begin()) { + ++_endNode; + _stopAtLayer = resolveTarget._stopLayerIt; + } + } + + _SkipEmptyNodes(); + + // The prim index may be empty within the resolve target range, so we need + // to check IsValid here. + if (IsValid()) { + const SdfLayerRefPtrVector& layers = + _curNode->GetLayerStack()->GetLayers(); + + // If we haven't skipped past the resolve target's start node, start + // with the resolve target's start layer. + if (_curNode == resolveTarget._startNodeIt) { + _curLayer = resolveTarget._startLayerIt; + } else { + _curLayer = layers.begin(); + } + + // If we reached the "stop at node" (and the resolver is still valid), + // the "stop at layer" determines what the end layer is. + if (_curNode == _stopAtNode) { + _endLayer = _stopAtLayer; + } else { + _endLayer = layers.end(); + } + } +} + void Usd_Resolver::_SkipEmptyNodes() { @@ -64,13 +120,6 @@ Usd_Resolver::_SkipEmptyNodes() } } -Usd_Resolver::Usd_Resolver(const PcpPrimIndex* index, bool skipEmptyNodes) - : _index(index) - , _skipEmptyNodes(skipEmptyNodes) -{ - _Init(); -} - size_t Usd_Resolver::GetLayerStackIndex() const { @@ -87,7 +136,14 @@ Usd_Resolver::NextNode() const SdfLayerRefPtrVector& layers = _curNode->GetLayerStack()->GetLayers(); _curLayer = layers.begin(); - _endLayer = layers.end(); + + // If we reached the "stop at node" (and the resolver is still valid), + // the "stop at layer" determines what the end layer is. + if (_curNode == _stopAtNode) { + _endLayer = _stopAtLayer; + } else { + _endLayer = layers.end(); + } } } diff --git a/pxr/usd/usd/resolver.h b/pxr/usd/usd/resolver.h index b0f9ccb673..77af1fbaa0 100644 --- a/pxr/usd/usd/resolver.h +++ b/pxr/usd/usd/resolver.h @@ -37,6 +37,7 @@ PXR_NAMESPACE_OPEN_SCOPE class PcpPrimIndex; +class UsdResolveTarget; /// \class Usd_Resolver /// @@ -52,7 +53,20 @@ class Usd_Resolver { /// about the prim represented by \p index. Otherwise, the resolver will /// visit all non-inert nodes in the index. USD_API - explicit Usd_Resolver(const PcpPrimIndex* index, bool skipEmptyNodes = true); + explicit Usd_Resolver( + const PcpPrimIndex* index, + bool skipEmptyNodes = true); + + /// Constructs a resolver with the given \p resolveTarget. The resolve + /// target provides the prim index as well as the range of nodes and layers + /// this resolver will iterate over. If \p skipEmptyNodes is \c true, the + /// resolver will skip over nodes that provide no opinions about the prim + /// represented by \p index. Otherwise, the resolver will visit all + /// non-inert nodes in the index. + USD_API + explicit Usd_Resolver( + const UsdResolveTarget &resolveTarget, + bool skipEmptyNodes = true); /// Returns true when there is a current Node and Layer. /// @@ -137,7 +151,6 @@ class Usd_Resolver { } private: - void _Init(); void _SkipEmptyNodes(); const PcpPrimIndex* _index; @@ -145,8 +158,10 @@ class Usd_Resolver { PcpNodeIterator _curNode; PcpNodeIterator _endNode; + PcpNodeIterator _stopAtNode; SdfLayerRefPtrVector::const_iterator _curLayer; SdfLayerRefPtrVector::const_iterator _endLayer; + SdfLayerRefPtrVector::const_iterator _stopAtLayer; }; PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/usd/usd/stage.cpp b/pxr/usd/usd/stage.cpp index 668da6cfb8..ff60dc1d63 100644 --- a/pxr/usd/usd/stage.cpp +++ b/pxr/usd/usd/stage.cpp @@ -7612,12 +7612,17 @@ SdfPropertySpecHandleVector UsdStage::_GetPropertyStack(const UsdProperty &prop, UsdTimeCode time) const { + auto makeUsdResolverFn = [&prop](bool skipEmptyNodes) { + return Usd_Resolver(&prop._Prim()->GetPrimIndex(), skipEmptyNodes); + }; + _PropertyStackResolver resolver(/* withLayerOffsets = */ false); if (time.IsDefault()) { - _GetResolvedValueAtDefaultImpl(prop, &resolver); + _GetResolvedValueAtDefaultImpl(prop, &resolver, makeUsdResolverFn); } else { double localTime = time.GetValue(); - _GetResolvedValueAtTimeImpl(prop, &resolver, &localTime); + _GetResolvedValueAtTimeImpl( + prop, &resolver, &localTime, makeUsdResolverFn); } return resolver.propertyStack; } @@ -7626,12 +7631,17 @@ std::vector> UsdStage::_GetPropertyStackWithLayerOffsets( const UsdProperty &prop, UsdTimeCode time) const { + auto makeUsdResolverFn = [&prop](bool skipEmptyNodes) { + return Usd_Resolver(&prop._Prim()->GetPrimIndex(), skipEmptyNodes); + }; + _PropertyStackResolver resolver(/* withLayerOffsets = */ true); if (time.IsDefault()) { - _GetResolvedValueAtDefaultImpl(prop, &resolver); + _GetResolvedValueAtDefaultImpl(prop, &resolver, makeUsdResolverFn); } else { double localTime = time.GetValue(); - _GetResolvedValueAtTimeImpl(prop, &resolver, &localTime); + _GetResolvedValueAtTimeImpl( + prop, &resolver, &localTime, makeUsdResolverFn); } return resolver.propertyStackWithLayerOffsets; } @@ -7804,6 +7814,37 @@ UsdStage::_GetResolveInfo(const UsdAttribute &attr, UsdResolveInfo *resolveInfo, const UsdTimeCode *time, _ExtraResolveInfo *extraInfo) const +{ + auto makeUsdResolverFn = [&attr](bool skipEmptyNodes) { + return Usd_Resolver(&attr._Prim()->GetPrimIndex(), skipEmptyNodes); + }; + _GetResolveInfoImpl(attr, resolveInfo, time, extraInfo, makeUsdResolverFn); + +} + +template +void +UsdStage::_GetResolveInfoWithResolveTarget( + const UsdAttribute &attr, + const UsdResolveTarget &resolveTarget, + UsdResolveInfo *resolveInfo, + const UsdTimeCode *time, + _ExtraResolveInfo *extraInfo) const +{ + auto makeUsdResolverFn = [&resolveTarget](bool skipEmptyNodes) { + return Usd_Resolver(resolveTarget, skipEmptyNodes); + }; + _GetResolveInfoImpl(attr, resolveInfo, time, extraInfo, makeUsdResolverFn); +} + +template +void +UsdStage::_GetResolveInfoImpl( + const UsdAttribute &attr, + UsdResolveInfo *resolveInfo, + const UsdTimeCode *time, + _ExtraResolveInfo *extraInfo, + const MakeUsdResolverFn &makeUsdResolverFn) const { _ExtraResolveInfo localExtraInfo; if (!extraInfo) { @@ -7812,12 +7853,14 @@ UsdStage::_GetResolveInfo(const UsdAttribute &attr, _ResolveInfoResolver resolver(attr, resolveInfo, extraInfo); if (!time) { - _GetResolvedValueAtTimeImpl(attr, &resolver, nullptr); + _GetResolvedValueAtTimeImpl( + attr, &resolver, nullptr, makeUsdResolverFn); } else if (time->IsDefault()) { - _GetResolvedValueAtDefaultImpl(attr, &resolver); + _GetResolvedValueAtDefaultImpl(attr, &resolver, makeUsdResolverFn); } else { double localTime = time->GetValue(); - _GetResolvedValueAtTimeImpl(attr, &resolver, &localTime); + _GetResolvedValueAtTimeImpl( + attr, &resolver, &localTime, makeUsdResolverFn); } if (TfDebug::IsEnabled(USD_VALIDATE_VARIABILITY) && @@ -7842,16 +7885,15 @@ UsdStage::_GetResolveInfo(const UsdAttribute &attr, // // Each of these functions is required to return true, to indicate that // iteration of opinions should stop, and false otherwise. -template +template void UsdStage::_GetResolvedValueAtDefaultImpl( const UsdProperty &prop, - Resolver *resolver) const + Resolver *resolver, + const MakeUsdResolverFn &makeUsdResolverFn) const { - auto primHandle = prop._Prim(); - SdfPath specPath; - Usd_Resolver res(&primHandle->GetPrimIndex(), /*skipEmptyNodes = */ true); + Usd_Resolver res = makeUsdResolverFn(/*skipEmptyNodes = */ true); for (bool isNewNode = true; res.IsValid(); isNewNode = res.NextLayer()) { if (isNewNode) { specPath = res.GetLocalPath(prop.GetName()); @@ -7950,14 +7992,15 @@ _GetResolvedValueAtTimeWithClipsImpl( resolver->ProcessFallback(); } -template +template void -UsdStage::_GetResolvedValueAtTimeImpl(const UsdProperty &prop, - Resolver *resolver, - const double *localTime) const +UsdStage::_GetResolvedValueAtTimeImpl( + const UsdProperty &prop, + Resolver *resolver, + const double *localTime, + const MakeUsdResolverFn &makeUsdResolverFn) const { auto primHandle = prop._Prim(); - const PcpPrimIndex *primIndex = &primHandle->GetPrimIndex(); if (primHandle->MayHaveOpinionsInClips()) { // Retrieve all clips that may contribute time samples for this @@ -7969,11 +8012,11 @@ UsdStage::_GetResolvedValueAtTimeImpl(const UsdProperty &prop, // Clips may contribute opinions at nodes where no specs for the // attribute exist in the node's LayerStack. So, since we have clips, // tell Usd_Resolver that we want to iterate over 'empty' nodes as well. - Usd_Resolver res(primIndex, /* skipEmptyNodes = */ false); + Usd_Resolver res = makeUsdResolverFn(/* skipEmptyNodes = */ false); _GetResolvedValueAtTimeWithClipsImpl( &res, prop.GetName(), resolver, localTime, clipsAffectingPrim); } else { - Usd_Resolver res(primIndex, /* skipEmptyNodes = */ true); + Usd_Resolver res = makeUsdResolverFn(/* skipEmptyNodes = */ true); _GetResolvedValueAtTimeNoClipsImpl( &res, prop.GetName(), resolver, localTime); } @@ -7987,6 +8030,17 @@ UsdStage::_GetResolveInfo(const UsdAttribute &attr, _GetResolveInfo(attr, resolveInfo, time); } +void +UsdStage::_GetResolveInfoWithResolveTarget( + const UsdAttribute &attr, + const UsdResolveTarget &resolveTarget, + UsdResolveInfo *resolveInfo, + const UsdTimeCode *time) const +{ + _GetResolveInfoWithResolveTarget( + attr, resolveTarget, resolveInfo, time); +} + template bool UsdStage::_GetValueFromResolveInfoImpl(const UsdResolveInfo &info, diff --git a/pxr/usd/usd/stage.h b/pxr/usd/usd/stage.h index 2710b24044..381f3d5bd8 100644 --- a/pxr/usd/usd/stage.h +++ b/pxr/usd/usd/stage.h @@ -74,6 +74,7 @@ class Usd_InstanceCache; class Usd_InstanceChanges; class Usd_InterpolatorBase; class UsdResolveInfo; +class UsdResolveTarget; class Usd_Resolver; class UsdPrim; class UsdPrimRange; @@ -2091,6 +2092,12 @@ class UsdStage : public TfRefBase, public TfWeakBase { UsdResolveInfo *resolveInfo, const UsdTimeCode *time = nullptr) const; + void _GetResolveInfoWithResolveTarget( + const UsdAttribute &attr, + const UsdResolveTarget &resolveTarget, + UsdResolveInfo *resolveInfo, + const UsdTimeCode *time = nullptr) const; + template struct _ExtraResolveInfo; // Gets the value resolve info for the given attribute. If time is provided, @@ -2104,17 +2111,47 @@ class UsdStage : public TfRefBase, public TfWeakBase { const UsdTimeCode *time = nullptr, _ExtraResolveInfo *extraInfo = nullptr) const; + // Gets the value resolve info for the given attribute using the given + // resolve target. If time is provided, the resolve info is evaluated for + // that specific time (which may be default). Otherwise, if time is null, + // the resolve info is evaluated for "any numeric time" and will not + // populate values in extraInfo that require a specific time to be + // evaluated. + template + void _GetResolveInfoWithResolveTarget( + const UsdAttribute &attr, + const UsdResolveTarget &resolveTarget, + UsdResolveInfo *resolveInfo, + const UsdTimeCode *time = nullptr, + _ExtraResolveInfo *extraInfo = nullptr) const; + + // Shared implementation function for _GetResolveInfo and + // _GetResolveInfoWithResolveTarget. The only difference between how these + // two functions behave is in how they create the Usd_Resolver used for + // iterating over nodes and layers, thus they provide this implementation + // with the needed MakeUsdResolverFn to create the Usd_Resolver. + template + void _GetResolveInfoImpl(const UsdAttribute &attr, + UsdResolveInfo *resolveInfo, + const UsdTimeCode *time, + _ExtraResolveInfo *extraInfo, + const MakeUsdResolverFn &makeUsdResolveFn) const; + template struct _ResolveInfoResolver; struct _PropertyStackResolver; - template - void _GetResolvedValueAtDefaultImpl(const UsdProperty &prop, - Resolver *resolver) const; - - template - void _GetResolvedValueAtTimeImpl(const UsdProperty &prop, - Resolver *resolver, - const double *time) const; + template + void _GetResolvedValueAtDefaultImpl( + const UsdProperty &prop, + Resolver *resolver, + const MakeUsdResolverFn &makeUsdResolverFn) const; + + template + void _GetResolvedValueAtTimeImpl( + const UsdProperty &prop, + Resolver *resolver, + const double *time, + const MakeUsdResolverFn &makeUsdResolverFn) const; bool _GetValue(UsdTimeCode time, const UsdAttribute &attr, VtValue* result) const; diff --git a/pxr/usd/usd/testenv/testUsdResolveTarget.cpp b/pxr/usd/usd/testenv/testUsdResolveTarget.cpp new file mode 100644 index 0000000000..1e5e6f8c4d --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTarget.cpp @@ -0,0 +1,789 @@ +// +// Copyright 2022 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// + +#include "pxr/pxr.h" +#include "pxr/usd/pcp/layerStack.h" +#include "pxr/usd/usd/attribute.h" +#include "pxr/usd/usd/attributeQuery.h" +#include "pxr/usd/usd/editContext.h" +#include "pxr/usd/usd/object.h" +#include "pxr/usd/usd/primCompositionQuery.h" +#include "pxr/usd/usd/stage.h" +#include "pxr/usd/sdf/path.h" +#include "pxr/base/tf/diagnostic.h" +#include "pxr/base/tf/ostreamMethods.h" +#include "pxr/base/tf/token.h" + +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +// This just for stringifying UsdResolveTarget to help debug failures in this +// test. +std::ostream& operator<<(std::ostream& os, const UsdResolveTarget& rt) +{ + os << "Resolve target:"; + os << "\n start node: " << rt.GetStartNode().GetSite(); + if (rt.GetStartLayer()) { + os << "\n start layer: " << rt.GetStartLayer()->GetIdentifier(); + } + if (rt.GetStopNode()) { + os << "\n stop node: " << rt.GetStopNode().GetSite(); + if (rt.GetStartLayer()) { + os << "\n stop layer: " << rt.GetStopLayer()->GetIdentifier(); + } + } + return os; +} + +// Helper for _VerifyResolveTarget +static void _VerifyResolveTargetSite( + const PcpNodeRef &node, + const PcpSite &expectedSite) +{ + PcpSite site(node.GetSite()); + TF_VERIFY(site == expectedSite, + "Site '%s' does not match expected '%s'", + TfStringify(site).c_str(), + TfStringify(expectedSite).c_str()); +} + +// Helper for _VerifyResolveTarget +static void _VerifyResolverTargetLayer( + const SdfLayerHandle &layer, + const std::string &expectedLayerName) +{ + std::string layerName = TfGetBaseName(layer->GetIdentifier()); + // Special case for expecting the session layer as the session layer created + // in this test will be an anonymous layer without a consistent identifier + // between runs. But it will always end in "root-session.usda" + if (expectedLayerName == "session") { + TF_VERIFY(TfStringEndsWith(layerName, "root-session.usda"), + "Layer name '%s' does not end with expected session layer suffix " + "'root-session.usda'", + layerName.c_str()); + } else { + TF_VERIFY(layerName == expectedLayerName, + "Layer name '%s' does not match expected layer name '%s'", + layerName.c_str(), + expectedLayerName.c_str()); + } +} + +// Helper for verifying a resolve target matches expected expect values. +// All resolve targets will have a start node and start layer but do not always +// have to have a stop node or stop layer +static void _VerifyResolveTarget( + const UsdResolveTarget &resolveTarget, + const std::pair &expectedStart, + const std::pair *expectedStop = nullptr) +{ + _VerifyResolveTargetSite( + resolveTarget.GetStartNode(), expectedStart.first); + _VerifyResolverTargetLayer( + resolveTarget.GetStartLayer(), expectedStart.second); + + if (expectedStop) { + _VerifyResolveTargetSite( + resolveTarget.GetStopNode(), expectedStop->first); + _VerifyResolverTargetLayer( + resolveTarget.GetStopLayer(), expectedStop->second); + } else { + TF_VERIFY(!resolveTarget.GetStopNode()); + TF_VERIFY(!resolveTarget.GetStopLayer()); + } +} + +// Helper for verifying the returned value from UsdAttributeQuery::Get. This +// tests both the templated explicit type overload and the type erased (VtValue) +// overload. +template +static void +_GetAndVerifyAttributeValue( + const UsdAttributeQuery &attrQuery, + const UsdTimeCode &time, + const T* expected) +{ + T value; + VtValue vtValue; + if (expected) { + TF_VERIFY(attrQuery.Get(&value, time), + "Failed to get value from attribute query at time %s", + TfStringify(time).c_str()); + TF_VERIFY(value == *expected, + "Returned value %s != expected value %s", + TfStringify(value).c_str(), + TfStringify(*expected).c_str()); + + TF_VERIFY(attrQuery.Get(&vtValue, time), + "Failed to get value from attribute query at time %s", + TfStringify(time).c_str()); + TF_VERIFY(vtValue.UncheckedGet() == *expected, + "Returned value %s != expected value %s", + TfStringify(vtValue).c_str(), + TfStringify(*expected).c_str()); + } else { + TF_VERIFY(!attrQuery.Get(&value, time), + "Attribute query Get was expected to fail at time %s", + TfStringify(time).c_str()); + TF_VERIFY(!attrQuery.Get(&vtValue, time), + "Attribute query Get was expected to fail at time %s", + TfStringify(time).c_str()); + } +} + +// Format for expected time sample values. +template +using _ExpectedTimeSamples = std::vector>; + +// Format for all expected values: a pair of expected time sample values and +// a VtValue holding the expected default value (if any). +template +using _ExpectedAttrGetValues = std::pair<_ExpectedTimeSamples, VtValue>; + +// Verifies that the results of calling the various API functions on the given +// UsdAttributeQuery match the expected results of those queries. +template +static void +_VerifyQuery( + const UsdAttributeQuery &attrQuery, + const _ExpectedAttrGetValues &expectedValues) +{ + // Extract the list of expected time sample values. + const _ExpectedTimeSamples &expectedTimeSampleValues = + expectedValues.first; + + // Extract the typed expected default value from expected values, which + // may be null if a default value is not expected. + const VtValue &expectedDefaultVtValue = expectedValues.second; + const T *expectedAuthoredDefaultValue = nullptr; + if (!expectedDefaultVtValue.IsEmpty()) { + TF_VERIFY(expectedDefaultVtValue.IsHolding(), + "Non-empty expected default VtValue must be holding a value of the " + "templated type."); + expectedAuthoredDefaultValue = + &(expectedDefaultVtValue.UncheckedGet()); + } + + // We expect HasAuthoredValue() to return true if we expect either time + // samples or a default value. + const bool expectedHasAuthoredValue = + !expectedTimeSampleValues.empty() || + expectedAuthoredDefaultValue; + TF_VERIFY(attrQuery.HasAuthoredValue() == expectedHasAuthoredValue, + "expected HasAuthoredValue() == %s", + TfStringify(expectedHasAuthoredValue).c_str()); + + // We expect HasValue to return true if we expect an authored value. + // Note that HasValue would return true if an attribute has a fallback value + // but this whole test doesn't use attributes with fallbacks. + const bool expectedHasValue = expectedHasAuthoredValue; + TF_VERIFY(attrQuery.HasValue() == expectedHasValue, + "expected HasValue() == %s", + TfStringify(expectedHasValue).c_str()); + + // Verify that GetTimeSamples returns the expected time sample times. + std::vector expectedTimeSampleTimes; + for (const auto &timeAndVal : expectedTimeSampleValues) { + expectedTimeSampleTimes.push_back(timeAndVal.first); + } + std::vector timeSampleTimes; + TF_VERIFY(attrQuery.GetTimeSamples(&timeSampleTimes)); + TF_VERIFY(timeSampleTimes == expectedTimeSampleTimes, + "Returned time sample times %s do not match expected time sample times " + "%s.", + TfStringify(timeSampleTimes).c_str(), + TfStringify(expectedTimeSampleTimes).c_str()); + + // Since this test currently doesn't involve clips, we expect + // ValueMightBeTimeVarying to be true iff we expect more than one time + // sample. + if (expectedTimeSampleValues.size() > 1) { + TF_VERIFY(attrQuery.ValueMightBeTimeVarying()); + } else { + TF_VERIFY(!attrQuery.ValueMightBeTimeVarying()); + } + + // Verify that calling Get at default time returns the expected default + // value. + _GetAndVerifyAttributeValue( + attrQuery, UsdTimeCode::Default(), expectedAuthoredDefaultValue); + // Verify that calling Get at each expected time sample time returns the + // expect time sample value. + for (const auto &timeAndVal : expectedTimeSampleValues) { + _GetAndVerifyAttributeValue( + attrQuery, timeAndVal.first, &(timeAndVal.second)); + } + // If we expect no time samples, verify that calling Get with a numeric time + // code returns the expected default value. + if (expectedTimeSampleValues.empty()) { + _GetAndVerifyAttributeValue( + attrQuery, 1.0, expectedAuthoredDefaultValue); + } +} + +// Makes a UsdAttributeQuery for the attribute using each of the given resolve +// targets and verifies for each that it produces the expected values +template +static void +_MakeAndVerifyQueries( + const UsdAttribute &attr, + const std::vector &resolveTargets, + const std::vector<_ExpectedAttrGetValues> expectedValues) +{ + std::cout << "\n** Start: Make and verify queries for attribute " << + attr.GetPath() << " **" << std::endl; + + TF_VERIFY(expectedValues.size() == resolveTargets.size(), + "Number or resolve targets %lu doesn't match the number of expected " + "values %lu.", + resolveTargets.size(), expectedValues.size()); + + for (size_t i = 0; i < resolveTargets.size(); ++i) { + std::cout << "Verifying query at " << resolveTargets[i] << std::endl; + + UsdAttributeQuery attrQuery(attr, resolveTargets[i]); + _VerifyQuery(attrQuery, expectedValues[i]); + } + + std::cout << "** SUCCESS: Make and verify queries for attribute " << + attr.GetPath() << " **" << std::endl; +} + +// Get all the possible resolve targets for the prim that can be created to +// resolve up to and to resolve stronger than the possible nodes and layers in +// its prim index. +static void +_GetAllResolveTargetsForPrim( + const UsdPrim &prim, + std::vector *upToResolveTargets, + std::vector *strongThanResolveTargets) +{ + // The prim composition query gets us every arc that could contribute specs + // to the prim (even if the arc would be culled normally) so we use it to + // create all resolve targets. + UsdPrimCompositionQuery query(prim); + std::vector arcs = query.GetCompositionArcs(); + + // Loop through every layer in each composition arc creating both the + // "up to" and "stronger than" resolve targets for each. + for (const UsdPrimCompositionQueryArc &arc : arcs) { + const SdfLayerRefPtrVector &layers = + arc.GetTargetNode().GetLayerStack()->GetLayers(); + for (const SdfLayerHandle &layer : layers) { + + upToResolveTargets->push_back( + arc.MakeResolveTargetUpTo(layer)); + strongThanResolveTargets->push_back( + arc.MakeResolveTargetStrongerThan(layer)); + } + } +} + +static void _TestGetAttrValueWithResolveTargets() +{ + UsdStageRefPtr stage = UsdStage::Open("./resolveTarget/root.usda"); + TF_AXIOM(stage); + + // Parent unculled prim stack is: + // /Parent : session.usda -> root.usda -> sub1.usda -> sub2.usda + // | + // ref + // v + // /InternalRef : session.usda -> root.usda -> sub1.usda -> sub2.usda + // | + // ref + // v + // /RefParent : ref.usda -> ref_sub1.usda -> ref_sub2.usda + UsdPrim parentPrim = stage->GetPrimAtPath(SdfPath("/Parent")); + TF_AXIOM(parentPrim); + // /Parent/RefChild is just a namespace child of /Parent with no additional + // composition arcs of its own outside of its ancestral composition. + UsdPrim childPrim = stage->GetPrimAtPath(SdfPath("/Parent/RefChild")); + TF_AXIOM(childPrim); + + // Get the root layer stack ID to use for verification purposes. + PcpLayerStackIdentifier rootLayerStackId( + stage->GetRootLayer(), + stage->GetSessionLayer(), + stage->GetPathResolverContext()); + + // Get the ref.usda layer stack ID to also use for verification purposes. + SdfLayerHandle refLayer = SdfLayer::Find("./resolveTarget/ref.usda"); + PcpLayerStackIdentifier + refLayerStackId(refLayer, nullptr, stage->GetPathResolverContext()); + + // Get all the possible resolve targets for the child prim. + std::vector upToResolveTargets; + std::vector strongThanResolveTargets; + _GetAllResolveTargetsForPrim( + childPrim, + &upToResolveTargets, + &strongThanResolveTargets); + + // This is the expected list of all node sites and sublayers we expect + // resolve targets to have been created for from the child prim. + std::vector > expectedTargets { + // Node: /Parent/RefChild + {PcpSite(rootLayerStackId, SdfPath("/Parent/RefChild")), "session"}, + {PcpSite(rootLayerStackId, SdfPath("/Parent/RefChild")), "root.usda"}, + {PcpSite(rootLayerStackId, SdfPath("/Parent/RefChild")), "sub1.usda"}, + {PcpSite(rootLayerStackId, SdfPath("/Parent/RefChild")), "sub2.usda"}, + + // Node: /Internal/RefChild + {PcpSite(rootLayerStackId, SdfPath("/InternalRef/RefChild")), "session"}, + {PcpSite(rootLayerStackId, SdfPath("/InternalRef/RefChild")), "root.usda"}, + {PcpSite(rootLayerStackId, SdfPath("/InternalRef/RefChild")), "sub1.usda"}, + {PcpSite(rootLayerStackId, SdfPath("/InternalRef/RefChild")), "sub2.usda"}, + + // Node: /RefParent/RefChild + {PcpSite(refLayerStackId, SdfPath("/RefParent/RefChild")), "ref.usda"}, + {PcpSite(refLayerStackId, SdfPath("/RefParent/RefChild")), "ref_sub1.usda"}, + {PcpSite(refLayerStackId, SdfPath("/RefParent/RefChild")), "ref_sub2.usda"} + }; + + TF_AXIOM(upToResolveTargets.size() == 11); + TF_AXIOM(strongThanResolveTargets.size() == 11); + for (size_t i = 0; i < expectedTargets.size(); ++i) { + // Verify that each "up to" resolve target starts at the expected node + // and layer. + _VerifyResolveTarget(upToResolveTargets[i], expectedTargets[i]); + + // Verify that each "stronger than" resolve target starts at the root + // node and session layer (strongest layer in the root node layer stack) + // and stops at the expected node and layer. + _VerifyResolveTarget(strongThanResolveTargets[i], + expectedTargets[0], &expectedTargets[i]); + } + + // Verify expected values from attribute queries made on attributes of + // childPrim using each resolve target. + + // /Parent/RefChild.foo + // Has only default values authored: + // root.usda: /Parent/RefChild -> 6.0 + // sub1.usda: /Parent/RefChild -> 5.0 + // sub2.usda: /Parent/RefChild -> 4.0 + // ref.usda: /RefParent/RefChild -> 3.0 + // ref_sub1.usda: /RefParent/RefChild -> 2.0 + // ref_sub2.usda: /RefParent/RefChild -> 1.0 + UsdAttribute fooAttr = childPrim.GetAttribute(TfToken("foo")); + TF_AXIOM(fooAttr); + _MakeAndVerifyQueries(fooAttr, upToResolveTargets, + { + // Node: /Parent/RefChild + {{}, VtValue(6.0f)}, + {{}, VtValue(6.0f)}, + {{}, VtValue(5.0f)}, + {{}, VtValue(4.0f)}, + + // Node: /Internal/RefChild + {{}, VtValue(3.0f)}, + {{}, VtValue(3.0f)}, + {{}, VtValue(3.0f)}, + {{}, VtValue(3.0f)}, + + // Node: /RefParent/RefChild + {{}, VtValue(3.0f)}, + {{}, VtValue(2.0f)}, + {{}, VtValue(1.0f)} + }); + + _MakeAndVerifyQueries(fooAttr, strongThanResolveTargets, + { + // Node: /Parent/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue(6.0f)}, + {{}, VtValue(6.0f)}, + + // Node: /Internal/RefChild + {{}, VtValue(6.0f)}, + {{}, VtValue(6.0f)}, + {{}, VtValue(6.0f)}, + {{}, VtValue(6.0f)}, + + // Node: /RefParent/RefChild + {{}, VtValue(6.0f)}, + {{}, VtValue(6.0f)}, + {{}, VtValue(6.0f)} + }); + + // /Parent/RefChild.var + // Has only time sample values authored: + // root.usda: /Parent/RefChild -> {1.0: 6, 6.0: 1} + // sub1.usda: /Parent/RefChild -> {1.0: 5, 5.0: 1} + // sub2.usda: /Parent/RefChild -> {1.0: 4, 4.0: 1} + // ref.usda: /RefParent/RefChild -> {1.0: 3, 3.0: 1} + // ref_sub1.usda: /RefParent/RefChild -> {1.0: 2, 2.0: 1} + // ref_sub2.usda: /RefParent/RefChild -> {1.0: 1} + UsdAttribute varAttr = childPrim.GetAttribute(TfToken("var")); + TF_AXIOM(varAttr); + _MakeAndVerifyQueries(varAttr, upToResolveTargets, + { + // Node: /Parent/RefChild + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 5}, {5.0, 1}}, VtValue()}, + {{{1.0, 4}, {4.0, 1}}, VtValue()}, + + // Node: /Internal/RefChild + {{{1.0, 3}, {3.0, 1}}, VtValue()}, + {{{1.0, 3}, {3.0, 1}}, VtValue()}, + {{{1.0, 3}, {3.0, 1}}, VtValue()}, + {{{1.0, 3}, {3.0, 1}}, VtValue()}, + + // Node: /RefParent/RefChild + {{{1.0, 3}, {3.0, 1}}, VtValue()}, + {{{1.0, 2}, {2.0, 1}}, VtValue()}, + {{{1.0, 1}}, VtValue()} + }); + + _MakeAndVerifyQueries(varAttr, strongThanResolveTargets, + { + // Node: /Parent/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + + // Node: /Internal/RefChild + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + + // Node: /RefParent/RefChild + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()} + }); + + // /Parent/RefChild.bar + // Has alternating time samples and default values authored: + // root.usda: /Parent/RefChild -> {1.0: 6, 6.0: 1} + // sub1.usda: /Parent/RefChild -> 5 + // sub2.usda: /Parent/RefChild -> {1.0: 4, 4.0: 1} + // ref.usda: /RefParent/RefChild -> 3 + // ref_sub1.usda: /RefParent/RefChild -> {1.0: 2, 2.0: 1} + // ref_sub2.usda: /RefParent/RefChild -> 1 + UsdAttribute barAttr = childPrim.GetAttribute(TfToken("bar")); + TF_AXIOM(barAttr); + _MakeAndVerifyQueries(barAttr, upToResolveTargets, + { + // Node: /Parent/RefChild + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + {{}, VtValue(5)}, + {{{1.0, 4}, {4.0, 1}}, VtValue(3)}, + + // Node: /Internal/RefChild + {{}, VtValue(3)}, + {{}, VtValue(3)}, + {{}, VtValue(3)}, + {{}, VtValue(3)}, + + // Node: /RefParent/RefChild + {{}, VtValue(3)}, + {{{1.0, 2}, {2.0, 1}}, VtValue(1)}, + {{}, VtValue(1)} + }); + + _MakeAndVerifyQueries(barAttr, strongThanResolveTargets, + { + // Node: /Parent/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue()}, + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + + // Node: /Internal/RefChild + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + + // Node: /RefParent/RefChild + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + {{{1.0, 6}, {6.0, 1}}, VtValue(5)}, + {{{1.0, 6}, {6.0, 1}}, VtValue(5)} + }); + + // /Parent/RefChild.sub1 + // Has default and time samples authored only on the sub1 layer of the + // root node: + // sub1.usda: /Parent/RefChild -> {1.0: "sub1_1", 5.0: "sub1_5"} + // "sub1_def" + UsdAttribute sub1Attr = childPrim.GetAttribute(TfToken("sub1")); + TF_AXIOM(sub1Attr); + _MakeAndVerifyQueries(sub1Attr, upToResolveTargets, + { + // Node: /Parent/RefChild + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{}, VtValue()}, + + // Node: /Internal/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()}, + + // Node: /RefParent/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()} + }); + + _MakeAndVerifyQueries(sub1Attr, strongThanResolveTargets, + { + // Node: /Parent/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + + // Node: /Internal/RefChild + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + + // Node: /RefParent/RefChild + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))}, + {{{1.0, TfToken("sub1_1")}, {5.0, TfToken("sub1_5")}}, + VtValue(TfToken("sub1_def"))} + }); + + // /Parent/RefChild.ref_sub1 + // Has default and time samples authored only on the ref_sub1 layer of the + // reference node: + // sub1.usda: /Parent/RefChild -> {1.0: "ref_sub1_1", 2.0: "ref_sub1_2"} + // "ref_sub1_def" + UsdAttribute refSub1Attr = childPrim.GetAttribute(TfToken("ref_sub1")); + TF_AXIOM(refSub1Attr); + _MakeAndVerifyQueries(refSub1Attr, upToResolveTargets, + { + // Node: /Parent/RefChild + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + + // Node: /Internal/RefChild + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + + // Node: /RefParent/RefChild + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))}, + {{}, VtValue()} + }); + + _MakeAndVerifyQueries(refSub1Attr, strongThanResolveTargets, + { + // Node: /Parent/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()}, + + // Node: /Internal/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()}, + {{}, VtValue()}, + + // Node: /RefParent/RefChild + {{}, VtValue()}, + {{}, VtValue()}, + {{{1.0, TfToken("ref_sub1_1")}, {2.0, TfToken("ref_sub1_2")}}, + VtValue(TfToken("ref_sub1_def"))} + }); + + // Test creating resolve targets from edit targets + { + // Create an edit target that targets the sub2 layer with no PcpMapping + UsdEditTarget editTarget(SdfLayer::Find("./resolveTarget/sub2.usda")); + + // Make both an "up to" and "stronger than" resolve target for + // /Parent/RefChild from this edit target. + UsdResolveTarget upToEditTarget = + childPrim.MakeResolveTargetUpToEditTarget(editTarget); + UsdResolveTarget strongerThanEditTarget = + childPrim.MakeResolveTargetStrongerThanEditTarget(editTarget); + + // Verify the resolve targets created from edit targets against the + // expected targets established above. + _VerifyResolveTarget(upToEditTarget, expectedTargets[3]); + _VerifyResolveTarget(strongerThanEditTarget, + expectedTargets[0], &expectedTargets[3]); + + // Using /Parent/RefChild.foo verify the attribute value resolves + // correctly based on "up to" and "stronger than" the edit target spec: + // root.usda: /Parent/RefChild -> 6.0 + // sub1.usda: /Parent/RefChild -> 5.0 + // sub2.usda: /Parent/RefChild -> 4.0 (edit target spec) + // ... + _VerifyQuery(UsdAttributeQuery(fooAttr, upToEditTarget), + {{}, VtValue(4.0f)}); + _VerifyQuery(UsdAttributeQuery(fooAttr, strongerThanEditTarget), + {{}, VtValue(6.0f)}); + + // Now set /Parent/RefChild.foo to 10.0 with the edit target. + UsdEditContext context(stage, editTarget); + fooAttr.Set(10.0f); + + // Like UsdPrimCompositionQuery and UsdAttributeQuery, resolve targets + // do not listen to change notification and must be recreated if a + // change potentially affecting the composed scene occurs. In this case + // authoring fooAttr's default on a layer that already has a spec for + // it does cause recomposition, but we recreate the resolve targets + // anyway. + upToEditTarget = + childPrim.MakeResolveTargetUpToEditTarget(editTarget); + strongerThanEditTarget = + childPrim.MakeResolveTargetStrongerThanEditTarget(editTarget); + + _VerifyResolveTarget(upToEditTarget, expectedTargets[3]); + _VerifyResolveTarget(strongerThanEditTarget, + expectedTargets[0], &expectedTargets[3]); + + // Verify the attribute value resolves correctly based on "up to" and + // "stronger than" the edit target spec's new value in sub2.usda: + // root.usda: /Parent/RefChild -> 6.0 + // sub1.usda: /Parent/RefChild -> 5.0 + // sub2.usda: /Parent/RefChild -> 10.0 (edit target spec) + // ... + _VerifyQuery(UsdAttributeQuery(fooAttr, upToEditTarget), + {{}, VtValue(10.0f)}); + _VerifyQuery(UsdAttributeQuery(fooAttr, strongerThanEditTarget), + {{}, VtValue(6.0f)}); + } + + { + // Create an edit target that targets the sub2 layer but maps across + // the /Parent's internal reference to /InternalRef. + PcpNodeRef internalRefNode = + parentPrim.GetPrimIndex().GetNodeProvidingSpec( + stage->GetRootLayer(), SdfPath("/InternalRef")); + UsdEditTarget editTarget( + SdfLayer::Find("./resolveTarget/sub2.usda"), internalRefNode); + + // Make both an "up to" and "stronger than" resolve target for + // /Parent/RefChild from this edit target. + UsdResolveTarget upToEditTarget = + childPrim.MakeResolveTargetUpToEditTarget(editTarget); + UsdResolveTarget strongerThanEditTarget = + childPrim.MakeResolveTargetStrongerThanEditTarget(editTarget); + + // Verify the resolve targets created from edit targets against the + // expected targets established above. + _VerifyResolveTarget(upToEditTarget, expectedTargets[7]); + _VerifyResolveTarget(strongerThanEditTarget, + expectedTargets[0], &expectedTargets[7]); + + // Using /Parent/RefChild.foo verify the attribute value resolves + // correctly based on "up to" and "stronger than" the edit target spec: + // root.usda: /Parent/RefChild -> 6.0 + // sub1.usda: /Parent/RefChild -> 5.0 + // sub2.usda: /Parent/RefChild -> 4.0 + // ... + // sub2.usda: /InternalRef/RefChild -> no spec (edit target spec) + // ... + // ref.usda: /RefParent/RefChild -> 3.0 + // ref_sub1.usda: /RefParent/RefChild -> 2.0 + // ref_sub2.usda: /RefParent/RefChild -> 1.0 + _VerifyQuery(UsdAttributeQuery(fooAttr, upToEditTarget), + {{}, VtValue(3.0f)}); + _VerifyQuery(UsdAttributeQuery(fooAttr, strongerThanEditTarget), + {{}, VtValue(6.0f)}); + + // Now set /Parent/RefChild.foo to 20.0 with the edit target. + UsdEditContext context(stage, editTarget); + fooAttr.Set(20.0f); + + // Like mentioned above, resolve targets do not listen to change + // notification and must be recreated if a change potentially affecting + // the composed scene occurs. In this case authoring fooAttr's default + // introduces a new spec that causes a node to have specs when it didn't + // before. We MUST recreate the resolve targets due to this change. + upToEditTarget = + childPrim.MakeResolveTargetUpToEditTarget(editTarget); + strongerThanEditTarget = + childPrim.MakeResolveTargetStrongerThanEditTarget(editTarget); + + _VerifyResolveTarget(upToEditTarget, expectedTargets[7]); + _VerifyResolveTarget(strongerThanEditTarget, + expectedTargets[0], &expectedTargets[7]); + + // Verify the attribute value resolves correctly based on "up to" and + // "stronger than" the edit target spec's new value in sub2.usda: + // root.usda: /Parent/RefChild -> 6.0 + // sub1.usda: /Parent/RefChild -> 5.0 + // sub2.usda: /Parent/RefChild -> 4.0 + // ... + // sub2.usda: /InternalRef/RefChild -> 20.0 (edit target spec) + // ... + // ref.usda: /RefParent/RefChild -> 3.0 + // ref_sub1.usda: /RefParent/RefChild -> 2.0 + // ref_sub2.usda: /RefParent/RefChild -> 1.0 + _VerifyQuery(UsdAttributeQuery(fooAttr, upToEditTarget), + {{}, VtValue(20.0f)}); + _VerifyQuery(UsdAttributeQuery(fooAttr, strongerThanEditTarget), + {{}, VtValue(6.0f)}); + + } +} + +int main() { + _TestGetAttrValueWithResolveTargets(); + + printf("\n\n>>> Test SUCCEEDED\n"); +} diff --git a/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref.usda b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref.usda new file mode 100644 index 0000000000..fa00f9ee54 --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref.usda @@ -0,0 +1,20 @@ +#usda 1.0 +( + subLayers = [@./ref_sub1.usda@, @./ref_sub2.usda@] +) + +over "RefParent" +{ + over "RefChild" + { + float foo = 3 + float radius = 3 + int var.timeSamples = { + 1: 3, + 3: 1 + } + + int bar = 3 + } +} + diff --git a/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub1.usda b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub1.usda new file mode 100644 index 0000000000..5e9dd07027 --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub1.usda @@ -0,0 +1,26 @@ +#usda 1.0 +( +) + +over "RefParent" +{ + over "RefChild" + { + float foo = 2 + float radius = 2 + int var.timeSamples = { + 1: 2, + 2: 1 + } + int bar.timeSamples = { + 1: 2, + 2: 1 + } + token ref_sub1 = "ref_sub1_def" + token ref_sub1.timeSamples = { + 1: "ref_sub1_1", + 2: "ref_sub1_2" + } + } +} + diff --git a/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub2.usda b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub2.usda new file mode 100644 index 0000000000..770dae2623 --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/ref_sub2.usda @@ -0,0 +1,18 @@ +#usda 1.0 +( +) + +def Xform "RefParent" +{ + def Sphere "RefChild" + { + float foo = 1 + float radius = 1 + int var.timeSamples = { + 1: 1 + } + + int bar = 1 + } +} + diff --git a/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/root.usda b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/root.usda new file mode 100644 index 0000000000..adb400b87f --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/root.usda @@ -0,0 +1,30 @@ +#usda 1.0 +( + subLayers = [@./sub1.usda@, @./sub2.usda@] +) + +over "Parent" ( + prepend references = [, @./ref.usda@] +) +{ + over "RefChild" + { + float foo = 6 + float radius = 6 + int var.timeSamples = { + 1: 6, + 6: 1 + } + + int bar.timeSamples = { + 1: 6, + 6: 1 + } + + } +} + +over "InternalRef" +{ + +} diff --git a/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub1.usda b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub1.usda new file mode 100644 index 0000000000..d1dd75d0e1 --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub1.usda @@ -0,0 +1,23 @@ +#usda 1.0 +( +) + +over "Parent" +{ + over "RefChild" + { + float foo = 5 + float radius = 5 + int var.timeSamples = { + 1: 5, + 5: 1 + } + + int bar = 5 + token sub1 = "sub1_def" + token sub1.timeSamples = { + 1: "sub1_1", + 5: "sub1_5" + } + } +} \ No newline at end of file diff --git a/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub2.usda b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub2.usda new file mode 100644 index 0000000000..575b1b02e0 --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTarget.testenv/resolveTarget/sub2.usda @@ -0,0 +1,21 @@ +#usda 1.0 +( +) + +over "Parent" +{ + over "RefChild" + { + float foo = 4 + float radius = 4 + int var.timeSamples = { + 1: 4, + 4: 1 + } + + int bar.timeSamples = { + 1: 4, + 4: 1 + } + } +} diff --git a/pxr/usd/usd/testenv/testUsdResolveTargetPy.py b/pxr/usd/usd/testenv/testUsdResolveTargetPy.py new file mode 100644 index 0000000000..11eaa13e25 --- /dev/null +++ b/pxr/usd/usd/testenv/testUsdResolveTargetPy.py @@ -0,0 +1,297 @@ +#!/pxrpythonsubst +# +# Copyright 2022 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. + +import unittest +from pxr import Sdf, Usd + +class TestUsdResolveTargetPy(unittest.TestCase): + """ + Test for value resolution with UsdResolverTarget in python. This test is + to verify that the python API works but is not meant to be a full coverage + test. Full coverage is handled by the C++ test testUsdResolveTarget. + """ + + def _VerifyAttrQuery(self, attrQuery, + expectedTimeSamples = None, + expectedDefaultValue = None) : + # We expect HasAuthoredValue() to return true if we expect either time + # samples or a default value. + expectedHasAuthoredValue = bool(expectedTimeSamples or expectedDefaultValue) + self.assertEqual(attrQuery.HasAuthoredValue(), expectedHasAuthoredValue) + + # We expect HasValue to return true if we expect an authored value. + # Note that HasValue would return true if an attribute has a fallback value + # but this whole test doesn't use attributes with fallbacks. + expectedHasValue = expectedHasAuthoredValue + self.assertEqual(attrQuery.HasValue(), expectedHasValue) + + # Verify that GetTimeSamples returns the expected time sample times. + expectedTimeSampleTimes = [] if expectedTimeSamples is None else \ + [time for time, _ in expectedTimeSamples] + self.assertEqual(attrQuery.GetTimeSamples(), expectedTimeSampleTimes) + + # Since this test currently doesn't involve clips, we expect + # ValueMightBeTimeVarying to be true iff we expect more than one time + # sample. + if len(expectedTimeSampleTimes) > 1: + self.assertTrue(attrQuery.ValueMightBeTimeVarying()) + else: + self.assertFalse(attrQuery.ValueMightBeTimeVarying()) + + # Verify that calling Get at default time returns the expected default + # value. + if expectedDefaultValue is None: + self.assertIsNone(attrQuery.Get(Usd.TimeCode.Default())) + else: + self.assertEqual( + attrQuery.Get(Usd.TimeCode.Default()), expectedDefaultValue) + + if expectedTimeSamples is None: + # If we expect no time samples, verify that calling Get with a + # numeric time code returns the expected default value. + if expectedDefaultValue is None: + self.assertIsNone(attrQuery.Get(1.0)) + else: + self.assertEqual( + attrQuery.Get(1.0), expectedDefaultValue) + else: + # Verify that calling Get at each expected time sample time returns + # the expected time sample value. + for time, val in expectedTimeSamples: + self.assertEqual(attrQuery.Get(time), val) + + def test_ResolveTargetFromEditTarget(self): + """Test UsdResolveTargets created from a UsdEditTarget.""" + + stage = Usd.Stage.Open("resolveTarget/root.usda") + # Parent unculled prim stack is: + # /Parent : session.usda -> root.usda -> sub1.usda -> sub2.usda + # | + # ref + # v + # /InternalRef : session.usda -> root.usda -> sub1.usda -> sub2.usda + # | + # ref + # v + # /RefParent : ref.usda -> ref_sub1.usda -> ref_sub2.usda + parentPrim = stage.GetPrimAtPath("/Parent") + self.assertTrue(parentPrim) + # /Parent/RefChild is just a namespace child of /Parent with no + # additional composition arcs of its own outside of its ancestral + # composition. + childPrim = stage.GetPrimAtPath("/Parent/RefChild") + self.assertTrue(childPrim) + + # Create an edit target that targets the sub2 layer with no PcpMapping + editTarget = Usd.EditTarget(Sdf.Layer.Find('resolveTarget/sub2.usda')) + self.assertTrue(editTarget.IsValid()) + self.assertFalse(editTarget.IsNull()) + + # Make both an "up to" and "stronger than" resolve target for + # /Parent/RefChild from this edit target. + resolveUpToEditTarget = \ + childPrim.MakeResolveTargetUpToEditTarget(editTarget); + resolveStrongerThanEditTarget = \ + childPrim.MakeResolveTargetStrongerThanEditTarget(editTarget); + self.assertFalse(resolveUpToEditTarget.IsNull()) + self.assertFalse(resolveStrongerThanEditTarget.IsNull()) + + # Using /Parent/RefChild.foo verify the attribute value resolves + # correctly based on "up to" and "stronger than" the edit target spec: + # root.usda: /Parent/RefChild -> 6.0 + # sub1.usda: /Parent/RefChild -> 5.0 + # sub2.usda: /Parent/RefChild -> 4.0 (edit target spec) + # ... + attr = stage.GetAttributeAtPath("/Parent/RefChild.foo") + self.assertTrue(attr) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, resolveUpToEditTarget), + expectedDefaultValue=4.0) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, resolveStrongerThanEditTarget), + expectedDefaultValue=6.0) + + # Now set /Parent/RefChild.foo to 10.0 with the edit target. + with Usd.EditContext(stage, editTarget): + attr.Set(10.0) + + # Like UsdPrimCompositionQuery and UsdAttributeQuery, resolve targets + # do not listen to change notification and must be recreated if a + # change potentially affecting the composed scene occurs. In this case + # authoring fooAttr's default on a layer that already has a spec for + # it does cause recomposition, but we recreate the resolve targets + # anyway. + resolveUpToEditTarget = \ + childPrim.MakeResolveTargetUpToEditTarget(editTarget); + resolveStrongerThanEditTarget = \ + childPrim.MakeResolveTargetStrongerThanEditTarget(editTarget); + self.assertFalse(resolveUpToEditTarget.IsNull()) + self.assertFalse(resolveStrongerThanEditTarget.IsNull()) + + # Verify the attribute value resolves correctly based on "up to" and + # "stronger than" the edit target spec's new value in sub2.usda: + # root.usda: /Parent/RefChild -> 6.0 + # sub1.usda: /Parent/RefChild -> 5.0 + # sub2.usda: /Parent/RefChild -> 10.0 (edit target spec) + # ... + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, resolveUpToEditTarget), + expectedDefaultValue=10.0) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, resolveStrongerThanEditTarget), + expectedDefaultValue=6.0) + + def test_ResolveTargetFromCompositionQuery(self): + """Test UsdResolveTargets created from a UsdPrimCompositionQuery.""" + + stage = Usd.Stage.Open("resolveTarget/root.usda") + # Parent unculled prim stack is: + # /Parent : session.usda -> root.usda -> sub1.usda -> sub2.usda + # | + # ref + # v + # /InternalRef : session.usda -> root.usda -> sub1.usda -> sub2.usda + # | + # ref + # v + # /RefParent : ref.usda -> ref_sub1.usda -> ref_sub2.usda + parentPrim = stage.GetPrimAtPath("/Parent") + self.assertTrue(parentPrim) + # /Parent/RefChild is just a namespace child of /Parent with no + # additional composition arcs of its own outside of its ancestral + # composition. + childPrim = stage.GetPrimAtPath("/Parent/RefChild") + self.assertTrue(childPrim) + + # Create a prim composition query for /Parent/RefChild. The prim + # composition query gets us every arc that could contribute specs to the + # prim (even if the arc would be culled normally) so we use it to + # create all resolve targets. + query = Usd.PrimCompositionQuery(childPrim) + arcs = query.GetCompositionArcs() + + # Loop through every layer in each composition arc creating both the + # "up to" and "stronger than" resolve targets for each. + upToResolveTargets = [] + strongerThanResolveTargets = [] + for arc in arcs: + layers = arc.GetTargetNode().layerStack.layers + for layer in layers: + upToResolveTargets.append( + arc.MakeResolveTargetUpTo(layer)) + strongerThanResolveTargets.append( + arc.MakeResolveTargetStrongerThan(layer)) + + self.assertEqual(len(upToResolveTargets), 11) + self.assertEqual(len(strongerThanResolveTargets), 11) + + # /Parent/RefChild.bar + # Has alternating time samples and default values authored: + # root.usda: /Parent/RefChild -> {1.0: 6, 6.0: 1} + # sub1.usda: /Parent/RefChild -> 5 + # sub2.usda: /Parent/RefChild -> {1.0: 4, 4.0: 1} + # ref.usda: /RefParent/RefChild -> 3 + # ref_sub1.usda: /RefParent/RefChild -> {1.0: 2, 2.0: 1} + # ref_sub2.usda: /RefParent/RefChild -> 1 + attr = stage.GetAttributeAtPath("/Parent/RefChild.bar") + self.assertTrue(attr) + + # Node: /Parent/RefChild + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[0]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=5) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[0]), + expectedTimeSamples=None, + expectedDefaultValue=None) + + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[1]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=5) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[1]), + expectedTimeSamples=None, + expectedDefaultValue=None) + + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[2]), + expectedTimeSamples=None, + expectedDefaultValue=5) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[2]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=None) + + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[3]), + expectedTimeSamples=[(1.0, 4), (4.0, 1)], + expectedDefaultValue=3) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[3]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=5) + + # Node: /Internal/RefChild + for i in range(4, 8): + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[i]), + expectedTimeSamples=None, + expectedDefaultValue=3) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[i]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=5) + + # Node: /RefParent/RefChild + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[8]), + expectedTimeSamples=None, + expectedDefaultValue=3) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[8]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=5) + + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[9]), + expectedTimeSamples=[(1.0, 2), (2.0, 1)], + expectedDefaultValue=1) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[9]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=5) + + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, upToResolveTargets[10]), + expectedTimeSamples=None, + expectedDefaultValue=1) + self._VerifyAttrQuery( + Usd.AttributeQuery(attr, strongerThanResolveTargets[10]), + expectedTimeSamples=[(1.0, 6), (6.0, 1)], + expectedDefaultValue=5) + +if __name__ == '__main__': + unittest.main() diff --git a/pxr/usd/usd/wrapAttributeQuery.cpp b/pxr/usd/usd/wrapAttributeQuery.cpp index 016d640d6b..a3dac7a0f0 100644 --- a/pxr/usd/usd/wrapAttributeQuery.cpp +++ b/pxr/usd/usd/wrapAttributeQuery.cpp @@ -112,6 +112,9 @@ void wrapUsdAttributeQuery() .def(init( (arg("prim"), arg("attributeName")))) + .def(init( + (arg("attribute"), arg("resolveTarget")))) + .def("CreateQueries", &UsdAttributeQuery::CreateQueries, (arg("prim"), arg("attributeNames")), return_value_policy()) diff --git a/pxr/usd/usd/wrapPrim.cpp b/pxr/usd/usd/wrapPrim.cpp index 465489cbe4..343d461a8c 100644 --- a/pxr/usd/usd/wrapPrim.cpp +++ b/pxr/usd/usd/wrapPrim.cpp @@ -27,6 +27,7 @@ #include "pxr/usd/usd/payloads.h" #include "pxr/usd/usd/relationship.h" #include "pxr/usd/usd/references.h" +#include "pxr/usd/usd/resolveTarget.h" #include "pxr/usd/usd/inherits.h" #include "pxr/usd/usd/specializes.h" #include "pxr/usd/usd/variantSets.h" @@ -452,6 +453,11 @@ void wrapUsdPrim() .def("GetInstances", &UsdPrim::GetInstances, return_value_policy()) + .def("MakeResolveTargetUpToEditTarget", + &UsdPrim::MakeResolveTargetUpToEditTarget) + .def("MakeResolveTargetStrongerThanEditTarget", + &UsdPrim::MakeResolveTargetStrongerThanEditTarget) + // Exposed only for testing and debugging. .def("_GetSourcePrimIndex", &Usd_PrimGetSourcePrimIndex, return_value_policy()) diff --git a/pxr/usd/usd/wrapPrimCompositionQuery.cpp b/pxr/usd/usd/wrapPrimCompositionQuery.cpp index a8ae431382..2dbc0104c2 100644 --- a/pxr/usd/usd/wrapPrimCompositionQuery.cpp +++ b/pxr/usd/usd/wrapPrimCompositionQuery.cpp @@ -23,6 +23,7 @@ // #include "pxr/pxr.h" #include "pxr/usd/usd/primCompositionQuery.h" +#include "pxr/usd/usd/resolveTarget.h" #include "pxr/base/tf/pyResultConversions.h" @@ -70,20 +71,32 @@ _WrapGetIntroducingListEditor(const UsdPrimCompositionQueryArc &arc) void wrapUsdPrimCompositionQueryArc() { class_("CompositionArc", no_init) - .def("GetTargetNode", &UsdPrimCompositionQueryArc::GetTargetNode) - .def("GetIntroducingNode", &UsdPrimCompositionQueryArc::GetIntroducingNode) + .def("GetTargetNode", + &UsdPrimCompositionQueryArc::GetTargetNode) + .def("GetIntroducingNode", + &UsdPrimCompositionQueryArc::GetIntroducingNode) .def("GetTargetLayer", &UsdPrimCompositionQueryArc::GetTargetLayer) - .def("GetTargetPrimPath", &UsdPrimCompositionQueryArc::GetTargetPrimPath) - .def("GetIntroducingLayer", &UsdPrimCompositionQueryArc::GetIntroducingLayer) - .def("GetIntroducingPrimPath", &UsdPrimCompositionQueryArc::GetIntroducingPrimPath) + .def("GetTargetPrimPath", + &UsdPrimCompositionQueryArc::GetTargetPrimPath) + .def("GetIntroducingLayer", + &UsdPrimCompositionQueryArc::GetIntroducingLayer) + .def("GetIntroducingPrimPath", + &UsdPrimCompositionQueryArc::GetIntroducingPrimPath) .def("GetIntroducingListEditor", &_WrapGetIntroducingListEditor) .def("GetArcType", &UsdPrimCompositionQueryArc::GetArcType) .def("IsImplicit", &UsdPrimCompositionQueryArc::IsImplicit) .def("IsAncestral", &UsdPrimCompositionQueryArc::IsAncestral) .def("HasSpecs", &UsdPrimCompositionQueryArc::HasSpecs) - .def("IsIntroducedInRootLayerStack", &UsdPrimCompositionQueryArc::IsIntroducedInRootLayerStack) - .def("IsIntroducedInRootLayerPrimSpec", &UsdPrimCompositionQueryArc::IsIntroducedInRootLayerPrimSpec) - ; + .def("IsIntroducedInRootLayerStack", + &UsdPrimCompositionQueryArc::IsIntroducedInRootLayerStack) + .def("IsIntroducedInRootLayerPrimSpec", + &UsdPrimCompositionQueryArc::IsIntroducedInRootLayerPrimSpec) + .def("MakeResolveTargetUpTo", + &UsdPrimCompositionQueryArc::MakeResolveTargetUpTo, + arg("subLayer")=object()) + .def("MakeResolveTargetStrongerThan", + &UsdPrimCompositionQueryArc::MakeResolveTargetStrongerThan, + arg("subLayer")=object()) ; } void wrapUsdPrimCompositionQuery() diff --git a/pxr/usd/usd/wrapResolveTarget.cpp b/pxr/usd/usd/wrapResolveTarget.cpp new file mode 100644 index 0000000000..4e94e9d4d7 --- /dev/null +++ b/pxr/usd/usd/wrapResolveTarget.cpp @@ -0,0 +1,45 @@ +// +// Copyright 2022 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +#include "pxr/pxr.h" +#include "pxr/usd/usd/resolveTarget.h" + +#include + +using namespace boost::python; + +PXR_NAMESPACE_USING_DIRECTIVE + +void wrapUsdResolveTarget() +{ + class_("ResolveTarget") + .def(init<>()) + .def("GetPrimIndex", &UsdResolveTarget::GetPrimIndex, + return_value_policy()) + .def("GetStartNode", &UsdResolveTarget::GetStartNode) + .def("GetStartLayer", &UsdResolveTarget::GetStartLayer) + .def("GetStopNode", &UsdResolveTarget::GetStopNode) + .def("GetStopLayer", &UsdResolveTarget::GetStopLayer) + .def("IsNull", &UsdResolveTarget::IsNull) + ; +}