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