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) + ; +}