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 #ifndef PXR_USD_USD_LIST_EDIT_IMPL_H
25 #define PXR_USD_USD_LIST_EDIT_IMPL_H
26 
27 #include "pxr/pxr.h"
28 #include "pxr/usd/usd/api.h"
29 #include "pxr/usd/usd/common.h"
30 #include "pxr/usd/usd/prim.h"
31 #include "pxr/usd/usd/stage.h"
32 #include "pxr/usd/usd/valueUtils.h"
33 
34 PXR_NAMESPACE_OPEN_SCOPE
35 
36 // Non templated base class to namespace the overloading of _TranslatePath on
37 // the list item type.
38 class Usd_ListEditImplBase
39 {
40 protected:
41     // Generic path translation for the list edit types.
42     static bool
_TranslatePath(SdfPath * path,const UsdEditTarget & editTarget)43     _TranslatePath(SdfPath *path, const UsdEditTarget& editTarget)
44     {
45         if (path->IsEmpty()) {
46             TF_CODING_ERROR("Invalid empty path");
47             return false;
48         }
49 
50         // Root prim paths for all list edit types aren't expected to be
51         // mappable across non-local edit targets, so we can just use the given
52         // path as-is.
53         if (path->IsRootPrimPath()) {
54             return true;
55         }
56 
57         const SdfPath mappedPath = editTarget.MapToSpecPath(*path);
58         if (mappedPath.IsEmpty()) {
59             TF_CODING_ERROR(
60                 "Cannot map <%s> to current edit target.", path->GetText());
61             return false;
62         }
63 
64         // If the edit target points inside a variant, the mapped path may
65         // contain a variant selection. We need to strip this out, since
66         // paths for these purposes may not contain variant selections.
67         *path = mappedPath.StripAllVariantSelections();
68         return true;
69     }
70 
71     // Special path translation for references and payloads
72     template <typename RefOrPayloadType>
73     static bool
_TranslatePath(RefOrPayloadType * refOrPayload,const UsdEditTarget & editTarget)74     _TranslatePath(RefOrPayloadType* refOrPayload,
75                    const UsdEditTarget& editTarget)
76     {
77         // We do not map prim paths across the edit target for non-internal
78         // references or payloads, as these paths are supposed to be in the
79         // namespace of the layer stack.
80         if (!refOrPayload->GetAssetPath().empty()) {
81             return true;
82         }
83 
84         // The generic _TranslatePath errors for empty paths as those are
85         // invalid for specializes and inherits. However an empty prim path
86         // is find for references and payloads.
87         SdfPath path = refOrPayload->GetPrimPath();
88         if (path.IsEmpty()) {
89             return true;
90         }
91 
92         // Translate the path and update the reference or payload.
93         if (!_TranslatePath(&path, editTarget)) {
94             return false;
95         }
96         refOrPayload->SetPrimPath(path);
97         return true;
98     }
99 };
100 
101 // Templated implementation of the edit operations provided by UsdRefereneces
102 // and UsdPayloads. Editing payloads and references is identical outside of
103 // their type.
104 template <class UsdListEditorType, class ListOpProxyType>
105 struct Usd_ListEditImpl : public Usd_ListEditImplBase
106 {
107     using ListOpValueType = typename ListOpProxyType::value_type;
108     using ListOpValueVector = typename ListOpProxyType::value_vector_type;
109 
AddUsd_ListEditImpl110     static bool Add(const UsdListEditorType &editor,
111                     const ListOpValueType& itemIn,
112                     UsdListPosition position)
113     {
114         const UsdPrim &prim = editor.GetPrim();
115 
116         if (!prim) {
117             TF_CODING_ERROR("Invalid prim");
118             return false;
119         }
120 
121         ListOpValueType item = itemIn;
122         if (!_TranslatePath(&item, prim.GetStage()->GetEditTarget())) {
123             return false;
124         }
125 
126         SdfChangeBlock block;
127         bool success = false;
128         {
129             TfErrorMark mark;
130             if (ListOpProxyType listEditor = _GetListEditor(prim)) {
131                 Usd_InsertListItem(listEditor, item, position);
132                 // mark *should* contain only errors from adding the item,
133                 // NOT any recomposition errors, because the
134                 // SdfChangeBlock handily defers composition till after we
135                 // leave this scope.
136                 success = mark.IsClean();
137             }
138         }
139         return success;
140     }
141 
RemoveUsd_ListEditImpl142     static bool Remove(const UsdListEditorType &editor,
143                        const ListOpValueType& itemIn)
144     {
145         const UsdPrim &prim = editor.GetPrim();
146 
147         if (!prim) {
148             TF_CODING_ERROR("Invalid prim");
149             return false;
150         }
151 
152         ListOpValueType item = itemIn;
153         if (!_TranslatePath(&item, prim.GetStage()->GetEditTarget())) {
154             return false;
155         }
156 
157         SdfChangeBlock block;
158         TfErrorMark mark;
159         bool success = false;
160 
161         if (ListOpProxyType listEditor = _GetListEditor(prim)) {
162             listEditor.Remove(item);
163             success = mark.IsClean();
164         }
165         mark.Clear();
166         return success;
167     }
168 
ClearUsd_ListEditImpl169     static bool Clear(const UsdListEditorType &editor)
170     {
171         const UsdPrim &prim = editor.GetPrim();
172 
173         if (!prim) {
174             TF_CODING_ERROR("Invalid prim");
175             return false;
176         }
177 
178         SdfChangeBlock block;
179         TfErrorMark mark;
180         bool success = false;
181 
182         if (ListOpProxyType listEditor = _GetListEditor(prim)) {
183             success = listEditor.ClearEdits() && mark.IsClean();
184         }
185         mark.Clear();
186         return success;
187     }
188 
SetUsd_ListEditImpl189     static bool Set(const UsdListEditorType &editor,
190                     const ListOpValueVector& itemsIn)
191     {
192         const UsdPrim &prim = editor.GetPrim();
193 
194         if (!prim) {
195             TF_CODING_ERROR("Invalid prim");
196             return false;
197         }
198 
199         const UsdEditTarget& editTarget = prim.GetStage()->GetEditTarget();
200 
201         TfErrorMark mark;
202 
203         ListOpValueVector items;
204         items.reserve(itemsIn.size());
205         for (ListOpValueType item : itemsIn) {
206             if (_TranslatePath(&item, editTarget)) {
207                 items.push_back(item);
208             }
209         }
210 
211         if (!mark.IsClean()) {
212             return false;
213         }
214 
215         SdfChangeBlock block;
216         if (ListOpProxyType listEditor = _GetListEditor(prim)) {
217             // There's a specific semantic meaning to setting the list op to
218             // an empty list which is to make the list explicitly
219             // empty. We have to handle this case specifically as setting the
220             // the list edit proxy's explicit items to an empty vector is a
221             // no-op when the list op is not currently explicit.
222             if (items.empty()) {
223                 listEditor.ClearEditsAndMakeExplicit();
224             } else {
225                 listEditor.GetExplicitItems() = items;
226             }
227         }
228 
229         bool success = mark.IsClean();
230         mark.Clear();
231         return success;
232     }
233 
234 private:
235     static ListOpProxyType
_GetListEditorUsd_ListEditImpl236     _GetListEditor(const UsdPrim &prim)
237     {
238         if (!TF_VERIFY(prim)) {
239             return ListOpProxyType();
240         }
241 
242         SdfPrimSpecHandle spec =
243             prim.GetStage()->_CreatePrimSpecForEditing(prim);
244 
245         if (!spec) {
246             return ListOpProxyType();
247         }
248 
249         return _GetListEditorForSpec(spec);
250     }
251 
252     // This is purposefully not defined here as this is needs to be specialized
253     // by UsdPayloads, UsdReferences, etc. The implementation is defined in
254     // their cpp files.
255     static ListOpProxyType
256     _GetListEditorForSpec(const SdfPrimSpecHandle &spec);
257 };
258 
259 PXR_NAMESPACE_CLOSE_SCOPE
260 
261 #endif // PXR_USD_USD_LIST_EDIT_IMPL_H
262