1 //
2 // Copyright 2016 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 /// \file usdUtils/dependencies.cpp
26 #include "pxr/pxr.h"
27 #include "pxr/usd/usdUtils/dependencies.h"
28 #include "pxr/usd/usdUtils/debugCodes.h"
29 
30 #include "pxr/usd/ar/packageUtils.h"
31 #include "pxr/usd/ar/resolver.h"
32 #include "pxr/usd/sdf/assetPath.h"
33 #include "pxr/usd/sdf/fileFormat.h"
34 #include "pxr/usd/sdf/layerUtils.h"
35 #include "pxr/usd/sdf/primSpec.h"
36 #include "pxr/usd/sdf/reference.h"
37 #include "pxr/usd/sdf/types.h"
38 #include "pxr/usd/sdf/variantSetSpec.h"
39 #include "pxr/usd/sdf/variantSpec.h"
40 #include "pxr/usd/usd/clipsAPI.h"
41 #include "pxr/usd/usd/stage.h"
42 #include "pxr/usd/usd/tokens.h"
43 #include "pxr/usd/usd/usdFileFormat.h"
44 #include "pxr/usd/usd/usdcFileFormat.h"
45 #include "pxr/usd/usd/zipFile.h"
46 
47 #include "pxr/base/arch/fileSystem.h"
48 #include "pxr/base/tf/fileUtils.h"
49 #include "pxr/base/tf/pathUtils.h"
50 #include "pxr/base/tf/stringUtils.h"
51 #include "pxr/base/trace/trace.h"
52 
53 #include <stack>
54 #include <vector>
55 
56 #include <boost/get_pointer.hpp>
57 
58 PXR_NAMESPACE_OPEN_SCOPE
59 
60 using std::vector;
61 using std::string;
62 
63 namespace {
64 
65 // Enum class representing the type of dependency.
66 enum class _DepType {
67     Reference,
68     Sublayer,
69     Payload
70 };
71 
72 // Enum class representing the external reference types that must be included
73 // in the search for external dependencies.
74 enum class _ReferenceTypesToInclude {
75     // Include only references that affect composition.
76     CompositionOnly,
77 
78     // Include all external references including asset-valued attributes
79     // and non-composition metadata containing SdfAssetPath values.
80     All
81 };
82 
83 class _FileAnalyzer {
84 public:
85     // The asset remapping function's signature.
86     // It takes a given asset path, the layer it was found in and a boolean
87     // value. The bool is used to indicate whether a dependency must be skipped
88     // on the given asset path, which is useful to skip for things like
89     // templated clip paths that cannot be resolved directly without additional
90     // processing.
91     // The function returns the corresponding remapped path.
92     //
93     // The layer is used to resolve the asset path in cases where the given
94     // asset path is a search path or a relative path.
95     using RemapAssetPathFunc = std::function<std::string
96             (const std::string &assetPath,
97              const SdfLayerRefPtr &layer,
98              bool skipDependency)>;
99 
100     // Takes the asset path and the type of dependency it is and does some
101     // arbitrary processing (like enumerating dependencies).
102     using ProcessAssetPathFunc = std::function<void
103             (const std::string &assetPath,
104              const _DepType &depType)>;
105 
106     // Opens the file at \p resolvedFilePath and analyzes its external
107     // dependencies.
108     //
109     // For each dependency that is detected, the provided (optional) callback
110     // functions are invoked.
111     //
112     // \p processPathFunc is invoked first with the raw (un-remapped) path.
113     // Then \p remapPathFunc is invoked.
_FileAnalyzer(const std::string & resolvedFilePath,_ReferenceTypesToInclude refTypesToInclude=_ReferenceTypesToInclude::All,const RemapAssetPathFunc & remapPathFunc={},const ProcessAssetPathFunc & processPathFunc={})114     _FileAnalyzer(const std::string &resolvedFilePath,
115                   _ReferenceTypesToInclude refTypesToInclude=
116                         _ReferenceTypesToInclude::All,
117                   const RemapAssetPathFunc &remapPathFunc={},
118                   const ProcessAssetPathFunc &processPathFunc={}) :
119         _filePath(resolvedFilePath),
120         _refTypesToInclude(refTypesToInclude),
121         _remapPathFunc(remapPathFunc),
122         _processPathFunc(processPathFunc)
123     {
124         // If this file can be opened on a USD stage or referenced into a USD
125         // stage via composition, then analyze the file, collect & update all
126         // references. If not, return early.
127         if (!UsdStage::IsSupportedFile(_filePath)) {
128             return;
129         }
130 
131         TRACE_FUNCTION();
132 
133         _layer = SdfLayer::FindOrOpen(_filePath);
134         if (!_layer) {
135             TF_WARN("Unable to open layer at path @%s@.", _filePath.c_str());
136             return;
137         }
138 
139         _AnalyzeDependencies();
140     }
141 
142     // overload version of the above constructor that takes a \c layer instead
143     // of a filePath.
_FileAnalyzer(const SdfLayerHandle & layer,_ReferenceTypesToInclude refTypesToInclude=_ReferenceTypesToInclude::All,const RemapAssetPathFunc & remapPathFunc={},const ProcessAssetPathFunc & processPathFunc={})144     _FileAnalyzer(const SdfLayerHandle& layer,
145                   _ReferenceTypesToInclude refTypesToInclude=
146                         _ReferenceTypesToInclude::All,
147                   const RemapAssetPathFunc &remapPathFunc={},
148                   const ProcessAssetPathFunc &processPathFunc={}) :
149         _layer(layer),
150         _refTypesToInclude(refTypesToInclude),
151         _remapPathFunc(remapPathFunc),
152         _processPathFunc(processPathFunc)
153     {
154         if (!_layer) {
155             return;
156         }
157 
158         _filePath = _layer->GetRealPath();
159 
160         _AnalyzeDependencies();
161     }
162 
163     // Returns the path to the file on disk that is being analyzed.
GetFilePath() const164     const std::string &GetFilePath() const {
165         return _filePath;
166     }
167 
168     // Returns the SdfLayer associated with the file being analyzed.
GetLayer() const169     const SdfLayerRefPtr &GetLayer() const {
170         return _layer;
171     }
172 
173 private:
174     // Open the layer, updates references to point to relative or search paths
175     // and accumulates all references.
176     void _AnalyzeDependencies();
177 
178     // This adds the given raw reference path to the list of all referenced
179     // paths. It also returns the remapped reference path, so client code
180     // can update the source reference to point to the remapped path.
181     std::string _ProcessDependency(const std::string &rawRefPath,
182                                    const _DepType &depType);
183 
184     // Processes any sublayers in the SdfLayer associated with the file.
185     void _ProcessSublayers();
186 
187     // Processes all payloads on the given primSpec.
188     void _ProcessPayloads(const SdfPrimSpecHandle &primSpec);
189 
190     // Processes prim metadata.
191     void _ProcessMetadata(const SdfPrimSpecHandle &primSpec);
192 
193     // Processes metadata on properties.
194     void _ProcessProperties(const SdfPrimSpecHandle &primSpec);
195 
196     // Processes all references on the given primSpec.
197     void _ProcessReferences(const SdfPrimSpecHandle &primSpec);
198 
199     // Returns the given VtValue with any asset paths remapped to point to
200     // destination-relative path.
201     VtValue _UpdateAssetValue(const VtValue &val);
202 
203     // Callback function that's passed into SdfPayloadsProxy::ModifyItemEdits()
204     // or SdfReferencesProxy::ModifyItemEdits() to update all payloads or
205     // references.
206     template <class RefOrPayloadType, _DepType DEP_TYPE>
207     boost::optional<RefOrPayloadType> _RemapRefOrPayload(
208         const RefOrPayloadType &refOrPayload);
209 
210     // Resolved path to the file.
211     std::string _filePath;
212 
213     // SdfLayer corresponding to the file. This will be null for non-layer
214     // files.
215     SdfLayerRefPtr _layer;
216 
217     // The types of references to include in the processing.
218     // If set to _ReferenceTypesToInclude::CompositionOnly,
219     // non-composition related asset references (eg. property values, property
220     // metadata and non-composition prim metadata) are ignored.
221     _ReferenceTypesToInclude _refTypesToInclude;
222 
223     // Remap and process path callback functions.
224     RemapAssetPathFunc _remapPathFunc;
225     ProcessAssetPathFunc _processPathFunc;
226 };
227 
228 std::string
_ProcessDependency(const std::string & rawRefPath,const _DepType & depType)229 _FileAnalyzer::_ProcessDependency(const std::string &rawRefPath,
230                                   const _DepType &depType)
231 {
232     if (_processPathFunc) {
233         _processPathFunc(rawRefPath, depType);
234     }
235 
236     if (_remapPathFunc) {
237         return _remapPathFunc(rawRefPath, GetLayer(), /*skipDependency*/ false);
238     }
239 
240     // Return the raw reference path if there's no asset path remapping
241     // function.
242     return rawRefPath;
243 }
244 
245 VtValue
_UpdateAssetValue(const VtValue & val)246 _FileAnalyzer::_UpdateAssetValue(const VtValue &val)
247 {
248     if (val.IsHolding<SdfAssetPath>()) {
249         auto assetPath = val.UncheckedGet<SdfAssetPath>();
250         std::string rawAssetPath = assetPath.GetAssetPath();
251         if (!rawAssetPath.empty()) {
252             return VtValue(SdfAssetPath(
253                     _ProcessDependency(rawAssetPath, _DepType::Reference)));
254         }
255     } else if (val.IsHolding<VtArray<SdfAssetPath>>()) {
256         VtArray<SdfAssetPath> updatedVal;
257         for (const SdfAssetPath& assetPath :
258             val.UncheckedGet< VtArray<SdfAssetPath> >()) {
259             std::string rawAssetPath = assetPath.GetAssetPath();
260             if (!rawAssetPath.empty()) {
261                 updatedVal.push_back(SdfAssetPath(
262                         _ProcessDependency(rawAssetPath, _DepType::Reference)));
263             } else {
264                 // Retain empty paths in the array.
265                 updatedVal.push_back(assetPath);
266             }
267         }
268         return VtValue(updatedVal);
269     }
270     else if (val.IsHolding<VtDictionary>()) {
271         VtDictionary updatedVal;
272         for (const auto& p : val.UncheckedGet<VtDictionary>()) {
273             updatedVal[p.first] = _UpdateAssetValue(p.second);
274         }
275         return VtValue(updatedVal);
276     }
277 
278     return val;
279 }
280 
281 void
_ProcessSublayers()282 _FileAnalyzer::_ProcessSublayers()
283 {
284     const std::vector<std::string> subLayerPaths = _layer->GetSubLayerPaths();
285 
286     if (_remapPathFunc) {
287         std::vector<std::string> newSubLayerPaths;
288         newSubLayerPaths.reserve(subLayerPaths.size());
289         for (auto &subLayer: subLayerPaths) {
290             newSubLayerPaths.push_back(
291                 _ProcessDependency(subLayer, _DepType::Sublayer));
292         }
293         _layer->SetSubLayerPaths(newSubLayerPaths);
294     } else {
295         for (auto &subLayer: subLayerPaths) {
296             _ProcessDependency(subLayer, _DepType::Sublayer);
297         }
298     }
299 }
300 
301 template <class RefOrPayloadType, _DepType DEP_TYPE>
302 boost::optional<RefOrPayloadType>
_RemapRefOrPayload(const RefOrPayloadType & refOrPayload)303 _FileAnalyzer::_RemapRefOrPayload(const RefOrPayloadType &refOrPayload)
304 {
305     // If this is a local (or self) reference or payload, there's no asset path
306     // to update.
307     if (refOrPayload.GetAssetPath().empty()) {
308         return refOrPayload;
309     }
310 
311     std::string remappedPath =
312         _ProcessDependency(refOrPayload.GetAssetPath(), DEP_TYPE);
313     // If the path was not remapped to a different path, then return the
314     // incoming payload unmodifed.
315     if (remappedPath == refOrPayload.GetAssetPath())
316         return refOrPayload;
317 
318     // The payload or reference path was remapped, hence construct a new
319     // SdfPayload or SdfReference object with the remapped path.
320     RefOrPayloadType remappedRefOrPayload = refOrPayload;
321     remappedRefOrPayload.SetAssetPath(remappedPath);
322     return remappedRefOrPayload;
323 }
324 
325 void
_ProcessPayloads(const SdfPrimSpecHandle & primSpec)326 _FileAnalyzer::_ProcessPayloads(const SdfPrimSpecHandle &primSpec)
327 {
328     if (_remapPathFunc) {
329         primSpec->GetPayloadList().ModifyItemEdits(std::bind(
330             &_FileAnalyzer::_RemapRefOrPayload<SdfPayload, _DepType::Payload>,
331             this, std::placeholders::_1));
332     } else {
333         for (SdfPayload const& payload:
334              primSpec->GetPayloadList().GetAddedOrExplicitItems()) {
335             _ProcessDependency(payload.GetAssetPath(), _DepType::Payload);
336         }
337     }
338 }
339 
340 void
_ProcessProperties(const SdfPrimSpecHandle & primSpec)341 _FileAnalyzer::_ProcessProperties(const SdfPrimSpecHandle &primSpec)
342 {
343     // Include external references in property values and metadata only if
344     // the client is interested in all reference types. i.e. return early if
345     // _refTypesToInclude is CompositionOnly.
346     if (_refTypesToInclude == _ReferenceTypesToInclude::CompositionOnly)
347         return;
348 
349     // XXX:2016-04-14 Note that we use the field access API
350     // here rather than calling GetAttributes, as creating specs for
351     // large numbers of attributes, most of which are *not* asset
352     // path-valued and therefore not useful here, is expensive.
353     //
354     const VtValue propertyNames =
355         primSpec->GetField(SdfChildrenKeys->PropertyChildren);
356 
357     if (propertyNames.IsHolding<vector<TfToken>>()) {
358         for (const auto& name :
359                 propertyNames.UncheckedGet<vector<TfToken>>()) {
360             // For every property
361             // Build an SdfPath to the property
362             const SdfPath path = primSpec->GetPath().AppendProperty(name);
363 
364             // Check property metadata
365             for (const TfToken& infoKey : _layer->ListFields(path)) {
366                 if (infoKey != SdfFieldKeys->Default &&
367                     infoKey != SdfFieldKeys->TimeSamples) {
368 
369                     VtValue value = _layer->GetField(path, infoKey);
370                     VtValue updatedValue = _UpdateAssetValue(value);
371                     if (_remapPathFunc && value != updatedValue) {
372                         _layer->SetField(path, infoKey, updatedValue);
373                     }
374                 }
375             }
376 
377             // Check property existence
378             const VtValue vtTypeName =
379                 _layer->GetField(path, SdfFieldKeys->TypeName);
380             if (!vtTypeName.IsHolding<TfToken>())
381                 continue;
382 
383             const TfToken typeName =
384                 vtTypeName.UncheckedGet<TfToken>();
385             if (typeName == SdfValueTypeNames->Asset ||
386                 typeName == SdfValueTypeNames->AssetArray) {
387 
388                 // Check default value
389                 VtValue defValue = _layer->GetField(path,
390                         SdfFieldKeys->Default);
391                 VtValue updatedDefValue = _UpdateAssetValue(defValue);
392                 if (_remapPathFunc && defValue != updatedDefValue) {
393                     _layer->SetField(path, SdfFieldKeys->Default,
394                             updatedDefValue);
395                 }
396 
397                 // Check timeSample values
398                 for (double t : _layer->ListTimeSamplesForPath(path)) {
399                     VtValue timeSampleVal;
400                     if (_layer->QueryTimeSample(path,
401                         t, &timeSampleVal)) {
402 
403                         VtValue updatedTimeSampleVal =
404                             _UpdateAssetValue(timeSampleVal);
405                         if (_remapPathFunc &&
406                             timeSampleVal != updatedTimeSampleVal) {
407                             _layer->SetTimeSample(path, t,
408                                     updatedTimeSampleVal);
409                         }
410                     }
411                 }
412             }
413         }
414     }
415 }
416 
417 void
_ProcessMetadata(const SdfPrimSpecHandle & primSpec)418 _FileAnalyzer::_ProcessMetadata(const SdfPrimSpecHandle &primSpec)
419 {
420     if (_refTypesToInclude == _ReferenceTypesToInclude::All) {
421         for (const TfToken& infoKey : primSpec->GetMetaDataInfoKeys()) {
422             VtValue value = primSpec->GetInfo(infoKey);
423             VtValue updatedValue = _UpdateAssetValue(value);
424             if (_remapPathFunc && value != updatedValue) {
425                 primSpec->SetInfo(infoKey, updatedValue);
426             }
427         }
428     }
429 
430     // Process clips["templateAssetPath"], which is a string value
431     // containing one or more #'s. See
432     // UsdClipsAPI::GetClipTemplateAssetPath for details.
433     VtValue clipsValue = primSpec->GetInfo(UsdTokens->clips);
434     if (!clipsValue.IsEmpty() && clipsValue.IsHolding<VtDictionary>()) {
435         const VtDictionary origClipsDict =
436                 clipsValue.UncheckedGet<VtDictionary>();
437 
438         // Create a copy of the clips dictionary, as we may have to modify it.
439         VtDictionary clipsDict = origClipsDict;
440         for (auto &clipSetNameAndDict : clipsDict) {
441             if (clipSetNameAndDict.second.IsHolding<VtDictionary>()) {
442                 VtDictionary clipDict =
443                     clipSetNameAndDict.second.UncheckedGet<VtDictionary>();
444 
445                 if (VtDictionaryIsHolding<std::string>(clipDict,
446                         UsdClipsAPIInfoKeys->templateAssetPath
447                             .GetString())) {
448                     const std::string &templateAssetPath =
449                             VtDictionaryGet<std::string>(clipDict,
450                                 UsdClipsAPIInfoKeys->templateAssetPath
451                                     .GetString());
452 
453                     if (templateAssetPath.empty()) {
454                         continue;
455                     }
456 
457                     // Remap templateAssetPath if there's a remap function and
458                     // update the clip dictionary.
459                     // This retains the #s in the templateAssetPath?
460                     if (_remapPathFunc) {
461                         // Not adding a dependency on the templated asset path
462                         // since it can't be resolved by the resolver.
463                         clipDict[UsdClipsAPIInfoKeys->templateAssetPath] =
464                             VtValue(_remapPathFunc(templateAssetPath,
465                                                    GetLayer(),
466                                                    /*skipDependency*/ true));
467                         clipsDict[clipSetNameAndDict.first] = VtValue(clipDict);
468                     }
469 
470                     // Compute the resolved location of the clips
471                     // directory, so we can do a TfGlob for the pattern.
472                     // This contains a '/' in the end.
473                     const std::string clipsDir = TfGetPathName(
474                             templateAssetPath);
475                     // Resolve clipsDir relative to this layer.
476                     if (clipsDir.empty()) {
477                         TF_WARN("Invalid template asset path '%s'.",
478                             templateAssetPath.c_str());
479                         continue;
480                     }
481                     const std::string clipsDirAssetPath =
482                         SdfComputeAssetPathRelativeToLayer(_layer, clipsDir);
483 
484                     // We don't attempt to resolve the clips directory asset
485                     // path, since Ar does not support directory-path
486                     // resolution.
487                     if (!TfIsDir(clipsDirAssetPath)) {
488                         TF_WARN("Clips directory '%s' is not a valid directory "
489                             "on the filesystem.", clipsDirAssetPath.c_str());
490                         continue;
491                     }
492 
493                     std::string clipsBaseName = TfGetBaseName(
494                             templateAssetPath);
495                     std::string globPattern = TfStringCatPaths(
496                             clipsDirAssetPath,
497                             TfStringReplace(clipsBaseName, "#", "*"));
498                     const std::vector<std::string> clipAssetRefs =
499                         TfGlob(globPattern);
500                     for (auto &clipAsset : clipAssetRefs) {
501                         // Reconstruct the raw, unresolved clip reference, for
502                         // which the dependency must be processed.
503                         //
504                         // clipsDir contains a '/' in the end, but
505                         // clipsDirAssetPath does not. Hence, add a '/' to
506                         // clipsDirAssetPath before doing the replace.
507                         std::string rawClipRef = TfStringReplace(
508                                 clipAsset, clipsDirAssetPath + '/', clipsDir);
509                         _ProcessDependency(rawClipRef, _DepType::Reference);
510                     }
511                 }
512             }
513         }
514 
515         // Update the clips dictionary only if it has been modified.
516         if (_remapPathFunc && clipsDict != origClipsDict) {
517             primSpec->SetInfo(UsdTokens->clips, VtValue(clipsDict));
518         }
519     }
520 }
521 
522 void
_ProcessReferences(const SdfPrimSpecHandle & primSpec)523 _FileAnalyzer::_ProcessReferences(const SdfPrimSpecHandle &primSpec)
524 {
525     if (_remapPathFunc) {
526         primSpec->GetReferenceList().ModifyItemEdits(std::bind(
527             &_FileAnalyzer::_RemapRefOrPayload<SdfReference,
528             _DepType::Reference>, this, std::placeholders::_1));
529     } else {
530         for (SdfReference const& reference:
531             primSpec->GetReferenceList().GetAddedOrExplicitItems()) {
532             _ProcessDependency(reference.GetAssetPath(), _DepType::Reference);
533         }
534     }
535 }
536 
537 void
_AnalyzeDependencies()538 _FileAnalyzer::_AnalyzeDependencies()
539 {
540     TRACE_FUNCTION();
541 
542     _ProcessSublayers();
543 
544     std::stack<SdfPrimSpecHandle> dfs;
545     dfs.push(_layer->GetPseudoRoot());
546 
547     while (!dfs.empty()) {
548         SdfPrimSpecHandle curr = dfs.top();
549         dfs.pop();
550 
551         // Metadata is processed even on the pseudoroot, which ensures
552         // we process layer metadata properly.
553         _ProcessMetadata(curr);
554         if (curr != _layer->GetPseudoRoot()) {
555             _ProcessPayloads(curr);
556             _ProcessProperties(curr);
557             _ProcessReferences(curr);
558         }
559 
560         // variants "children"
561         for (const SdfVariantSetsProxy::value_type& p :
562             curr->GetVariantSets()) {
563             for (const SdfVariantSpecHandle& variantSpec :
564                 p.second->GetVariantList()) {
565                 dfs.push(variantSpec->GetPrimSpec());
566             }
567         }
568 
569         // children
570         for (const SdfPrimSpecHandle& child : curr->GetNameChildren()) {
571             dfs.push(child);
572         }
573     }
574 }
575 
576 class _AssetLocalizer {
577 public:
578     using LayerAndDestPath = std::pair<SdfLayerRefPtr, std::string>;
579     using SrcPathAndDestPath = std::pair<std::string, std::string>;
580     using DestFilePathAndAnalyzer = std::pair<std::string, _FileAnalyzer>;
581     using LayerDependenciesMap = std::unordered_map<SdfLayerRefPtr,
582             std::vector<std::string>, TfHash>;
583 
584     // Computes the given asset's dependencies recursively and determines
585     // the information needed to localize the asset.
586     // If \p destDir is empty, none of the asset layers are modified, allowing
587     // this class to be used purely as a recursive dependency finder.
588     // \p firstLayerName if non-empty, holds desired the name of the root layer
589     // in the localized asset.
590     //
591     // If \p origRootFilePath is non-empty, it points to the original root layer
592     // of which \p assetPath is a flattened representation. This is by
593     // UsdUtilsCreateNewARKitUsdzPackage(), to point to the original
594     // (unflattened) asset with external dependencies.
595     //
596     // \p dependenciesToSkip lists an optional set of dependencies that must be
597     // skipped in the created package. This list must contain fully resolved
598     // asset paths that must be skipped in the created package. It cannot
599     // contain the resolved root \p assetPath value itself. If a dependency
600     // is skipped because it exists in the \p dependenciesToSkip list, none of
601     // the transitive dependencies referenced by the skipped dependency are
602     // processed and may be missing in the created package.
_AssetLocalizer(const SdfAssetPath & assetPath,const std::string & destDir,const std::string & firstLayerName=std::string (),const std::string & origRootFilePath=std::string (),const std::vector<std::string> & dependenciesToSkip=std::vector<std::string> ())603     _AssetLocalizer(const SdfAssetPath &assetPath, const std::string &destDir,
604                     const std::string &firstLayerName=std::string(),
605                     const std::string &origRootFilePath=std::string(),
606                     const std::vector<std::string>
607                         &dependenciesToSkip=std::vector<std::string>())
608     {
609         _DirectoryRemapper dirRemapper;
610 
611         auto &layerDependenciesMap = _layerDependenciesMap;
612 
613         auto &resolver = ArGetResolver();
614 
615         std::string rootFilePath = resolver.Resolve(assetPath.GetAssetPath());
616 
617         // Ensure that the resolved path is not empty.
618         if (rootFilePath.empty()) {
619             return;
620         }
621 
622 #if AR_VERSION == 1
623         // ... and can be localized to a physical location on disk.
624         if (!ArGetResolver().FetchToLocalResolvedPath(assetPath.GetAssetPath(),
625                     rootFilePath)) {
626             return;
627         }
628 #endif
629 
630         const auto remapAssetPathFunc =
631             [&layerDependenciesMap, &dirRemapper, &destDir, &rootFilePath,
632              &origRootFilePath, &firstLayerName](
633                 const std::string &ap,
634                 const SdfLayerRefPtr &layer,
635                 bool skipDependency) {
636             if (!skipDependency) {
637                 layerDependenciesMap[layer].push_back(ap);
638             }
639 
640             // If destination directory is an empty string, skip any remapping.
641             // of asset paths.
642             if (destDir.empty()) {
643                 return ap;
644             }
645 
646             return _RemapAssetPath(ap, layer,
647                     origRootFilePath, rootFilePath, firstLayerName,
648                     &dirRemapper, /* isRelativePath */ nullptr);
649         };
650 
651         // Set of all seen files. We maintain this set to avoid redundant
652         // dependency analysis of already seen files.
653         std::unordered_set<std::string> seenFiles;
654 
655         std::stack<DestFilePathAndAnalyzer> filesToLocalize;
656         {
657             seenFiles.insert(rootFilePath);
658             std::string destFilePath = TfStringCatPaths(destDir,
659                     TfGetBaseName(rootFilePath));
660             filesToLocalize.emplace(destFilePath, _FileAnalyzer(rootFilePath,
661                     /*refTypesToInclude*/ _ReferenceTypesToInclude::All,
662                     remapAssetPathFunc));
663         }
664 
665         while (!filesToLocalize.empty()) {
666             // Copying data here since we're about to pop.
667             const DestFilePathAndAnalyzer destFilePathAndAnalyzer =
668                 filesToLocalize.top();
669             filesToLocalize.pop();
670 
671             auto &destFilePath = destFilePathAndAnalyzer.first;
672             auto &fileAnalyzer = destFilePathAndAnalyzer.second;
673 
674             if (!fileAnalyzer.GetLayer()) {
675                 _fileCopyMap.emplace_back(fileAnalyzer.GetFilePath(),
676                                           destFilePath);
677                 continue;
678             }
679 
680             _layerExportMap.emplace_back(fileAnalyzer.GetLayer(),
681                                          destFilePath);
682 
683             const auto &layerDepIt = layerDependenciesMap.find(
684                     fileAnalyzer.GetLayer());
685 
686             if (layerDepIt == _layerDependenciesMap.end()) {
687                 // The layer has no external dependencies.
688                 continue;
689             }
690 
691             for (std::string ref : layerDepIt->second) {
692                 // If this is a package-relative path, then simply copy the
693                 // package over.
694                 // Note: recursive search for dependencies ends here.
695                 // This is because we don't want to be modifying packaged
696                 // assets during asset isolation or archival.
697                 // XXX: We may want to reconsider this approach in the future.
698                 if (ArIsPackageRelativePath(ref)) {
699                     ref = ArSplitPackageRelativePathOuter(ref).first;
700                 }
701 
702                 const std::string refAssetPath =
703                         SdfComputeAssetPathRelativeToLayer(
704                             fileAnalyzer.GetLayer(), ref);
705 
706                 std::string resolvedRefFilePath = resolver.Resolve(refAssetPath);
707 
708                 if (resolvedRefFilePath.empty()) {
709                     TF_WARN("Failed to resolve reference @%s@ with computed "
710                             "asset path @%s@ found in layer @%s@.",
711                             ref.c_str(),
712                             refAssetPath.c_str(),
713                             fileAnalyzer.GetFilePath().c_str());
714 
715                     _unresolvedAssetPaths.push_back(refAssetPath);
716                     continue;
717                 }
718 
719 #if AR_VERSION == 1
720                 // Ensure that the resolved path can be fetched to a physical
721                 // location on disk.
722                 if (!ArGetResolver().FetchToLocalResolvedPath(refAssetPath,
723                         resolvedRefFilePath)) {
724                     TF_WARN("Failed to fetch-to-local resolved path for asset "
725                         "@%s@ : '%s'. Skipping dependency.",
726                         refAssetPath.c_str(), resolvedRefFilePath.c_str());
727                     continue;
728                 }
729 #endif
730 
731                 // Check if this dependency must skipped.
732                 if (std::find(dependenciesToSkip.begin(),
733                               dependenciesToSkip.end(), resolvedRefFilePath) !=
734                            dependenciesToSkip.end()) {
735                     continue;
736                 }
737 
738                 // Given the way our remap function (_RemapAssetPath) works, we
739                 // should only have to copy every resolved file once during
740                 // localization.
741                 if (!seenFiles.insert(resolvedRefFilePath).second) {
742                     continue;
743                 }
744 
745                 // XXX: We don't localize directory references. Should we copy
746                 // the entire directory over?
747                 if (TfIsDir(resolvedRefFilePath)) {
748                     continue;
749                 }
750 
751                 bool isRelativePath = false;
752                 std::string remappedRef = _RemapAssetPath(ref,
753                     fileAnalyzer.GetLayer(),
754                     origRootFilePath, rootFilePath, firstLayerName,
755                     &dirRemapper, &isRelativePath);
756 
757                 // If it's a relative path, construct the full path relative to
758                 // the final (destination) location of the reference-containing
759                 // file.
760                 const std::string destDirForRef =
761                     isRelativePath ? TfGetPathName(destFilePath) : destDir;
762                 const std::string destFilePathForRef = TfStringCatPaths(
763                         destDirForRef, remappedRef);
764 
765                 filesToLocalize.emplace(destFilePathForRef, _FileAnalyzer(
766                         resolvedRefFilePath,
767                         /* refTypesToInclude */ _ReferenceTypesToInclude::All,
768                         remapAssetPathFunc));
769             }
770         }
771     }
772 
773     // Get the list of layers to be localized along with their corresponding
774     // destination paths.
GetLayerExportMap() const775     const std::vector<LayerAndDestPath> &GetLayerExportMap() const {
776         return _layerExportMap;
777     }
778 
779     // Get the list of source files to be copied along with their corresponding
780     // destination paths.
GetFileCopyMap() const781     const std::vector<SrcPathAndDestPath> &GetFileCopyMap() const {
782         return _fileCopyMap;
783     }
784 
785     // Returns ths list of all unresolved references.
GetUnresolvedAssetPaths() const786     const std::vector<std::string> GetUnresolvedAssetPaths() const {
787         return _unresolvedAssetPaths;
788     }
789 
790 private:
791     // This will contain a mapping of SdfLayerRefPtr's mapped to their
792     // desination path inside the destination directory.
793     std::vector<LayerAndDestPath> _layerExportMap;
794 
795     // This will contain a mapping of source file path to the corresponding
796     // desination file path.
797     std::vector<SrcPathAndDestPath> _fileCopyMap;
798 
799     // A map of layers and their corresponding vector of raw external reference
800     // paths.
801     LayerDependenciesMap _layerDependenciesMap;
802 
803     // List of all the unresolvable asset paths.
804     std::vector<std::string> _unresolvedAssetPaths;
805 
806     // Helper object for remapping paths to an artifically-generated path.
807     class _DirectoryRemapper {
808     public:
_DirectoryRemapper()809         _DirectoryRemapper() : _nextDirectoryNum(0) { }
810 
811         // Remap the given file path by replacing the directory with a
812         // unique, artifically generated name. The generated directory name
813         // will be reused if the original directory is seen again on a
814         // subsequent call to Remap.
Remap(const std::string & filePath)815         std::string Remap(const std::string& filePath)
816         {
817             if (ArIsPackageRelativePath(filePath)) {
818                 std::pair<std::string, std::string> packagePath =
819                     ArSplitPackageRelativePathOuter(filePath);
820                 return ArJoinPackageRelativePath(
821                     Remap(packagePath.first), packagePath.second);
822             }
823 
824             const std::string pathName = TfGetPathName(filePath);
825             if (pathName.empty()) {
826                 return filePath;
827             }
828 
829             const std::string baseName = TfGetBaseName(filePath);
830 
831             auto insertStatus = _oldToNewDirectory.insert({pathName, ""});
832             if (insertStatus.second) {
833                 insertStatus.first->second =
834                     TfStringPrintf("%zu", _nextDirectoryNum++);
835             }
836 
837             return TfStringCatPaths(insertStatus.first->second, baseName);
838         }
839 
840     private:
841         size_t _nextDirectoryNum;
842         std::unordered_map<std::string, std::string> _oldToNewDirectory;
843     };
844 
845     // Remaps a given asset path (\p refPath) to be relative to the layer
846     // containing it (\p layer) for the purpose of localization.
847     // \p dirRemapper should not be empty.
848     // \p pathType is allowed to be a nullptr.
849     // \p origRootFilePath should contain to the path to the original
850     // root file from which the asset at \p rootFilePath was created (possibly
851     // by flattening),
852     // \p rootFilePath should contain a path to the resolved and localized root
853     // asset layer on disk.
854     // \p firstLayerName if non-empty contains the final name of the asset's
855     // root layer.
856     static std::string _RemapAssetPath(
857         const std::string &refPath,
858         const SdfLayerRefPtr &layer,
859         const std::string origRootFilePath,
860         const std::string rootFilePath,
861         const std::string &firstLayerName,
862         _DirectoryRemapper *dirRemapper,
863         bool *isRelativePath);
864 };
865 
866 std::string
_RemapAssetPath(const std::string & refPath,const SdfLayerRefPtr & layer,std::string origRootFilePath,std::string rootFilePath,const std::string & firstLayerName,_DirectoryRemapper * dirRemapper,bool * isRelativePathOut)867 _AssetLocalizer::_RemapAssetPath(const std::string &refPath,
868                                  const SdfLayerRefPtr &layer,
869                                  std::string origRootFilePath,
870                                  std::string rootFilePath,
871                                  const std::string &firstLayerName,
872                                  _DirectoryRemapper *dirRemapper,
873                                  bool *isRelativePathOut)
874 {
875     auto &resolver = ArGetResolver();
876 
877 #if AR_VERSION == 1
878     const bool isContextDependentPath = resolver.IsSearchPath(refPath);
879     const bool isRelativePath =
880         !isContextDependentPath && resolver.IsRelativePath(refPath);
881 #else
882     const bool isContextDependentPath =
883         resolver.IsContextDependentPath(refPath);
884 
885     // We determine if refPath is relative by creating identifiers with
886     // and without the anchoring layer and seeing if they're the same.
887     // If they aren't, then refPath depends on the anchor, so we assume
888     // it's relative.
889     const bool isRelativePath =
890         !isContextDependentPath &&
891         (resolver.CreateIdentifier(refPath, layer->GetResolvedPath()) !=
892          resolver.CreateIdentifier(refPath));
893 #endif
894 
895     // Return relative paths unmodified.
896     if (isRelativePathOut) {
897         *isRelativePathOut = isRelativePath;
898     }
899 
900     if (isRelativePath) {
901         return refPath;
902     }
903 
904     std::string result = refPath;
905     if (isContextDependentPath) {
906         // Absolutize the search path, to avoid collisions resulting from the
907         // same search path resolving to different paths in different resolver
908         // contexts.
909         const std::string refAssetPath =
910                 SdfComputeAssetPathRelativeToLayer(layer, refPath);
911         const std::string refFilePath = resolver.Resolve(refAssetPath);
912 
913         bool resolveOk = !refFilePath.empty();
914 #if AR_VERSION == 1
915         // Ensure that the resolved path can be fetched to a physical
916         // location on disk.
917         resolveOk = resolveOk &&
918             resolver.FetchToLocalResolvedPath(refAssetPath, refFilePath);
919 #endif
920 
921         if (resolveOk) {
922             result = refFilePath;
923         } else {
924             // Failed to resolve, hence retain the reference as is.
925             result = refAssetPath;
926         }
927     }
928 
929     // Normalize paths compared below to account for path format differences.
930 #if AR_VERSION == 1
931     const std::string layerPath =
932         resolver.ComputeNormalizedPath(layer->GetRealPath());
933     result = resolver.ComputeNormalizedPath(result);
934     rootFilePath = resolver.ComputeNormalizedPath(rootFilePath);
935     origRootFilePath = resolver.ComputeNormalizedPath(origRootFilePath);
936 #else
937     const std::string layerPath =
938         TfNormPath(layer->GetRealPath());
939     result = TfNormPath(result);
940     rootFilePath = TfNormPath(rootFilePath);
941     origRootFilePath = TfNormPath(origRootFilePath);
942 #endif
943 
944     bool resultPointsToRoot = ((result == rootFilePath) ||
945                                (result == origRootFilePath));
946     // If this is a self-reference, then remap to a relative path that points
947     // to the file itself.
948     if (result == layerPath) {
949         // If this is a self-reference in the root layer and we're renaming the
950         // root layer, simply set the reference path to point to the renamed
951         // root layer.
952         return resultPointsToRoot && !firstLayerName.empty() ?
953             firstLayerName : TfGetBaseName(result);
954     }
955 
956     // References to the original (unflattened) root file need to be remapped
957     // to point to the new root file.
958     if (resultPointsToRoot && layerPath == rootFilePath) {
959         return !firstLayerName.empty() ? firstLayerName : TfGetBaseName(result);
960     }
961 
962     // Result is now an absolute or a repository path. Simply strip off the
963     // leading slashes to make it relative.
964     // Strip off any drive letters.
965     if (result.size() >= 2 && result[1] == ':') {
966         result.erase(0, 2);
967     }
968 
969     // Strip off any initial slashes.
970     result = TfStringTrimLeft(result, "/");
971 
972     // Remap the path to an artifically-constructed one so that the source
973     // directory structure isn't embedded in the final .usdz file. Otherwise,
974     // sensitive information (e.g. usernames, movie titles...) in directory
975     // names may be inadvertently leaked in the .usdz file.
976     return dirRemapper->Remap(result);
977 }
978 
979 // Returns a relative path for fullDestPath, relative to the given destination
980 // directory (destDir).
981 static
982 std::string
_GetDestRelativePath(const std::string & fullDestPath,const std::string & destDir)983 _GetDestRelativePath(const std::string &fullDestPath,
984                      const std::string &destDir)
985 {
986     std::string destPath = fullDestPath;
987     // fullDestPath won't start with destDir if destDir is a relative path,
988     // relative to CWD.
989     if (TfStringStartsWith(destPath, destDir)) {
990         destPath = destPath.substr(destDir.length());
991     }
992     return destPath;
993 }
994 
995 } // end of anonymous namespace
996 
997 
998 static void
_ExtractExternalReferences(const std::string & filePath,const _ReferenceTypesToInclude & refTypesToInclude,std::vector<std::string> * subLayers,std::vector<std::string> * references,std::vector<std::string> * payloads)999 _ExtractExternalReferences(
1000     const std::string& filePath,
1001     const _ReferenceTypesToInclude &refTypesToInclude,
1002     std::vector<std::string>* subLayers,
1003     std::vector<std::string>* references,
1004     std::vector<std::string>* payloads)
1005 {
1006     // We only care about knowing what the dependencies are. Hence, set
1007     // remapPathFunc to empty.
1008     _FileAnalyzer(filePath, refTypesToInclude,
1009         /*remapPathFunc*/ {},
1010         [&subLayers, &references, &payloads](const std::string &assetPath,
1011                                           const _DepType &depType) {
1012             if (depType == _DepType::Reference) {
1013                 references->push_back(assetPath);
1014             } else if (depType == _DepType::Sublayer) {
1015                 subLayers->push_back(assetPath);
1016             } else if (depType == _DepType::Payload) {
1017                 payloads->push_back(assetPath);
1018             }
1019         });
1020 
1021     // Sort and remove duplicates
1022     std::sort(references->begin(), references->end());
1023     references->erase(std::unique(references->begin(), references->end()),
1024         references->end());
1025     std::sort(payloads->begin(), payloads->end());
1026     payloads->erase(std::unique(payloads->begin(), payloads->end()),
1027         payloads->end());
1028 }
1029 
1030 // XXX: don't even know if it's important to distinguish where
1031 // these asset paths are coming from..  if it's not important, maybe this
1032 // should just go into Sdf's _GatherPrimAssetReferences?  if it is important,
1033 // we could also have another function that takes 3 vectors.
1034 void
UsdUtilsExtractExternalReferences(const std::string & filePath,std::vector<std::string> * subLayers,std::vector<std::string> * references,std::vector<std::string> * payloads)1035 UsdUtilsExtractExternalReferences(
1036     const std::string& filePath,
1037     std::vector<std::string>* subLayers,
1038     std::vector<std::string>* references,
1039     std::vector<std::string>* payloads)
1040 {
1041     TRACE_FUNCTION();
1042     _ExtractExternalReferences(filePath, _ReferenceTypesToInclude::All,
1043         subLayers, references, payloads);
1044 }
1045 
1046 static
1047 bool
_CreateNewUsdzPackage(const SdfAssetPath & assetPath,const std::string & usdzFilePath,const std::string & firstLayerName,const std::string & origRootFilePath=std::string (),const std::vector<std::string> & dependenciesToSkip=std::vector<std::string> ())1048 _CreateNewUsdzPackage(const SdfAssetPath &assetPath,
1049                       const std::string &usdzFilePath,
1050                       const std::string &firstLayerName,
1051                       const std::string &origRootFilePath=std::string(),
1052                       const std::vector<std::string> &dependenciesToSkip
1053                             =std::vector<std::string>())
1054 {
1055     TF_DEBUG(USDUTILS_CREATE_USDZ_PACKAGE).Msg("Creating USDZ package at '%s' "
1056         "containing asset @%s@.\n", usdzFilePath.c_str(),
1057         assetPath.GetAssetPath().c_str());
1058 
1059     std::string destDir = TfGetPathName(usdzFilePath);
1060     destDir = destDir.empty() ? "./" : destDir;
1061     _AssetLocalizer localizer(assetPath, destDir, firstLayerName,
1062                               origRootFilePath, dependenciesToSkip);
1063 
1064     auto &layerExportMap = localizer.GetLayerExportMap();
1065     auto &fileCopyMap = localizer.GetFileCopyMap();
1066 
1067     if (layerExportMap.empty() && fileCopyMap.empty()) {
1068         return false;
1069     }
1070 
1071     // Set of all the packaged files.
1072     std::unordered_set<std::string> packagedFiles;
1073 
1074     const std::string tmpDirPath = ArchGetTmpDir();
1075 
1076     UsdZipFileWriter writer = UsdZipFileWriter::CreateNew(usdzFilePath);
1077 
1078     auto &resolver = ArGetResolver();
1079     // This returns true of src and dest have the same file extension.
1080     const auto extensionsMatch = [&resolver](const std::string &src,
1081                                              const std::string &dest) {
1082         return resolver.GetExtension(src) == resolver.GetExtension(dest);
1083     };
1084 
1085     bool firstLayer = true;
1086     bool success = true;
1087     for (auto &layerAndDestPath : layerExportMap) {
1088         const auto &layer = layerAndDestPath.first;
1089         std::string destPath = _GetDestRelativePath(
1090                 layerAndDestPath.second, destDir);
1091 
1092         // Change the first layer's name if requested.
1093         if (firstLayer && !firstLayerName.empty()) {
1094             const std::string pathName = TfGetPathName(destPath);
1095             destPath = TfStringCatPaths(pathName, firstLayerName);
1096             firstLayer = false;
1097         }
1098 
1099         if (!packagedFiles.insert(destPath).second) {
1100             TF_WARN("A file already exists at path \"%s\" in the package. "
1101                 "Skipping export of layer @%s@.", destPath.c_str(),
1102                 layer->GetIdentifier().c_str());
1103             continue;
1104         }
1105 
1106         TF_DEBUG(USDUTILS_CREATE_USDZ_PACKAGE).Msg(
1107             ".. adding layer @%s@ to package at path '%s'.\n",
1108             layer->GetIdentifier().c_str(), destPath.c_str());
1109 
1110         // If the layer is a package or if it's inside a package, copy the
1111         // entire package. We could extract the package and copy only the
1112         // dependencies, but this could get very complicated.
1113         if (layer->GetFileFormat()->IsPackage() ||
1114              ArIsPackageRelativePath(layer->GetIdentifier())) {
1115             std::string packagePath = ArSplitPackageRelativePathOuter(
1116                     layer->GetRealPath()).first;
1117             std::string destPackagePath = ArSplitPackageRelativePathOuter(
1118                     destPath).first;
1119             if (!packagePath.empty()) {
1120                 std::string inArchivePath = writer.AddFile(packagePath,
1121                         destPackagePath);
1122                 if (inArchivePath.empty()) {
1123                     success = false;
1124                 }
1125             }
1126         } else if (!layer->IsDirty() &&
1127                    extensionsMatch(layer->GetRealPath(), destPath)) {
1128             // If the layer hasn't been modified from its persistent
1129             // representation and if its extension isn't changing in the
1130             // package, then simply copy it over from its real-path (i.e.
1131             // location on disk). This preserves any existing comments in the
1132             // file (which will be lost if we were to export all layers before
1133             // adding them to to the package).
1134             std::string inArchivePath = writer.AddFile(layer->GetRealPath(),
1135                     destPath);
1136             if (inArchivePath.empty()) {
1137                 success = false;
1138             }
1139         } else {
1140             // If the layer has been modified or needs to be modified, then we
1141             // need to export it to a temporary file before adding it to the
1142             // package.
1143             SdfFileFormat::FileFormatArguments args;
1144 
1145             const SdfFileFormatConstPtr fileFormat =
1146                     SdfFileFormat::FindByExtension(
1147                         SdfFileFormat::GetFileExtension(destPath));
1148 
1149             if (TfDynamic_cast<UsdUsdFileFormatConstPtr>(fileFormat)) {
1150                 args[UsdUsdFileFormatTokens->FormatArg] =
1151                         UsdUsdFileFormat::GetUnderlyingFormatForLayer(
1152                             *get_pointer(layer));
1153             }
1154 
1155             std::string tmpLayerExportPath = TfStringCatPaths(tmpDirPath,
1156                     TfGetBaseName(destPath));
1157             layer->Export(tmpLayerExportPath, /*comment*/ "", args);
1158 
1159             std::string inArchivePath = writer.AddFile(tmpLayerExportPath,
1160                     destPath);
1161 
1162             if (inArchivePath.empty()) {
1163                 // XXX: Should we discard the usdz file and return early here?
1164                 TF_WARN("Failed to add temporary layer at '%s' to the package "
1165                     "at path '%s'.", tmpLayerExportPath.c_str(),
1166                     usdzFilePath.c_str());
1167                 success = false;
1168             } else {
1169                 // The file has been added to the package successfully. We can
1170                 // delete it now.
1171                 TfDeleteFile(tmpLayerExportPath);
1172             }
1173         }
1174     }
1175 
1176     for (auto &fileSrcAndDestPath : fileCopyMap) {
1177         const std::string &srcPath = fileSrcAndDestPath.first;
1178         const std::string destPath = _GetDestRelativePath(
1179                 fileSrcAndDestPath.second, destDir);
1180         TF_DEBUG(USDUTILS_CREATE_USDZ_PACKAGE).Msg(
1181             ".. adding file '%s' to package at path '%s'.\n",
1182             srcPath.c_str(), destPath.c_str());
1183 
1184         if (!packagedFiles.insert(destPath).second) {
1185             TF_WARN("A file already exists at path \"%s\" in the package. "
1186                 "Skipping copy of file \"%s\".", destPath.c_str(),
1187                 srcPath.c_str());
1188             continue;
1189         }
1190 
1191         // If the file is a package or inside a package, copy the
1192         // entire package. We could extract the package and copy only the
1193         // dependencies, but this could get very complicated.
1194         if (ArIsPackageRelativePath(destPath)) {
1195             std::string packagePath = ArSplitPackageRelativePathOuter(
1196                     srcPath).first;
1197             std::string destPackagePath = ArSplitPackageRelativePathOuter(
1198                     destPath).first;
1199             if (!packagePath.empty()) {
1200                 std::string inArchivePath = writer.AddFile(packagePath,
1201                     destPackagePath);
1202                 if (inArchivePath.empty()) {
1203                     success = false;
1204                 }
1205             }
1206         }
1207         else {
1208             std::string inArchivePath = writer.AddFile(srcPath, destPath);
1209             if (inArchivePath.empty()) {
1210                 // XXX: Should we discard the usdz file and return early here?
1211                 TF_WARN("Failed to add file '%s' to the package at path '%s'.",
1212                     srcPath.c_str(), usdzFilePath.c_str());
1213                 success = false;
1214             }
1215         }
1216     }
1217 
1218     return writer.Save() && success;
1219 }
1220 
1221 bool
UsdUtilsCreateNewUsdzPackage(const SdfAssetPath & assetPath,const std::string & usdzFilePath,const std::string & firstLayerName)1222 UsdUtilsCreateNewUsdzPackage(const SdfAssetPath &assetPath,
1223                              const std::string &usdzFilePath,
1224                              const std::string &firstLayerName)
1225 {
1226     return _CreateNewUsdzPackage(assetPath, usdzFilePath, firstLayerName);
1227 }
1228 
1229 bool
UsdUtilsCreateNewARKitUsdzPackage(const SdfAssetPath & assetPath,const std::string & inUsdzFilePath,const std::string & firstLayerName)1230 UsdUtilsCreateNewARKitUsdzPackage(
1231     const SdfAssetPath &assetPath,
1232     const std::string &inUsdzFilePath,
1233     const std::string &firstLayerName)
1234 {
1235     auto &resolver = ArGetResolver();
1236 
1237     std::string usdzFilePath = ArchNormPath(inUsdzFilePath);
1238 
1239     const std::string resolvedPath = resolver.Resolve(assetPath.GetAssetPath());
1240     if (resolvedPath.empty()) {
1241         return false;
1242     }
1243 
1244     // Check if the given asset has external dependencies that participate in
1245     // the composition of the stage.
1246     std::vector<std::string> sublayers, references, payloads;
1247     _ExtractExternalReferences(resolvedPath,
1248         _ReferenceTypesToInclude::CompositionOnly,
1249         &sublayers, &references, &payloads);
1250 
1251     // Ensure that the root layer has the ".usdc" extension.
1252     std::string targetBaseName = firstLayerName.empty() ?
1253         TfGetBaseName(assetPath.GetAssetPath()) : firstLayerName;
1254     const std::string &fileExt = resolver.GetExtension(targetBaseName);
1255     bool renamingRootLayer = false;
1256     if (fileExt != UsdUsdcFileFormatTokens->Id) {
1257         renamingRootLayer = true;
1258         targetBaseName = targetBaseName.substr(0, targetBaseName.rfind(".")+1) +
1259                 UsdUsdcFileFormatTokens->Id.GetString();
1260     }
1261 
1262     // If there are no external dependencies needed for composition, we can
1263     // invoke the regular packaging function.
1264     if (sublayers.empty() && references.empty() && payloads.empty()) {
1265         if (renamingRootLayer) {
1266             return _CreateNewUsdzPackage(assetPath, usdzFilePath,
1267                     /*firstLayerName*/ targetBaseName,
1268                     /* origRootFilePath*/ resolvedPath,
1269                     /* dependenciesToSkip */ {resolvedPath});
1270         } else {
1271             return _CreateNewUsdzPackage(assetPath, usdzFilePath,
1272                 /*firstLayerName*/ targetBaseName,
1273                 /* origRootFilePath*/ resolvedPath);
1274         }
1275     }
1276 
1277     TF_WARN("The given asset '%s' contains one or more composition arcs "
1278         "referencing external USD files. Flattening it to a single .usdc file "
1279         "before packaging. This will result in loss of features such as "
1280         "variantSets and all asset references to be absolutized.",
1281         assetPath.GetAssetPath().c_str());
1282 
1283     const auto &usdStage = UsdStage::Open(resolvedPath);
1284     const std::string tmpFileName =
1285             ArchMakeTmpFileName(targetBaseName, ".usdc");
1286 
1287     TF_DEBUG(USDUTILS_CREATE_USDZ_PACKAGE).Msg(
1288         "Flattening asset @%s@ located at '%s' to temporary layer at "
1289         "path '%s'.\n", assetPath.GetAssetPath().c_str(), resolvedPath.c_str(),
1290         tmpFileName.c_str());
1291 
1292     if (!usdStage->Export(tmpFileName, /*addSourceFileComment*/ false)) {
1293         TF_WARN("Failed to flatten and export the USD stage '%s'.",
1294             UsdDescribe(usdStage).c_str());
1295         return false;
1296     }
1297 
1298     bool success = _CreateNewUsdzPackage(SdfAssetPath(tmpFileName),
1299         usdzFilePath, /* firstLayerName */ targetBaseName,
1300         /* origRootFilePath*/ resolvedPath,
1301         /*dependenciesToSkip*/ {resolvedPath});
1302 
1303     if (success) {
1304         TfDeleteFile(tmpFileName);
1305     } else {
1306         TF_WARN("Failed to create a .usdz package from temporary, flattened "
1307             "layer '%s'.", tmpFileName.c_str());;
1308     }
1309 
1310     return success;
1311 }
1312 
1313 bool
UsdUtilsComputeAllDependencies(const SdfAssetPath & assetPath,std::vector<SdfLayerRefPtr> * layers,std::vector<std::string> * assets,std::vector<std::string> * unresolvedPaths)1314 UsdUtilsComputeAllDependencies(const SdfAssetPath &assetPath,
1315                                std::vector<SdfLayerRefPtr> *layers,
1316                                std::vector<std::string> *assets,
1317                                std::vector<std::string> *unresolvedPaths)
1318 {
1319     // We are not interested in localizing here, hence pass in the empty string
1320     // for destination directory.
1321     _AssetLocalizer localizer(assetPath, /* destDir */ std::string());
1322 
1323     // Clear the vectors before we start.
1324     layers->clear();
1325     assets->clear();
1326 
1327     // Reserve space in the vectors.
1328     layers->reserve(localizer.GetLayerExportMap().size());
1329     assets->reserve(localizer.GetFileCopyMap().size());
1330 
1331     for (auto &layerAndDestPath : localizer.GetLayerExportMap()) {
1332         layers->push_back(layerAndDestPath.first);
1333     }
1334 
1335     for (auto &srcAndDestPath : localizer.GetFileCopyMap()) {
1336         assets->push_back(srcAndDestPath.first);
1337     }
1338 
1339     *unresolvedPaths = localizer.GetUnresolvedAssetPaths();
1340 
1341     // Return true if one or more layers or assets were added  to the results.
1342     return !layers->empty() || !assets->empty();
1343 }
1344 
1345 void
UsdUtilsModifyAssetPaths(const SdfLayerHandle & layer,const UsdUtilsModifyAssetPathFn & modifyFn)1346 UsdUtilsModifyAssetPaths(
1347         const SdfLayerHandle& layer,
1348         const UsdUtilsModifyAssetPathFn& modifyFn)
1349 {
1350     _FileAnalyzer(layer,
1351         _ReferenceTypesToInclude::All,
1352         [&modifyFn](const std::string& assetPath,
1353                     const SdfLayerRefPtr& layer,
1354                     bool skipDep) {
1355             return modifyFn(assetPath);
1356         }
1357     );
1358 }
1359 
1360 PXR_NAMESPACE_CLOSE_SCOPE
1361