1 //
2 // Copyright 2019 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 
25 #include "pxr/pxr.h"
26 #include "pxr/usd/pcp/dynamicFileFormatContext.h"
27 #include "pxr/usd/pcp/layerStack.h"
28 #include "pxr/usd/pcp/node_Iterator.h"
29 #include "pxr/usd/pcp/primIndex_StackFrame.h"
30 #include "pxr/usd/sdf/layer.h"
31 #include "pxr/base/vt/value.h"
32 
33 PXR_NAMESPACE_OPEN_SCOPE
34 
35 namespace {
36 
37 // Helper class for composing a field value from the context's inputs.
38 class _ComposeValueHelper
39 {
40 public:
41     // Templated static function is the only public interface. ComposeFunc is
42     // expected to be a function of type void (VtValue &&)
43     template <typename ComposeFunc>
44     static
ComposeValue(const PcpNodeRef & parentNode,PcpPrimIndex_StackFrame * previousFrame,const TfToken & fieldName,bool strongestOpinionOnly,const ComposeFunc & composeFunc)45     bool ComposeValue(
46         const PcpNodeRef &parentNode,
47         PcpPrimIndex_StackFrame *previousFrame,
48         const TfToken &fieldName,
49         bool strongestOpinionOnly,
50         const ComposeFunc &composeFunc)
51     {
52         // Create the a new composer with the context state.
53         _ComposeValueHelper composer(
54             parentNode, previousFrame, fieldName, strongestOpinionOnly);
55         // Initiate composition using the compose function and return if
56         // a value was found.
57         composer._ComposeOpinionFromAncestors(composeFunc);
58         return composer._foundValue;
59     }
60 
61 private:
_ComposeValueHelper(const PcpNodeRef & parentNode,PcpPrimIndex_StackFrame * & previousStackFrame,const TfToken & fieldName,bool strongestOpinionOnly)62     _ComposeValueHelper(
63         const PcpNodeRef &parentNode,
64         PcpPrimIndex_StackFrame *&previousStackFrame,
65         const TfToken &fieldName,
66         bool strongestOpinionOnly)
67         : _iterator(parentNode, previousStackFrame)
68         , _fieldName(fieldName)
69         , _strongestOpinionOnly(strongestOpinionOnly)
70     {
71     }
72 
73     // Composes the values from the node and its subtree. Return true if
74     // composition should stop.
75     template <typename ComposeFunc>
_ComposeOpinionInSubtree(const PcpNodeRef & node,const ComposeFunc & composeFunc)76     bool _ComposeOpinionInSubtree(const PcpNodeRef &node,
77                                   const ComposeFunc &composeFunc)
78     {
79         // Search the node's layer stack in strength order for the field on
80         // the spec.
81         for (const SdfLayerHandle &layer : node.GetLayerStack()->GetLayers()) {
82             VtValue value;
83             if (layer->HasField(node.GetPath(), _fieldName, &value)) {
84                 // Process the value and mark found
85                 composeFunc(std::move(value));
86                 _foundValue = true;
87                 // Stop if we only need the strongest opinion.
88                 if (_strongestOpinionOnly) {
89                     return true;
90                 }
91             }
92         }
93 
94         TF_FOR_ALL(childNode, Pcp_GetChildrenRange(node)) {
95             if (_ComposeOpinionInSubtree(*childNode, composeFunc)) {
96                 return true;
97             }
98         }
99 
100         return false;
101     };
102 
103     // Recursively composes opinions from ancestors of the parent node and
104     // their subtrees in strength order. Returns true if composition should
105     // stop.
106     template <typename ComposeFunc>
_ComposeOpinionFromAncestors(const ComposeFunc & composeFunc)107     bool _ComposeOpinionFromAncestors(const ComposeFunc &composeFunc)
108     {
109         PcpNodeRef currentNode = _iterator.node;
110 
111         // Try parent node.
112         _iterator.Next();
113         if (_iterator.node) {
114             // Recurse on parent node's ancestors.
115             if (_ComposeOpinionFromAncestors(composeFunc)) {
116                 return true;
117             }
118         }
119 
120         // Otherwise compose from the current node and it subtrees.
121         if (_ComposeOpinionInSubtree(currentNode, composeFunc)) {
122             return true;
123         }
124         return false;
125     };
126 
127     // State during value composition.
128     PcpPrimIndex_StackFrameIterator _iterator;
129     const TfToken &_fieldName;
130     bool _strongestOpinionOnly;
131     bool _foundValue {false};
132 };
133 
134 } // anonymous namespace
135 
PcpDynamicFileFormatContext(const PcpNodeRef & parentNode,PcpPrimIndex_StackFrame * previousStackFrame,TfToken::Set * composedFieldNames)136 PcpDynamicFileFormatContext::PcpDynamicFileFormatContext(
137     const PcpNodeRef &parentNode,
138     PcpPrimIndex_StackFrame *previousStackFrame,
139     TfToken::Set *composedFieldNames)
140     : _parentNode(parentNode)
141     , _previousStackFrame(previousStackFrame)
142     , _composedFieldNames(composedFieldNames)
143 {
144 }
145 
146 bool
_IsAllowedFieldForArguments(const TfToken & field,bool * fieldValueIsDictionary) const147 PcpDynamicFileFormatContext::_IsAllowedFieldForArguments(
148     const TfToken &field, bool *fieldValueIsDictionary) const
149 {
150     // We're starting off by restricting the allowed fields to be only fields
151     // defined by plugins. We may ease this in the future to allow certain
152     // builtin fields as well but there will need to be some updates to
153     // change management to handle these correctly.
154     const SdfSchemaBase &schema =
155         _parentNode.GetLayerStack()->GetIdentifier().rootLayer->GetSchema();
156     const SdfSchema::FieldDefinition* fieldDef =
157         schema.GetFieldDefinition(field);
158     if (!(fieldDef && fieldDef->IsPlugin())) {
159         TF_CODING_ERROR("Field %s is not a plugin field and is not supported "
160                         "for composing dynamic file format arguments",
161                         field.GetText());
162         return false;
163     }
164 
165     if (fieldValueIsDictionary) {
166         *fieldValueIsDictionary =
167             fieldDef->GetFallbackValue().IsHolding<VtDictionary>();
168     }
169 
170     return true;
171 }
172 
173 bool
ComposeValue(const TfToken & field,VtValue * value) const174 PcpDynamicFileFormatContext::ComposeValue(
175     const TfToken &field, VtValue *value) const
176 {
177     bool fieldIsDictValued = false;
178     if (!_IsAllowedFieldForArguments(field, &fieldIsDictValued)) {
179         return false;
180     }
181 
182     // Update the cached field names for dependency tracking.
183     if (_composedFieldNames) {
184         _composedFieldNames->insert(field);
185     }
186 
187     // If the field is a dictionary, compose the dictionary's key values from
188     // strongest to weakest opinions.
189     if (fieldIsDictValued) {
190         VtDictionary composedDict;
191         if (_ComposeValueHelper::ComposeValue(_parentNode, _previousStackFrame,
192                 field, /*findStrongestOnly = */ false,
193                 [&composedDict](VtValue &&val){
194                     if (val.IsHolding<VtDictionary>()) {
195                         VtDictionaryOverRecursive(
196                             &composedDict, val.UncheckedGet<VtDictionary>());
197                     } else {
198                         TF_CODING_ERROR("Expected value to contain VtDictionary");
199                     }
200                 })
201             ) {
202             // Output the composed dictionary only if we found a value for the
203             // field.
204             value->Swap(composedDict);
205             return true;
206         }
207         return false;
208     } else {
209         // For all other value type we compose by just grabbing the strongest
210         // opinion if it exists.
211         return _ComposeValueHelper::ComposeValue(_parentNode,
212             _previousStackFrame, field, /*findStrongestOnly = */ true,
213             [&value](VtValue &&val){
214                 // Take advantage of VtValue's move assignment
215                 // operator.
216                 *value = std::move(val);
217             });
218     }
219 }
220 
221 bool
ComposeValueStack(const TfToken & field,VtValueVector * values) const222 PcpDynamicFileFormatContext::ComposeValueStack(
223     const TfToken &field, VtValueVector *values) const
224 {
225     if (!_IsAllowedFieldForArguments(field)) {
226         return false;
227     }
228 
229     // Update the cached field names for dependency tracking.
230     if (_composedFieldNames) {
231         _composedFieldNames->insert(field);
232     }
233 
234     // For the value stack, just add all opinions we can find for the field
235     // in strength order.
236     return _ComposeValueHelper::ComposeValue(_parentNode, _previousStackFrame,
237         field, /*findStrongestOnly = */ false,
238         [&values](VtValue &&val){
239              // Take advantage of VtValue's move assignment
240              // operator.
241              values->emplace_back(std::move(val));
242         });
243 }
244 
245 // "Private" function for creating a PcpDynamicFileFormatContext; should only
246 // be used by prim indexing.
247 PcpDynamicFileFormatContext
Pcp_CreateDynamicFileFormatContext(const PcpNodeRef & parentNode,PcpPrimIndex_StackFrame * previousFrame,TfToken::Set * composedFieldNames)248 Pcp_CreateDynamicFileFormatContext(const PcpNodeRef &parentNode,
249                                    PcpPrimIndex_StackFrame *previousFrame,
250                                    TfToken::Set *composedFieldNames)
251 {
252     return PcpDynamicFileFormatContext(
253         parentNode, previousFrame, composedFieldNames);
254 }
255 
256 PXR_NAMESPACE_CLOSE_SCOPE
257