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