1 //
2 // Copyright 2016-2019 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 /// \file alembicWriter.cpp
25 
26 #include "pxr/pxr.h"
27 #include "pxr/usd/plugin/usdAbc/alembicWriter.h"
28 #include "pxr/usd/plugin/usdAbc/alembicUtil.h"
29 #include "pxr/usd/usdGeom/hermiteCurves.h"
30 #include "pxr/usd/usdGeom/tokens.h"
31 #include "pxr/usd/usdGeom/xformOp.h"
32 #include "pxr/usd/sdf/schema.h"
33 #include "pxr/usd/usd/schemaRegistry.h"
34 #include "pxr/base/trace/trace.h"
35 #include "pxr/base/tf/enum.h"
36 #include "pxr/base/tf/envSetting.h"
37 #include "pxr/base/tf/fileUtils.h"
38 #include "pxr/base/tf/ostreamMethods.h"
39 #include "pxr/base/tf/pathUtils.h"
40 #include <Alembic/Abc/OArchive.h>
41 #include <Alembic/Abc/OObject.h>
42 #include <Alembic/AbcGeom/OCamera.h>
43 #include <Alembic/AbcGeom/OCurves.h>
44 #include <Alembic/AbcGeom/OPoints.h>
45 #include <Alembic/AbcGeom/OPolyMesh.h>
46 #include <Alembic/AbcGeom/OSubD.h>
47 #include <Alembic/AbcGeom/OXform.h>
48 #include <Alembic/AbcGeom/Visibility.h>
49 #include <Alembic/AbcCoreOgawa/All.h>
50 #include <boost/functional/hash.hpp>
51 #include <algorithm>
52 #include <functional>
53 #include <memory>
54 #include <set>
55 #include <type_traits>
56 
57 PXR_NAMESPACE_OPEN_SCOPE
58 
59 
60 // The name of this exporter, embedded in written Alembic files.
61 static const char* writerName = "UsdAbc_AlembicData";
62 
63 TF_DEFINE_PRIVATE_TOKENS(
64     _tokens,
65     (transform)
66     ((xformOpTransform, "xformOp:transform"))
67 );
68 
69 TF_DEFINE_ENV_SETTING(USD_ABC_READ_FLOAT2_AS_UV, true,
70         "Turn to false to disable reading float2 arrays as uv sets");
71 
72 namespace {
73 
74 using namespace ::Alembic::AbcGeom;
75 using namespace UsdAbc_AlembicUtil;
76 
77 // The SdfAbstractData time samples type.
78 // XXX: SdfAbstractData should typedef this.
79 typedef std::set<double> UsdAbc_TimeSamples;
80 
81 struct _Subtract {
operator ()__anon980b96790111::_Subtract82     double operator()(double x, double y) const { return x - y; }
83 };
84 
85 static
86 GeometryScope
_GetGeometryScope(const TfToken & interpolation)87 _GetGeometryScope(const TfToken& interpolation)
88 {
89     static const TfToken constant("constant");
90     static const TfToken uniform("uniform");
91     static const TfToken varying("varying");
92     static const TfToken vertex("vertex");
93     static const TfToken faceVarying("faceVarying");
94     if (interpolation.IsEmpty() || interpolation == constant) {
95         return kConstantScope;
96     }
97     if (interpolation == uniform) {
98         return kUniformScope;
99     }
100     if (interpolation == varying) {
101         return kVaryingScope;
102     }
103     if (interpolation == vertex) {
104         return kVertexScope;
105     }
106     if (interpolation == faceVarying) {
107         return kFacevaryingScope;
108     }
109     return kUnknownScope;
110 }
111 
112 //
113 // UsdSamples
114 //
115 
116 /// \class UsdSamples
117 /// \brief Wraps time samples from a Usd property.
118 ///
119 /// Wraps time samples or a default in a Usd property, providing a uniform
120 /// interface.
121 class UsdSamples {
122 public:
123     UsdSamples(const SdfPath& primPath, const TfToken& propertyName);
124 
125     /// Construct from a property.  If the property has time samples use
126     /// those, otherwise use the default as a single time sample at time
127     /// zero.  If there's no default then return an empty set.
128     ///
129     /// This validates the samples to ensure they all have the same type.
130     UsdSamples(const SdfPath& primPath,
131                const TfToken& propertyName,
132                const SdfAbstractData& data);
133 
134     /// Returns a path.
135     SdfPath GetPath() const;
136 
137     /// Returns \c true iff there are no samples.
138     bool IsEmpty() const;
139 
140     /// Returns the number of samples.
141     size_t GetNumSamples() const;
142 
143     /// Returns \c true iff the property is time sampled, \c false if
144     /// the value was taken from the default or if there were no opinions.
145     bool IsTimeSampled() const;
146 
147     /// Returns the type name of the samples.
148     const SdfValueTypeName& GetTypeName() const;
149 
150     /// Returns a field on the property.
151     VtValue GetField(const TfToken& name) const;
152 
153     /// Returns the sample closest to time \p time.
154     const VtValue& Get(double time) const;
155 
156     /// Adds the set of all sample times to \p times.
157     void AddTimes(UsdAbc_TimeSamples* times) const;
158 
159     /// Returns the sample map.
160     const SdfTimeSampleMap& GetSamples() const;
161 
162     /// Sets the samples to \p samples.  The contents of \p samples is
163     /// undefined after the call.
164     void TakeSamples(SdfTimeSampleMap& samples);
165 
166 private:
167     bool _Validate();
168     void _Clear();
169 
170 private:
171     SdfPath _propPath;
172     const SdfAbstractData* _data;
173     boost::shared_ptr<VtValue> _value;
174     boost::shared_ptr<SdfTimeSampleMap> _local;
175     const SdfTimeSampleMap* _samples;
176     bool _timeSampled;
177     SdfValueTypeName _typeName;
178 };
179 
UsdSamples(const SdfPath & primPath,const TfToken & propertyName)180 UsdSamples::UsdSamples(const SdfPath& primPath, const TfToken& propertyName) :
181     _propPath(primPath.AppendProperty(propertyName)),
182     _data(NULL)
183 {
184     _Clear();
185 }
186 
UsdSamples(const SdfPath & primPath,const TfToken & propertyName,const SdfAbstractData & data)187 UsdSamples::UsdSamples(
188     const SdfPath& primPath,
189     const TfToken& propertyName,
190     const SdfAbstractData& data) :
191     _propPath(primPath.AppendProperty(propertyName)),
192     _data(&data)
193 {
194     VtValue value;
195     if (data.Has(_propPath, SdfFieldKeys->TimeSamples, &value)) {
196         if (TF_VERIFY(value.IsHolding<SdfTimeSampleMap>())) {
197             _value.reset(new VtValue);
198             _value->Swap(value);
199             _samples     = &_value->UncheckedGet<SdfTimeSampleMap>();
200             _timeSampled = true;
201         }
202         else {
203             _Clear();
204             return;
205         }
206     }
207     else if (data.Has(_propPath, SdfFieldKeys->Default, &value)) {
208         _local.reset(new SdfTimeSampleMap);
209         (*_local)[0.0].Swap(value);
210         _samples       = _local.get();
211         _timeSampled   = false;
212     }
213     else {
214         _Clear();
215         return;
216     }
217     if (TF_VERIFY(data.Has(_propPath, SdfFieldKeys->TypeName, &value),
218                   "No type name on <%s>", _propPath.GetText())) {
219         if (TF_VERIFY(value.IsHolding<TfToken>())) {
220             _typeName =
221                 SdfSchema::GetInstance().
222                     FindType(value.UncheckedGet<TfToken>());
223             _Validate();
224         }
225         else {
226             _Clear();
227         }
228     }
229     else {
230         _Clear();
231     }
232 }
233 
234 bool
_Validate()235 UsdSamples::_Validate()
236 {
237     const TfType type = _typeName.GetType();
238     const TfType backupType =
239         (type == TfType::Find<float>()) ? TfType::Find<double>() : type;
240 restart:
241     for (const auto& v : *_samples) {
242         if (v.second.GetType() != type) {
243             if (!TF_VERIFY(v.second.GetType() == backupType,
244                               "Expected sample at <%s> time %f of type '%s', "
245                               "got '%s'",
246                               GetPath().GetText(),
247                               v.first, type.GetTypeName().c_str(),
248                               v.second.GetType().GetTypeName().c_str())) {
249                 _Clear();
250                 return false;
251             }
252             else {
253                 // Make sure we have a local copy.
254                 if (!_local) {
255                     _local.reset(new SdfTimeSampleMap(*_samples));
256                     _samples = _local.get();
257                     goto restart;
258                 }
259 
260                 // Convert double to float.
261                 (*_local)[v.first] =
262                     static_cast<float>(v.second.UncheckedGet<double>());
263             }
264         }
265     }
266     return true;
267 }
268 
269 void
_Clear()270 UsdSamples::_Clear()
271 {
272     _value.reset();
273     _local.reset(new SdfTimeSampleMap);
274     _samples       = _local.get();
275     _timeSampled   = false;
276     _typeName      = SdfValueTypeName();
277 }
278 
279 SdfPath
GetPath() const280 UsdSamples::GetPath() const
281 {
282     return _propPath;
283 }
284 
285 bool
IsEmpty() const286 UsdSamples::IsEmpty() const
287 {
288     return _samples->empty();
289 }
290 
291 size_t
GetNumSamples() const292 UsdSamples::GetNumSamples() const
293 {
294     return _samples->size();
295 }
296 
297 bool
IsTimeSampled() const298 UsdSamples::IsTimeSampled() const
299 {
300     return _timeSampled;
301 }
302 
303 const SdfValueTypeName&
GetTypeName() const304 UsdSamples::GetTypeName() const
305 {
306     return _typeName;
307 }
308 
309 VtValue
GetField(const TfToken & name) const310 UsdSamples::GetField(const TfToken& name) const
311 {
312     return _data->Get(_propPath, name);
313 }
314 
315 const VtValue&
Get(double time) const316 UsdSamples::Get(double time) const
317 {
318     if (IsEmpty()) {
319         static const VtValue empty;
320         return empty;
321     }
322     else {
323         SdfTimeSampleMap::const_iterator i = _samples->lower_bound(time);
324         return i == _samples->end() ? _samples->rbegin()->second : i->second;
325     }
326 }
327 
328 void
AddTimes(UsdAbc_TimeSamples * times) const329 UsdSamples::AddTimes(UsdAbc_TimeSamples* times) const
330 {
331     for (const auto& v : *_samples) {
332         times->insert(v.first);
333     }
334 }
335 
336 const SdfTimeSampleMap&
GetSamples() const337 UsdSamples::GetSamples() const
338 {
339     return *_samples;
340 }
341 
342 void
TakeSamples(SdfTimeSampleMap & samples)343 UsdSamples::TakeSamples(SdfTimeSampleMap& samples)
344 {
345     if (!_local) {
346         _value.reset();
347         _local.reset(new SdfTimeSampleMap);
348     }
349     _local.get()->swap(samples);
350     _samples = _local.get();
351     _Validate();
352 }
353 
354 //
355 // _Parent
356 //
357 
358 /// \class _Parent
359 /// \brief Encapsulates an Alembic parent object
360 ///
361 /// This mainly exists to extract certain properties from objects that
362 /// have them.  The Alembic type hierarchy and templating prevents us
363 /// from dynamic casting to a type that can provide these properties
364 /// (there isn't a single type to cast to, instead there's a templated
365 /// class and the template argument depends on the actual type of the
366 /// object).  This object holds enough type information to get what we
367 /// want.
368 class _Parent {
369 public:
370     /// Construct invalid parent.
_Parent()371     _Parent() : _object(new _Prim(shared_ptr<OObject>(new OObject))) { }
372 
373     /// Construct from an Alembic shared pointer to an OObject subclass.
374     template <class T>
_Parent(const shared_ptr<T> & prim)375     _Parent(const shared_ptr<T>& prim) :
376         _object(new _Prim(static_pointer_cast<OObject>(prim))) { }
377 
378     /// Construct from an Alembic shared pointer to a supported
379     /// schema based OSchemaObject.
_Parent(const shared_ptr<OCamera> & prim)380     _Parent(const shared_ptr<OCamera>& prim):
381         _object(new _GeomPrim<OCamera>(prim)) { }
_Parent(const shared_ptr<OCurves> & prim)382     _Parent(const shared_ptr<OCurves>& prim):
383         _object(new _GeomPrim<OCurves>(prim)) { }
_Parent(const shared_ptr<OPoints> & prim)384     _Parent(const shared_ptr<OPoints>& prim):
385         _object(new _GeomPrim<OPoints>(prim)) { }
_Parent(const shared_ptr<OPolyMesh> & prim)386     _Parent(const shared_ptr<OPolyMesh>& prim):
387         _object(new _GeomPrim<OPolyMesh>(prim)) { }
_Parent(const shared_ptr<OSubD> & prim)388     _Parent(const shared_ptr<OSubD>& prim):
389         _object(new _GeomPrim<OSubD>(prim)) { }
_Parent(const shared_ptr<OXform> & prim)390     _Parent(const shared_ptr<OXform>& prim):
391         _object(new _GeomPrim<OXform>(prim)) { }
392 
393     /// Returns the OObject.
394     operator OObject&() const;
395 
396     /// Returns the OCompoundProperty holding the object's properties.
397     OCompoundProperty GetProperties() const;
398 
399     /// Returns the OCompoundProperty holding the object's schema.
400     OCompoundProperty GetSchema() const;
401 
402     /// Returns the OCompoundProperty holding the ".arbGeomParams" property.
403     /// This returns an invalid property if the object isn't geometric.
404     OCompoundProperty GetArbGeomParams() const;
405 
406     /// Returns the OCompoundProperty holding the ".userProperties" property.
407     /// This returns an invalid property if the object isn't geometric.
408     OCompoundProperty GetUserProperties() const;
409 
410 private:
411     class _Prim {
412     public:
_Prim(const shared_ptr<OObject> & object)413         explicit _Prim(const shared_ptr<OObject>& object) : _object(object) { }
414         virtual ~_Prim();
GetObjectPtr() const415         const shared_ptr<OObject>& GetObjectPtr() const { return _object; }
416         virtual OCompoundProperty GetSchema() const;
417         virtual OCompoundProperty GetArbGeomParams() const;
418         virtual OCompoundProperty GetUserProperties() const;
419 
420     private:
421         shared_ptr<OObject> _object;
422     };
423 
424     template <class T>
425     class _GeomPrim : public _Prim {
426     public:
_GeomPrim(const shared_ptr<T> & object)427         explicit _GeomPrim(const shared_ptr<T>& object) : _Prim(object) { }
~_GeomPrim()428         virtual ~_GeomPrim() { }
429         virtual OCompoundProperty GetSchema() const;
430         virtual OCompoundProperty GetArbGeomParams() const;
431         virtual OCompoundProperty GetUserProperties() const;
432     };
433 
434 private:
435     shared_ptr<_Prim> _object;
436 };
437 
~_Prim()438 _Parent::_Prim::~_Prim()
439 {
440     // Do nothing
441 }
442 
443 OCompoundProperty
GetSchema() const444 _Parent::_Prim::GetSchema() const
445 {
446     return OCompoundProperty();
447 }
448 
449 OCompoundProperty
GetArbGeomParams() const450 _Parent::_Prim::GetArbGeomParams() const
451 {
452     return OCompoundProperty();
453 }
454 
455 OCompoundProperty
GetUserProperties() const456 _Parent::_Prim::GetUserProperties() const
457 {
458     return OCompoundProperty();
459 }
460 
461 template <class T>
462 OCompoundProperty
GetSchema() const463 _Parent::_GeomPrim<T>::GetSchema() const
464 {
465     return static_pointer_cast<T>(GetObjectPtr())->getSchema();
466 }
467 
468 template <class T>
469 OCompoundProperty
GetArbGeomParams() const470 _Parent::_GeomPrim<T>::GetArbGeomParams() const
471 {
472     return
473         static_pointer_cast<T>(GetObjectPtr())->getSchema().getArbGeomParams();
474 }
475 
476 template <class T>
477 OCompoundProperty
GetUserProperties() const478 _Parent::_GeomPrim<T>::GetUserProperties() const
479 {
480     return
481         static_pointer_cast<T>(GetObjectPtr())->getSchema().getUserProperties();
482 }
483 
operator OObject&() const484 _Parent::operator OObject&() const
485 {
486     return *_object->GetObjectPtr();
487 }
488 
489 OCompoundProperty
GetProperties() const490 _Parent::GetProperties() const
491 {
492     return _object->GetObjectPtr()->getProperties();
493 }
494 
495 OCompoundProperty
GetSchema() const496 _Parent::GetSchema() const
497 {
498     return _object->GetSchema();
499 }
500 
501 OCompoundProperty
GetArbGeomParams() const502 _Parent::GetArbGeomParams() const
503 {
504     return _object->GetArbGeomParams();
505 }
506 
507 OCompoundProperty
GetUserProperties() const508 _Parent::GetUserProperties() const
509 {
510     return _object->GetUserProperties();
511 }
512 
513 //
514 // _WriterSchema
515 //
516 
517 class _PrimWriterContext;
518 
519 /// \class _WriterSchema
520 /// \brief The Alembic to Usd schema.
521 ///
522 /// This class stores functions to write a Usd prim to Alembic keyed by
523 /// type.  Each type can have multiple writers, each affected by the
524 /// previous via a \c _PrimWriterContext.
525 class _WriterSchema {
526 public:
527     typedef std::function<void (_PrimWriterContext*)> PrimWriter;
528     typedef std::vector<PrimWriter> PrimWriterVector;
529     typedef UsdAbc_AlembicDataConversion::FromUsdConverter Converter;
530 
531     _WriterSchema();
532 
533     /// Returns the prim writers for the given type.  Returns an empty
534     /// vector if the type isn't known.
535     const PrimWriterVector& GetPrimWriters(const TfToken&) const;
536 
537     // Helper for defining types.
538     class TypeRef {
539     public:
TypeRef(PrimWriterVector * writers)540         TypeRef(PrimWriterVector* writers) : _writers(writers) { }
541 
AppendWriter(const PrimWriter & writer)542         TypeRef& AppendWriter(const PrimWriter& writer)
543         {
544             _writers->push_back(writer);
545             return *this;
546         }
547 
548     private:
549         PrimWriterVector* _writers;
550     };
551 
552     /// Adds a type and returns a helper for defining it.
553     template <class T>
AddType(T name)554     TypeRef AddType(T name)
555     {
556         return TypeRef(&_writers[TfToken(name)]);
557     }
558 
559     /// Adds the fallback type and returns a helper for defining it.
AddFallbackType()560     TypeRef AddFallbackType()
561     {
562         return AddType(TfToken());
563     }
564 
565     /// Returns \c true iff the samples are valid.
566     bool IsValid(const UsdSamples&) const;
567 
568     /// Returns \c true iff the samples are a shaped type.
569     bool IsShaped(const UsdSamples&) const;
570 
571     /// Returns the Alembic DataType suitable for the values in \p samples.
572     DataType GetDataType(const UsdSamples& samples) const;
573 
574     /// Returns the (default) conversion for the Alembic property type with
575     /// name \p typeName.
576     SdfValueTypeName FindConverter(const UsdAbc_AlembicType& typeName) const;
577 
578     /// Returns the (default) conversion for the Usd property type with
579     /// name \p typeName.
580     UsdAbc_AlembicType FindConverter(const SdfValueTypeName& typeName) const;
581 
582     /// Returns the conversion function for the given conversion.
583     const Converter& GetConverter(const SdfValueTypeName& typeName) const;
584 
585 private:
586     const UsdAbc_AlembicConversions _conversions;
587 
588     typedef std::map<TfToken, PrimWriterVector> _WriterMap;
589     _WriterMap _writers;
590 };
591 
_WriterSchema()592 _WriterSchema::_WriterSchema()
593 {
594     // Do nothing
595 }
596 
597 const _WriterSchema::PrimWriterVector&
GetPrimWriters(const TfToken & name) const598 _WriterSchema::GetPrimWriters(const TfToken& name) const
599 {
600     _WriterMap::const_iterator i = _writers.find(name);
601     if (i != _writers.end()) {
602         return i->second;
603     }
604     i = _writers.find(TfToken());
605     if (i != _writers.end()) {
606         return i->second;
607     }
608     static const PrimWriterVector empty;
609     return empty;
610 }
611 
612 bool
IsValid(const UsdSamples & samples) const613 _WriterSchema::IsValid(const UsdSamples& samples) const
614 {
615     return GetConverter(samples.GetTypeName()) ? true : false;
616 }
617 
618 bool
IsShaped(const UsdSamples & samples) const619 _WriterSchema::IsShaped(const UsdSamples& samples) const
620 {
621     return samples.GetTypeName().IsArray();
622 }
623 
624 DataType
GetDataType(const UsdSamples & samples) const625 _WriterSchema::GetDataType(const UsdSamples& samples) const
626 {
627     return FindConverter(samples.GetTypeName()).GetDataType();
628 }
629 
630 SdfValueTypeName
FindConverter(const UsdAbc_AlembicType & typeName) const631 _WriterSchema::FindConverter(const UsdAbc_AlembicType& typeName) const
632 {
633     return _conversions.data.FindConverter(typeName);
634 }
635 
636 UsdAbc_AlembicType
FindConverter(const SdfValueTypeName & typeName) const637 _WriterSchema::FindConverter(const SdfValueTypeName& typeName) const
638 {
639     return _conversions.data.FindConverter(typeName);
640 }
641 
642 const _WriterSchema::Converter&
GetConverter(const SdfValueTypeName & typeName) const643 _WriterSchema::GetConverter(const SdfValueTypeName& typeName) const
644 {
645     return _conversions.data.GetConverter(typeName);
646 }
647 
648 /// \class _WriterContext
649 /// \brief The Alembic to Usd writer context.
650 ///
651 /// This object holds information used by the writer for a given archive
652 /// and Usd data.
653 class _WriterContext {
654 public:
655     _WriterContext();
656 
657     /// Returns the archive.
658     void SetArchive(const OArchive& archive);
659 
660     /// Returns the archive.
GetArchive() const661     const OArchive& GetArchive() const { return _archive; }
662 
663     /// Returns the archive.
GetArchive()664     OArchive& GetArchive() { return _archive; }
665 
666     /// Sets the writer schema.
SetSchema(const _WriterSchema * schema)667     void SetSchema(const _WriterSchema* schema) { _schema = schema; }
668 
669     /// Returns the writer schema.
GetSchema() const670     const _WriterSchema& GetSchema() const { return *_schema; }
671 
672     /// Set the Usd data that we will translate.  Also resets _timeScale to
673     /// data's timeCodesPerSecond
SetData(const SdfAbstractDataConstPtr & data)674     void SetData(const SdfAbstractDataConstPtr& data) {
675         _data = data;
676         VtValue tcps;
677         if (data->Has(SdfPath::AbsoluteRootPath(),
678                       SdfFieldKeys->TimeCodesPerSecond, &tcps)) {
679             if (tcps.IsHolding<double>()) {
680                 _timeScale = tcps.UncheckedGet<double>();
681             }
682         }
683     }
684 
685     /// Returns the Usd data.
GetData() const686     const SdfAbstractData& GetData() const { return *boost::get_pointer(_data);}
687 
688     /// Sets or resets the flag named \p flagName.
689     void SetFlag(const TfToken& flagName, bool set);
690 
691     /// Returns \c true iff a flag is in the set.
692     bool IsFlagSet(const TfToken& flagName) const;
693 
694     /// Adds/returns a time sampling.
695     uint32_t AddTimeSampling(const UsdAbc_TimeSamples&);
696 
697 private:
698     // Conversion options.
699     double _timeScale;              // Scale Alembic time by this factor.
700     double _timeOffset;             // Offset Alembic->Usd time (after scale).
701     std::set<TfToken, TfTokenFastArbitraryLessThan> _flags;
702 
703     // Output state.
704     OArchive _archive;
705     const _WriterSchema* _schema;
706     SdfAbstractDataConstPtr _data;
707 
708     // The set of time samplings we've created.  We tend to reuse the same
709     // samplings a lot so caching these avoids reanalyzing the samples to
710     // determine their kind.  This also avoids having Alembic check for
711     // duplicate time samplings.  This maps a set of samples to the index
712     // of the time sampling in the archive.  Index 0 is reserved by
713     // Alembic to mean uniform starting at 0, cycling at 1.
714     std::map<UsdAbc_TimeSamples, uint32_t> _timeSamplings;
715 };
716 
_WriterContext()717 _WriterContext::_WriterContext() :
718     _timeScale(24.0),               // Usd is frames, Alembic is seconds.
719     _timeOffset(0.0),               // Time 0.0 to frame 0.
720     _schema(NULL)
721 {
722     // Do nothing
723 }
724 
725 void
SetArchive(const OArchive & archive)726 _WriterContext::SetArchive(const OArchive& archive)
727 {
728     _archive = archive;
729     _timeSamplings.clear();
730 }
731 
732 void
SetFlag(const TfToken & flagName,bool set)733 _WriterContext::SetFlag(const TfToken& flagName, bool set)
734 {
735     if (set) {
736         _flags.insert(flagName);
737     }
738     else {
739         _flags.erase(flagName);
740     }
741 }
742 
743 bool
IsFlagSet(const TfToken & flagName) const744 _WriterContext::IsFlagSet(const TfToken& flagName) const
745 {
746     return _flags.count(flagName);
747 }
748 
749 uint32_t
AddTimeSampling(const UsdAbc_TimeSamples & inSamples)750 _WriterContext::AddTimeSampling(const UsdAbc_TimeSamples& inSamples)
751 {
752     // Handle empty case.
753     if (inSamples.empty()) {
754         // No samples -> identity time sampling.
755         return 0;
756     }
757 
758     // Get the cached index.  If not zero then we already have this one.
759     std::map<UsdAbc_TimeSamples, uint32_t>::const_iterator tsi =
760         _timeSamplings.find(inSamples);
761     if (tsi != _timeSamplings.end()) {
762         return tsi->second;
763     }
764     uint32_t& index = _timeSamplings[inSamples];
765 
766     // Scale and offset samples.
767     UsdAbc_TimeSamples samples;
768     for (double time : inSamples) {
769         samples.insert((time - _timeOffset) / _timeScale);
770     }
771 
772     // Handy iterators.  i refers to the first element and n to end.
773     // Initially j refers to the second element.
774     UsdAbc_TimeSamples::const_iterator j = samples.begin();
775     const UsdAbc_TimeSamples::const_iterator i = j++;
776     const UsdAbc_TimeSamples::const_iterator n = samples.end();
777 
778     // Handle other special cases.  Note that we store the index returned
779     // by Alembic.  Alembic will compare our TimeSampling() against all of
780     // the ones it's seen so far and return the index of the existing one,
781     // if any.  So we can map multiple time samples sets to the same
782     // time sampling index.
783     if (samples.size() == 1) {
784         // One sample -> uniform starting at the sample, arbitrary cycle time.
785         return index = _archive.addTimeSampling(TimeSampling(1, *i));
786     }
787     if (samples.size() == 2) {
788         // Two samples -> uniform.  Cyclic and acyclic would also work but
789         // uniform is probably more likely to match an existing sampling.
790         return index =
791             _archive.addTimeSampling(TimeSampling(*j - *i, *i));
792     }
793 
794     // Figure out if the samples are uniform (T0 + N * dT for integer N),
795     // cyclic (Ti + N * dT for integer N and samples i=[0,M]), or acyclic
796     // (Ti for samples i=[0,M]).  In all cases, the total number of samples
797     // is an independent variable and doesn't affect the choice.  That is,
798     // 1 1/2 cycles is cyclic even though we don't complete a cycle.  Note,
799     // however, that any acyclic sequence is also a cyclic sequence of one
800     // less sample (which we'll call the trivial cyclic sequence);  we
801     // choose the acyclic type.
802     //
803     // First find the deltas between samples.
804     std::vector<double> dt;
805     std::transform(j, n, i, std::back_inserter(dt), _Subtract());
806 
807     // Scan forward in dt for element M that matches dt[0] then check that
808     // dt[0..M-1] == dt[M..2M-1).  If that checks out then check that the
809     // cycle continues until we run out of samples.  Otherwise repeat from
810     // M + 1 until we find a cycle or run out of samples.  Don't check
811     // dt.back() to avoid trivial cyclic.  We adjust j to point to the
812     // time sample at the start of the delta dt[k].
813     size_t k = 1;
814     const size_t m = dt.size();
815     TimeSamplingType timeSamplingType(TimeSamplingType::kAcyclic);
816     for (; k != m - 1; ++j, ++k) {
817         // Check for a cycle by comparing s[i] == s[i + k] for i in
818         // [0..N-k-1] where N = len(s) and k is the cycle length.
819         if (std::equal(dt.begin() + k, dt.begin() + m, dt.begin())) {
820             // Cyclic or uniform (which is cyclic with samps/cycle == 1).
821             timeSamplingType = TimeSamplingType(k, *j - *i);
822             break;
823         }
824     }
825 
826     // If we're still acyclic then use every sample.
827     if (timeSamplingType.isAcyclic()) {
828         j = n;
829     }
830 
831     // Cyclic or acyclic.
832     return index =
833         _archive.addTimeSampling(
834             TimeSampling(timeSamplingType, std::vector<chrono_t>(i, j)));
835 }
836 
837 /// \class _PrimWriterContext
838 /// \brief The Alembic to Usd prim writer context.
839 ///
840 /// This object holds information used by the writer for a given prim.
841 /// Each prim writer can modify the context to change the behavior of
842 /// later writers for that prim.
843 class _PrimWriterContext {
844 public:
845     typedef _Parent Parent;
846 
847     _PrimWriterContext(_WriterContext&,
848                        const Parent& parent,
849                        const SdfPath& path);
850 
851     /// Return the path to this prim.
852     SdfPath GetPath() const;
853 
854     /// Returns the Usd field from the prim.
855     VtValue GetField(const TfToken& fieldName) const;
856 
857     /// Returns the Usd field from the named property.
858     VtValue GetPropertyField(const TfToken& propertyName,
859                              const TfToken& fieldName) const;
860 
861     /// Returns the archive.
862     OArchive& GetArchive();
863 
864     /// Returns the writer schema.
865     const _WriterSchema& GetSchema() const;
866 
867     /// Returns the abstract data.
868     const SdfAbstractData& GetData() const;
869 
870     /// Returns the spec type for the named property.
871     SdfSpecType GetSpecType(const TfToken& propertyName) const;
872 
873     /// Tests a flag.
874     bool IsFlagSet(const TfToken& flagName) const;
875 
876     /// Adds/returns a time sampling.
877     uint32_t AddTimeSampling(const UsdAbc_TimeSamples&);
878 
879     /// Returns the parent object.
880     const Parent& GetParent() const;
881 
882     /// Sets the parent object.
883     void SetParent(const Parent& parent);
884 
885     /// Causes \c GetAlembicPrimName() to have the suffix appended.
886     void PushSuffix(const std::string& suffix);
887 
888     /// Returns a prim name that is valid Alembic, isn't in use, and has
889     /// all suffixes appended.
890     std::string GetAlembicPrimName() const;
891 
892     /// Returns an Alembic name for \p name that is valid in Alembic and
893     /// isn't in use.
894     std::string GetAlembicPropertyName(const TfToken& name) const;
895 
896     /// Sets the union of extracted sample times to \p timeSamples.
897     void SetSampleTimesUnion(const UsdAbc_TimeSamples& timeSamples);
898 
899     /// Returns the union of extracted sample times.
900     const UsdAbc_TimeSamples& GetSampleTimesUnion() const;
901 
902     /// Returns the samples for a Usd property.  If the property doesn't
903     /// exist or has already been extracted then this returns an empty
904     /// samples object.  The property is extracted from the context so
905     /// it cannot be extracted again.  The sample times union is updated
906     /// to include the sample times from the returned object.
ExtractSamples(const TfToken & name)907     UsdSamples ExtractSamples(const TfToken& name)
908     {
909         return _ExtractSamples(name, {});
910     }
911 
912     /// Returns the samples for a Usd property.  If the property doesn't
913     /// exist or has already been extracted then this returns an empty
914     /// samples object.  The property is extracted from the context so
915     /// it cannot be extracted again.  The sample times union is updated
916     /// to include the sample times from the returned object.  This
917     /// verifies that the property is holding a value of either the given type
918     //  or the alternative type;
919     /// if not it returns an empty samples object.
ExtractSamples(const TfToken & name,const SdfValueTypeName & type,const SdfValueTypeName & alternativeType)920     UsdSamples ExtractSamples(const TfToken& name, const SdfValueTypeName& type,
921                               const SdfValueTypeName& alternativeType)
922     {
923         return _ExtractSamples(name, {type, alternativeType});
924     }
925 
926     /// Returns the samples for a Usd property.  If the property doesn't
927     /// exist or has already been extracted then this returns an empty
928     /// samples object.  The property is extracted from the context so
929     /// it cannot be extracted again.  The sample times union is updated
930     /// to include the sample times from the returned object.  This
931     /// verifies that the property is holding a value of the given type;
932     /// if not it returns an empty samples object.
ExtractSamples(const TfToken & name,const SdfValueTypeName & type)933     UsdSamples ExtractSamples(const TfToken& name, const SdfValueTypeName& type)
934     {
935         return _ExtractSamples(name, {type});
936     }
937 
938     /// Removes samples for a Usd property.  The property cannot be extracted
939     /// after removal.
RemoveSamples(const TfToken & name)940     void RemoveSamples(const TfToken& name)
941     {
942         auto i = std::find(_unextracted.begin(), _unextracted.end(), name);
943         if (i != _unextracted.end()) {
944             _unextracted.erase(i);
945         }
946     }
947 
948     /// Returns the names of properties that have not been extracted yet
949     /// in Usd property order.
950     TfTokenVector GetUnextractedNames() const;
951 
952     /// Return the _WriterContext associated with this prim.
953     _WriterContext& GetWriterContext() const;
954 
955 private:
956     UsdSamples _ExtractSamples(const TfToken& name);
957 
_ExtractSamples(const TfToken & name,const std::vector<SdfValueTypeName> & types)958     UsdSamples _ExtractSamples(const TfToken& name,
959             const std::vector<SdfValueTypeName> &types)
960     {
961         UsdSamples result = _ExtractSamples(name);
962         if (!result.IsEmpty() && !types.empty()) {
963             SdfValueTypeName resultTypeName = result.GetTypeName();
964             if (find(types.begin(), types.end(), resultTypeName) ==
965                 types.end())
966             {
967                 TF_WARN("Property '%s' did not have expected type (got '%s')",
968                         GetPath().AppendProperty(name).GetText(),
969                         resultTypeName.GetAsToken().GetText());
970                 return UsdSamples(GetPath(), name);
971             }
972         }
973         result.AddTimes(&_sampleTimes);
974         return result;
975     }
976 
977 private:
978     typedef std::vector<SdfValueTypeName> SdfValueTypeNameVector;
979 
980     _WriterContext& _context;
981     Parent _parent;
982     SdfPath _path;
983     std::string _suffix;
984     UsdAbc_TimeSamples _sampleTimes;
985     TfTokenVector _unextracted;
986 };
987 
_PrimWriterContext(_WriterContext & context,const Parent & parent,const SdfPath & path)988 _PrimWriterContext::_PrimWriterContext(
989     _WriterContext& context,
990     const Parent& parent,
991     const SdfPath& path) :
992     _context(context),
993     _parent(parent),
994     _path(path)
995 {
996     // Fill _unextracted with all of the property names.
997     VtValue tmp;
998     if (_context.GetData().Has(
999             _path, SdfChildrenKeys->PropertyChildren, &tmp)) {
1000         if (tmp.IsHolding<TfTokenVector>()) {
1001             _unextracted = tmp.UncheckedGet<TfTokenVector>();
1002         }
1003     }
1004 }
1005 
1006 SdfPath
GetPath() const1007 _PrimWriterContext::GetPath() const
1008 {
1009     return _path.IsPropertyPath() ? _path.GetParentPath() : _path;
1010 }
1011 
1012 VtValue
GetField(const TfToken & fieldName) const1013 _PrimWriterContext::GetField(const TfToken& fieldName) const
1014 {
1015     return _context.GetData().Get(_path, fieldName);
1016 }
1017 
1018 VtValue
GetPropertyField(const TfToken & propertyName,const TfToken & fieldName) const1019 _PrimWriterContext::GetPropertyField(
1020     const TfToken& propertyName,
1021     const TfToken& fieldName) const
1022 {
1023     return _context.GetData().Get(
1024         GetPath().AppendProperty(propertyName), fieldName);
1025 }
1026 
1027 OArchive&
GetArchive()1028 _PrimWriterContext::GetArchive()
1029 {
1030     return _context.GetArchive();
1031 }
1032 
1033 const _WriterSchema&
GetSchema() const1034 _PrimWriterContext::GetSchema() const
1035 {
1036     return _context.GetSchema();
1037 }
1038 
1039 const SdfAbstractData&
GetData() const1040 _PrimWriterContext::GetData() const
1041 {
1042     return _context.GetData();
1043 }
1044 
1045 SdfSpecType
GetSpecType(const TfToken & propertyName) const1046 _PrimWriterContext::GetSpecType(const TfToken& propertyName) const
1047 {
1048     return _context.GetData().GetSpecType(
1049         GetPath().AppendProperty(propertyName));
1050 }
1051 
1052 bool
IsFlagSet(const TfToken & flagName) const1053 _PrimWriterContext::IsFlagSet(const TfToken& flagName) const
1054 {
1055     return _context.IsFlagSet(flagName);
1056 }
1057 
1058 uint32_t
AddTimeSampling(const UsdAbc_TimeSamples & samples)1059 _PrimWriterContext::AddTimeSampling(const UsdAbc_TimeSamples& samples)
1060 {
1061     return _context.AddTimeSampling(samples);
1062 }
1063 
1064 const _PrimWriterContext::Parent&
GetParent() const1065 _PrimWriterContext::GetParent() const
1066 {
1067     return _parent;
1068 }
1069 
1070 void
SetParent(const Parent & parent)1071 _PrimWriterContext::SetParent(const Parent& parent)
1072 {
1073     _parent = parent;
1074 }
1075 
1076 void
PushSuffix(const std::string & suffix)1077 _PrimWriterContext::PushSuffix(const std::string& suffix)
1078 {
1079     _suffix += suffix;
1080 }
1081 
1082 std::string
GetAlembicPrimName() const1083 _PrimWriterContext::GetAlembicPrimName() const
1084 {
1085     // Valid Alembic prim name set is a superset of valid Usd prim names.
1086     // XXX: Should verify this name is not in use, however we know
1087     //      we're not given how we use it (we only add a suffix to
1088     //      an only child).
1089     return GetPath().GetName() + _suffix;
1090 }
1091 
1092 std::string
GetAlembicPropertyName(const TfToken & name) const1093 _PrimWriterContext::GetAlembicPropertyName(const TfToken& name) const
1094 {
1095     // Valid Alembic property name set is a superset of valid Usd property
1096     // names.  Alembic accepts the Usd namespace delimiter as-is.
1097     return name.GetString();
1098 }
1099 
1100 void
SetSampleTimesUnion(const UsdAbc_TimeSamples & samples)1101 _PrimWriterContext::SetSampleTimesUnion(const UsdAbc_TimeSamples& samples)
1102 {
1103     _sampleTimes = samples;
1104 }
1105 
1106 const UsdAbc_TimeSamples&
GetSampleTimesUnion() const1107 _PrimWriterContext::GetSampleTimesUnion() const
1108 {
1109     return _sampleTimes;
1110 }
1111 
1112 UsdSamples
_ExtractSamples(const TfToken & name)1113 _PrimWriterContext::_ExtractSamples(const TfToken& name)
1114 {
1115     TfTokenVector::iterator i =
1116         std::find(_unextracted.begin(), _unextracted.end(), name);
1117     if (i != _unextracted.end()) {
1118         _unextracted.erase(i);
1119         return UsdSamples(GetPath(), name, GetData());
1120     }
1121     return UsdSamples(GetPath(), name);
1122 }
1123 
1124 TfTokenVector
GetUnextractedNames() const1125 _PrimWriterContext::GetUnextractedNames() const
1126 {
1127     return _unextracted;
1128 }
1129 
1130 _WriterContext&
GetWriterContext() const1131 _PrimWriterContext::GetWriterContext() const
1132 {
1133     return _context;
1134 }
1135 
1136 // ----------------------------------------------------------------------------
1137 
1138 //
1139 // Utilities
1140 //
1141 
1142 /// Returns the Alembic metadata name for a Usd metadata field name.
1143 static
1144 std::string
_AmdName(const std::string & name)1145 _AmdName(const std::string& name)
1146 {
1147     return "Usd:" + name;
1148 }
1149 
1150 static
1151 bool
_IsOver(const _PrimWriterContext & context)1152 _IsOver(const _PrimWriterContext& context)
1153 {
1154     if (context.GetField(SdfFieldKeys->TypeName).IsEmpty()) {
1155         return true;
1156     }
1157     const VtValue value = context.GetField(SdfFieldKeys->Specifier);
1158     return ! value.IsHolding<SdfSpecifier>() ||
1159                value.UncheckedGet<SdfSpecifier>() == SdfSpecifierOver;
1160 }
1161 
1162 // Reverse the order of the subsequences in \p valuesMap where the subsequence
1163 // lengths are given by \p counts.
1164 template <class T>
1165 static
1166 void
_ReverseWindingOrder(UsdSamples * valuesMap,const UsdSamples & countsMap)1167 _ReverseWindingOrder(UsdSamples* valuesMap, const UsdSamples& countsMap)
1168 {
1169     typedef VtArray<T> ValueArray;
1170     typedef VtArray<int> CountArray;
1171 
1172     SdfTimeSampleMap result;
1173     for (const auto& v : valuesMap->GetSamples()) {
1174         const VtValue& valuesValue = v.second;
1175         const VtValue& countsValue = countsMap.Get(v.first);
1176         if (! TF_VERIFY(valuesValue.IsHolding<ValueArray>())) {
1177             continue;
1178         }
1179         if (! TF_VERIFY(countsValue.IsHolding<CountArray>())) {
1180             continue;
1181         }
1182         ValueArray values        = valuesValue.UncheckedGet<ValueArray>();
1183         const CountArray& counts = countsValue.UncheckedGet<CountArray>();
1184         if (! UsdAbc_ReverseOrderImpl(values, counts)) {
1185             continue;
1186         }
1187         result[v.first].Swap(values);
1188     }
1189     valuesMap->TakeSamples(result);
1190 }
1191 
1192 // Adjust faceVertexIndices for winding order if orientation is right-handed.
1193 static
1194 void
_ReverseWindingOrder(const _PrimWriterContext * context,UsdSamples * faceVertexIndices,const UsdSamples & faceVertexCounts)1195 _ReverseWindingOrder(
1196     const _PrimWriterContext* context,
1197     UsdSamples* faceVertexIndices,
1198     const UsdSamples& faceVertexCounts)
1199 {
1200     // Get property value.  We must reverse the winding order if it's
1201     // right-handed in Usd.  (Alembic is always left-handed.)
1202     // XXX: Should probably check time samples, too, but we expect
1203     //      this to be uniform.
1204     const VtValue value =
1205         context->GetPropertyField(UsdGeomTokens->orientation,
1206                                   SdfFieldKeys->Default);
1207     if (! value.IsHolding<TfToken>() ||
1208             value.UncheckedGet<TfToken>() != UsdGeomTokens->leftHanded) {
1209         _ReverseWindingOrder<int>(faceVertexIndices, faceVertexCounts);
1210     }
1211 }
1212 
1213 static
1214 std::string
_GetInterpretation(const SdfValueTypeName & typeName)1215 _GetInterpretation(const SdfValueTypeName& typeName)
1216 {
1217     const TfToken& roleName = typeName.GetRole();
1218     if (roleName == SdfValueRoleNames->Point) {
1219         return "point";
1220     }
1221     if (roleName == SdfValueRoleNames->Normal) {
1222         return "normal";
1223     }
1224     if (roleName == SdfValueRoleNames->Vector) {
1225         return "vector";
1226     }
1227     if (roleName == SdfValueRoleNames->Color) {
1228         if (typeName == SdfValueTypeNames->Float4 ||
1229             typeName == SdfValueTypeNames->Double4) {
1230             return "rgba";
1231         }
1232         return "rgb";
1233     }
1234     if (roleName == SdfValueRoleNames->Transform) {
1235         return "matrix";
1236     }
1237     if (typeName == SdfValueTypeNames->Quatd ||
1238         typeName == SdfValueTypeNames->Quatf) {
1239         return "quat";
1240     }
1241     return std::string();
1242 }
1243 
1244 static
1245 std::string
_Stringify(const DataType & type)1246 _Stringify(const DataType& type)
1247 {
1248     if (type.getExtent() > 1) {
1249         return TfStringPrintf("%s[%d]",PODName(type.getPod()),type.getExtent());
1250     }
1251     else {
1252         return PODName(type.getPod());
1253     }
1254 }
1255 
1256 // Make a sample, converting the Usd value to the given Alembic data type.
1257 // If the given conversion doesn't perform that conversion then fail.
1258 //
1259 // Note:  This is the one and only place we invoke a converter to create a
1260 // _SampleForAlembic.
1261 static _SampleForAlembic
_MakeSample(const _WriterSchema & schema,const _WriterSchema::Converter & converter,const SdfValueTypeName & usdType,const VtValue & usdValue,const DataType & expectedAlembicType,bool skipAlembicTypeCheck=false)1262 _MakeSample(
1263     const _WriterSchema& schema,
1264     const _WriterSchema::Converter& converter,
1265     const SdfValueTypeName& usdType,
1266     const VtValue& usdValue,
1267     const DataType& expectedAlembicType,
1268     bool skipAlembicTypeCheck = false)
1269 {
1270     TRACE_SCOPE("UsdAbc_AlembicDataWriter:_MakeSample");
1271 
1272     // Done if there is no value.
1273     if (usdValue.IsEmpty()) {
1274         return _SampleForAlembic();
1275     }
1276 
1277     // This catches when the programmer fails to supply a conversion function
1278     // for the conversion.
1279     if (! converter) {
1280         return _ErrorSampleForAlembic(TfStringPrintf(
1281                     "No conversion for '%s'",
1282                     usdType.GetAsToken().GetText()));
1283     }
1284 
1285     // This catches when the programmer supplies a conversion that doesn't
1286     // yield the correct Alembic type.  This should never happen.
1287     if (! skipAlembicTypeCheck) {
1288         const UsdAbc_AlembicType actualAlembicType = schema.FindConverter(usdType);
1289         if (actualAlembicType.GetDataType() != expectedAlembicType) {
1290             return _ErrorSampleForAlembic(TfStringPrintf(
1291                         "Internal error: trying to convert '%s' to '%s'",
1292                         usdType.GetAsToken().GetText(),
1293                         _Stringify(expectedAlembicType).c_str()));
1294         }
1295     }
1296 
1297     // This catches when the Usd property doesn't have the expected type.
1298     // This should never happen because we check properties for the
1299     // expected type when we extract samples from the _PrimWriterContext,
1300     // or we choose our conversion based on the type of the samples.
1301     const SdfValueTypeName actualUsdType =
1302         SdfSchema::GetInstance().FindType(usdValue);
1303     if (actualUsdType != usdType) {
1304         // Handle role types.  These have a different name but the same
1305         // value type.
1306         if (usdType.GetType() != actualUsdType.GetType()) {
1307             return _ErrorSampleForAlembic(TfStringPrintf(
1308                         "Internal error: Trying to use conversion for '%s' to "
1309                         "convert from '%s'",
1310                         usdType.GetAsToken().GetText(),
1311                         actualUsdType.GetAsToken().GetText()));
1312         }
1313     }
1314 
1315     // Convert.
1316     _SampleForAlembic result(converter(usdValue));
1317 
1318     // Check extent.
1319     if (expectedAlembicType.getExtent() != 1) {
1320         if (result.GetCount() % expectedAlembicType.getExtent() != 0) {
1321             return _ErrorSampleForAlembic(TfStringPrintf(
1322                         "Internal error: didn't get a multiple of the extent "
1323                         "(%zd %% %d = %zd)",
1324                         result.GetCount(), expectedAlembicType.getExtent(),
1325                         result.GetCount() % expectedAlembicType.getExtent()));
1326         }
1327     }
1328 
1329     return result;
1330 }
1331 
1332 // Helper to handle enum types in the following _MakeSample() function.
1333 template <typename T, typename Enable = void>
1334 struct PodEnum {
1335     static const PlainOldDataType pod_enum = PODTraitsFromType<T>::pod_enum;
1336 };
1337 template <typename T>
1338 struct PodEnum<T, typename std::enable_if<std::is_enum<T>::value>::type> {
1339     static const PlainOldDataType pod_enum = kUint8POD;
1340 };
1341 
1342 // Make a sample, converting the Usd value to the given data type, T.
1343 // If the given conversion doesn't perform that conversion then fail.
1344 template <class T>
1345 static _SampleForAlembic
_MakeSample(const _WriterSchema & schema,const _WriterSchema::Converter & converter,const SdfValueTypeName & usdType,const VtValue & usdValue,bool skipAlembicTypeCheck=false)1346 _MakeSample(
1347     const _WriterSchema& schema,
1348     const _WriterSchema::Converter& converter,
1349     const SdfValueTypeName& usdType,
1350     const VtValue& usdValue,
1351     bool skipAlembicTypeCheck = false)
1352 {
1353     return _MakeSample(schema, converter, usdType, usdValue,
1354                        DataType(PodEnum<T>::pod_enum),
1355                        skipAlembicTypeCheck);
1356 }
1357 
1358 template <class T, int N>
1359 static _SampleForAlembic
_MakeSample(const _WriterSchema & schema,const _WriterSchema::Converter & converter,const SdfValueTypeName & usdType,const VtValue & usdValue,bool skipAlembicTypeCheck=false)1360 _MakeSample(
1361     const _WriterSchema& schema,
1362     const _WriterSchema::Converter& converter,
1363     const SdfValueTypeName& usdType,
1364     const VtValue& usdValue,
1365     bool skipAlembicTypeCheck = false)
1366 {
1367     return _MakeSample(schema, converter, usdType, usdValue,
1368                        DataType(PODTraitsFromType<T>::pod_enum, N),
1369                        skipAlembicTypeCheck);
1370 }
1371 
1372 static bool
_CheckSample(const _SampleForAlembic & sample,const UsdSamples & samples,const SdfValueTypeName & usdType)1373 _CheckSample(
1374     const _SampleForAlembic& sample,
1375     const UsdSamples& samples,
1376     const SdfValueTypeName& usdType)
1377 {
1378     std::string message;
1379     if (sample.IsError(&message)) {
1380         TF_WARN("Can't convert from '%s' on <%s>: %s",
1381                 usdType.GetAsToken().GetText(),
1382                 samples.GetPath().GetText(),
1383                 message.c_str());
1384         return false;
1385     }
1386     return static_cast<bool>(sample);
1387 }
1388 
1389 // An object we can use for mapping in _MakeIndexed.  It holds a pointer to
1390 // its data.  It's not local to _MakeIndexed because we use it as a template
1391 // argument.
1392 template<class POD, size_t extent>
1393 struct _MakeIndexedValue {
1394     const POD* value;
_MakeIndexedValue__anon980b96790111::_MakeIndexedValue1395     _MakeIndexedValue(const POD* value_) : value(value_) { }
1396 
1397     // Copy value to \p dst.
CopyTo__anon980b96790111::_MakeIndexedValue1398     void CopyTo(POD* dst) const
1399     {
1400         std::copy(value, value + extent, dst);
1401     }
1402 
1403     // Compare for equality.
operator ==__anon980b96790111::_MakeIndexedValue1404     bool operator==(const _MakeIndexedValue<POD, extent>& rhs) const
1405     {
1406         return std::equal(value, value + extent, rhs.value);
1407     }
1408 
1409     // Compare for less-than.
operator <__anon980b96790111::_MakeIndexedValue1410     bool operator<(const _MakeIndexedValue<POD, extent>& rhs) const
1411     {
1412         for (size_t i = 0; i != extent; ++i) {
1413             if (value[i] < rhs.value[i]) {
1414                 return true;
1415             }
1416             if (rhs.value[i] < value[i]) {
1417                 return false;
1418             }
1419         }
1420         return false;
1421     }
1422 };
1423 
1424 /// Make the values indexed.  This stores only unique values and makes
1425 /// an index vector with an element for each original value indexing the
1426 /// unique value.
1427 template<class POD, size_t extent>
1428 static
1429 void
_MakeIndexed(_SampleForAlembic * values)1430 _MakeIndexed(_SampleForAlembic* values)
1431 {
1432     // A map of values to an index.
1433     typedef _MakeIndexedValue<POD, extent> Value;
1434     typedef std::map<Value, uint32_t> IndexMap;
1435     typedef std::pair<typename IndexMap::iterator, bool> IndexMapResult;
1436     typedef _SampleForAlembic::IndexArray IndexArray;
1437     typedef IndexArray::value_type Index;
1438 
1439     // Allocate a vector of indices with the right size.
1440     const size_t n = values->GetCount() / extent;
1441     _SampleForAlembic::IndexArrayPtr indicesPtr(new IndexArray(n, 0));
1442     IndexArray& indices = *indicesPtr;
1443 
1444     // Find unique values.
1445     Index index = 0;
1446     IndexMap indexMap;
1447     std::vector<Value> unique;
1448     const POD* ptr = values->GetDataAs<POD>();
1449     for (size_t i = 0; i != n; ptr += extent, ++i) {
1450         const IndexMapResult result =
1451             indexMap.insert(std::make_pair(Value(ptr), index));
1452         if (result.second) {
1453             // Found a unique value.
1454             unique.push_back(result.first->first);
1455             ++index;
1456         }
1457         indices[i] = result.first->second;
1458     }
1459 
1460     // If there are enough duplicates use indexing otherwise don't.
1461     if (n * sizeof(POD[extent]) <=
1462             unique.size() * sizeof(POD[extent]) + n * sizeof(uint32_t)) {
1463         return;
1464     }
1465 
1466     // Build the result.
1467     const size_t numPODs = extent * unique.size();
1468     boost::shared_array<POD> uniqueBuffer(new POD[numPODs]);
1469     for (size_t i = 0, n = unique.size(); i != n; ++i) {
1470         unique[i].CopyTo(uniqueBuffer.get() + i * extent);
1471     }
1472 
1473     // Create a new sample object with the indexes.
1474     _SampleForAlembic result(uniqueBuffer, numPODs);
1475     result.SetIndices(indicesPtr);
1476 
1477     // Cut over.
1478     *values = result;
1479 }
1480 
1481 //
1482 // Data copy/conversion functions.
1483 //
1484 
1485 // Copy to a T.
1486 template <class DST, class R, class T>
1487 void
_Copy(const _WriterSchema & schema,double time,const UsdSamples & samples,DST * dst,R (DST::* method)(T))1488 _Copy(
1489     const _WriterSchema& schema,
1490     double time,
1491     const UsdSamples& samples,
1492     DST* dst,
1493     R (DST::*method)(T))
1494 {
1495     typedef typename boost::remove_const<
1496                 typename boost::remove_reference<T>::type
1497             >::type SampleValueType;
1498 
1499     const SdfValueTypeName& usdType = samples.GetTypeName();
1500     const _WriterSchema::Converter& converter = schema.GetConverter(usdType);
1501 
1502     // Make a sample holding a value of type SampleValueType.
1503     _SampleForAlembic sample =
1504         _MakeSample<SampleValueType>(schema, converter,
1505                                      usdType, samples.Get(time));
1506     if (! _CheckSample(sample, samples, usdType)) {
1507         return;
1508     }
1509 
1510     // Write to dst.
1511     (dst->*method)(*sample.GetDataAs<SampleValueType>());
1512 }
1513 
1514 // Copy to a T with explicit converter.
1515 template <class DST, class R, class T>
1516 void
_Copy(const _WriterSchema & schema,const _WriterSchema::Converter & converter,double time,const UsdSamples & samples,DST * dst,R (DST::* method)(T))1517 _Copy(
1518     const _WriterSchema& schema,
1519     const _WriterSchema::Converter& converter,
1520     double time,
1521     const UsdSamples& samples,
1522     DST* dst,
1523     R (DST::*method)(T))
1524 {
1525     typedef typename boost::remove_const<
1526                 typename boost::remove_reference<T>::type
1527             >::type SampleValueType;
1528 
1529     const SdfValueTypeName& usdType = samples.GetTypeName();
1530 
1531     // Make a sample holding a value of type SampleValueType.
1532     static const bool skipAlembicTypeCheck = true;
1533     _SampleForAlembic sample =
1534         _MakeSample<SampleValueType>(schema, converter,
1535                                      usdType, samples.Get(time),
1536                                      skipAlembicTypeCheck);
1537     if (! _CheckSample(sample, samples, usdType)) {
1538         return;
1539     }
1540 
1541     // Write to dst.
1542     (dst->*method)(*sample.GetDataAs<SampleValueType>());
1543 }
1544 
1545 // Copy to a TypedArraySample<T>.  The client *must* hold the returned
1546 // _SampleForAlembic until the sample is finally consumed.
1547 template <class DST, class R, class T>
1548 _SampleForAlembic
_Copy(const _WriterSchema & schema,const _WriterSchema::Converter & converter,double time,const UsdSamples & samples,DST * dst,R (DST::* method)(const TypedArraySample<T> &),bool skipAlembicTypeCheck=true)1549 _Copy(
1550     const _WriterSchema& schema,
1551     const _WriterSchema::Converter& converter,
1552     double time,
1553     const UsdSamples& samples,
1554     DST* dst,
1555     R (DST::*method)(const TypedArraySample<T>&),
1556     bool skipAlembicTypeCheck = true)
1557 {
1558     typedef T SampleValueTraits;
1559     typedef typename SampleValueTraits::value_type Type;
1560     typedef TypedArraySample<SampleValueTraits> AlembicSample;
1561 
1562     // The property is ultimately an array of this type where each element
1563     // has length extent.
1564     typedef typename PODTraitsFromEnum<
1565                         SampleValueTraits::pod_enum>::value_type PodType;
1566     static const int extent = SampleValueTraits::extent;
1567 
1568     const SdfValueTypeName& usdType = samples.GetTypeName();
1569 
1570     // Make a sample holding an array of type PodType.
1571     _SampleForAlembic sample =
1572         _MakeSample<PodType, extent>(schema, converter,
1573                                      usdType, samples.Get(time),
1574                                      skipAlembicTypeCheck);
1575     if (! _CheckSample(sample, samples, usdType)) {
1576         return sample;
1577     }
1578 
1579     // Write to dst.
1580     (dst->*method)(AlembicSample(sample.GetDataAs<Type>(),
1581                                  sample.GetCount() / extent));
1582 
1583     return sample;
1584 }
1585 
1586 // Copy to a TypedArraySample<T>.  The client *must* hold the returned
1587 // _SampleForAlembic until the sample is finally consumed.
1588 template <class DST, class R, class T>
1589 _SampleForAlembic
_Copy(const _WriterSchema & schema,double time,const UsdSamples & samples,DST * dst,R (DST::* method)(const TypedArraySample<T> &))1590 _Copy(
1591     const _WriterSchema& schema,
1592     double time,
1593     const UsdSamples& samples,
1594     DST* dst,
1595     R (DST::*method)(const TypedArraySample<T>&))
1596 {
1597     static const bool skipAlembicTypeCheck = true;
1598     return _Copy(schema, schema.GetConverter(samples.GetTypeName()),
1599                  time, samples, dst, method, !skipAlembicTypeCheck);
1600 }
1601 
1602 // Copy to a OTypedGeomParam<T>::Sample.  The client *must* hold the returned
1603 // _SampleForAlembic until the sample is finally consumed.
1604 // XXX: For some reason the compiler can't deduce the type T so clients
1605 //      are forced to provide it.
1606 template <class T, class DST, class R>
1607 _SampleForAlembic
_Copy(const _WriterSchema & schema,double time,const UsdSamples & valueSamples,DST * sample,R (DST::* method)(const typename OTypedGeomParam<T>::Sample &))1608 _Copy(
1609     const _WriterSchema& schema,
1610     double time,
1611     const UsdSamples& valueSamples,
1612     DST* sample,
1613     R (DST::*method)(const typename OTypedGeomParam<T>::Sample&))
1614 {
1615     typedef T SampleValueTraits;
1616     typedef typename SampleValueTraits::value_type Type;
1617     typedef typename OTypedGeomParam<SampleValueTraits>::Sample AlembicSample;
1618     typedef TypedArraySample<SampleValueTraits> AlembicValueSample;
1619 
1620     // The property is ultimately an array of this type where each element
1621     // has length extent.
1622     typedef typename PODTraitsFromEnum<
1623                         SampleValueTraits::pod_enum>::value_type PodType;
1624     static const int extent = SampleValueTraits::extent;
1625 
1626     const SdfValueTypeName& usdType = valueSamples.GetTypeName();
1627     const _WriterSchema::Converter& converter = schema.GetConverter(usdType);
1628 
1629     // Make a sample holding an array of type PodType.
1630     _SampleForAlembic vals =
1631         _MakeSample<PodType, extent>(schema, converter,
1632                                      usdType, valueSamples.Get(time));
1633     if (! _CheckSample(vals, valueSamples, usdType)) {
1634         return vals;
1635     }
1636 
1637     // Get the interpolation.
1638     VtValue value = valueSamples.GetField(UsdGeomTokens->interpolation);
1639     const GeometryScope geoScope =
1640         value.IsHolding<TfToken>()
1641             ? _GetGeometryScope(value.UncheckedGet<TfToken>())
1642             : kUnknownScope;
1643 
1644     // Make the values indexed if desired.
1645     _MakeIndexed<PodType, extent>(&vals);
1646 
1647     // Get the indices.
1648     if (_SampleForAlembic::IndexArrayPtr indicesPtr = vals.GetIndices()) {
1649         // Note that the indices pointer is valid so long as vals is valid.
1650         const _SampleForAlembic::IndexArray& indices = *indicesPtr;
1651         UInt32ArraySample indicesSample(&indices[0], indices.size());
1652         (sample->*method)(
1653             AlembicSample(
1654                 AlembicValueSample(vals.GetDataAs<Type>(),
1655                                    vals.GetCount() / extent),
1656                 indicesSample,
1657                 geoScope));
1658     }
1659     else {
1660         (sample->*method)(
1661             AlembicSample(
1662                 AlembicValueSample(vals.GetDataAs<Type>(),
1663                                    vals.GetCount() / extent),
1664                 geoScope));
1665     }
1666 
1667     return vals;
1668 }
1669 
1670 // Copy to a scalar property.
1671 static
1672 void
_Copy(const _WriterSchema & schema,const _WriterSchema::Converter & converter,double time,const UsdSamples & samples,OScalarProperty * property)1673 _Copy(
1674     const _WriterSchema& schema,
1675     const _WriterSchema::Converter& converter,
1676     double time,
1677     const UsdSamples& samples,
1678     OScalarProperty* property)
1679 {
1680     const SdfValueTypeName& usdType = samples.GetTypeName();
1681     const DataType& dataType = property->getDataType();
1682 
1683     // Make a sample holding a value of the type expected by the property.
1684     static const bool skipAlembicTypeCheck = true;
1685     _SampleForAlembic sample =
1686         _MakeSample(schema, converter, usdType,
1687                     samples.Get(time), dataType,
1688                     skipAlembicTypeCheck);
1689     if (! _CheckSample(sample, samples, usdType)) {
1690         return;
1691     }
1692 
1693     // Write to dst.
1694     property->set(sample.GetData());
1695 }
1696 
1697 // Copy to an array property.
1698 static
1699 void
_Copy(const _WriterSchema & schema,const _WriterSchema::Converter & converter,double time,const UsdSamples & samples,OArrayProperty * property)1700 _Copy(
1701     const _WriterSchema& schema,
1702     const _WriterSchema::Converter& converter,
1703     double time,
1704     const UsdSamples& samples,
1705     OArrayProperty* property)
1706 {
1707     const SdfValueTypeName& usdType = samples.GetTypeName();
1708     const DataType& dataType = property->getDataType();
1709 
1710     // Make a sample holding an array of the type expected by the property.
1711     static const bool skipAlembicTypeCheck = true;
1712     _SampleForAlembic sample =
1713         _MakeSample(schema, converter, usdType,
1714                     samples.Get(time), dataType,
1715                     skipAlembicTypeCheck);
1716     if (! _CheckSample(sample, samples, usdType)) {
1717         return;
1718     }
1719 
1720     // Write to dst.
1721     Dimensions count(sample.GetCount() / dataType.getExtent());
1722     property->set(ArraySample(sample.GetData(), dataType, count));
1723 }
1724 
1725 static
1726 void
_CopyXform(double time,const UsdSamples & samples,XformSample * sample)1727 _CopyXform(
1728     double time,
1729     const UsdSamples& samples,
1730     XformSample* sample)
1731 {
1732     const VtValue value = samples.Get(time);
1733     if (value.IsHolding<GfMatrix4d>()) {
1734         const GfMatrix4d& transform = value.UncheckedGet<GfMatrix4d>();
1735         sample->addOp(
1736             XformOp(kMatrixOperation, kMatrixHint),
1737             M44d(reinterpret_cast<const double(*)[4]>(transform.GetArray())));
1738     }
1739     else {
1740         TF_WARN("Expected type 'GfMatrix4d', got '%s'",
1741                 ArchGetDemangled(value.GetTypeName()).c_str());
1742     }
1743 }
1744 
1745 template <class DST>
1746 static
1747 void
_CopySelfBounds(double time,const UsdSamples & samples,DST * sample)1748 _CopySelfBounds(
1749     double time,
1750     const UsdSamples& samples,
1751     DST* sample)
1752 {
1753     const VtValue value = samples.Get(time);
1754     if (value.IsHolding<VtArray<GfVec3f> >()) {
1755         const VtArray<GfVec3f>& a = value.UncheckedGet<VtArray<GfVec3f> >();
1756         Box3d box(V3d(a[0][0], a[0][1], a[0][2]),
1757                   V3d(a[1][0], a[1][1], a[1][2]));
1758         sample->setSelfBounds(box);
1759     }
1760     else {
1761         TF_WARN("Expected type 'VtArray<GfVec3f>', got '%s'",
1762                 ArchGetDemangled(value.GetTypeName()).c_str());
1763     }
1764 }
1765 
1766 static
1767 _SampleForAlembic
_CopyVisibility(const VtValue & src)1768 _CopyVisibility(const VtValue& src)
1769 {
1770     const TfToken& value = src.UncheckedGet<TfToken>();
1771     if (value.IsEmpty() || value == UsdGeomTokens->inherited) {
1772         return _SampleForAlembic(int8_t(kVisibilityDeferred));
1773     }
1774     if (value == UsdGeomTokens->invisible) {
1775         return _SampleForAlembic(int8_t(kVisibilityHidden));
1776     }
1777     return _ErrorSampleForAlembic(TfStringPrintf(
1778                             "Unsupported invisibility '%s'",
1779                             value.GetText()));
1780 }
1781 
1782 static
1783 _SampleForAlembic
_CopySubdivisionScheme(const VtValue & src)1784 _CopySubdivisionScheme(const VtValue& src)
1785 {
1786     const TfToken& value = src.UncheckedGet<TfToken>();
1787     if (value.IsEmpty() || value == UsdGeomTokens->catmullClark) {
1788         return _SampleForAlembic(std::string("catmull-clark"));
1789     }
1790     if (value == UsdGeomTokens->loop) {
1791         return _SampleForAlembic(std::string("loop"));
1792     }
1793     if (value == UsdGeomTokens->bilinear) {
1794         return _SampleForAlembic(std::string("bilinear"));
1795     }
1796     return _ErrorSampleForAlembic(TfStringPrintf(
1797                             "Unsupported subdivisionScheme '%s'",
1798                             value.GetText()));
1799 }
1800 
1801 static
1802 _SampleForAlembic
_CopyInterpolateBoundary(const VtValue & src)1803 _CopyInterpolateBoundary(const VtValue& src)
1804 {
1805     const TfToken& value = src.UncheckedGet<TfToken>();
1806     if (value.IsEmpty() || value == UsdGeomTokens->edgeAndCorner) {
1807         return _SampleForAlembic(int32_t(1));
1808     }
1809     if (value == UsdGeomTokens->none) {
1810         return _SampleForAlembic(int32_t(0));
1811     }
1812     if (value == UsdGeomTokens->edgeOnly) {
1813         return _SampleForAlembic(int32_t(2));
1814     }
1815     return _ErrorSampleForAlembic(TfStringPrintf(
1816                             "Unsupported interpolateBoundary '%s'",
1817                             value.GetText()));
1818 }
1819 
1820 static
1821 _SampleForAlembic
_CopyFaceVaryingInterpolateBoundary(const VtValue & src)1822 _CopyFaceVaryingInterpolateBoundary(const VtValue& src)
1823 {
1824     const TfToken& value = src.UncheckedGet<TfToken>();
1825     if (value.IsEmpty() || value == UsdGeomTokens->cornersPlus1 ||
1826         value == UsdGeomTokens->cornersOnly ||
1827         value == UsdGeomTokens->cornersPlus2) {
1828         return _SampleForAlembic(int32_t(1));
1829     }
1830     if (value == UsdGeomTokens->all) {
1831         return _SampleForAlembic(int32_t(0));
1832     }
1833     if (value == UsdGeomTokens->none) {
1834         return _SampleForAlembic(int32_t(2));
1835     }
1836     if (value == UsdGeomTokens->boundaries) {
1837         return _SampleForAlembic(int32_t(3));
1838     }
1839     return _ErrorSampleForAlembic(TfStringPrintf(
1840                             "Unsupported faceVaryingLinearInterpolation '%s'",
1841                             value.GetText()));
1842 }
1843 
1844 static
1845 _SampleForAlembic
_CopyAdskColor(const VtValue & src)1846 _CopyAdskColor(const VtValue& src)
1847 {
1848     const VtArray<GfVec3f>& color = src.UncheckedGet<VtArray<GfVec3f> >();
1849     std::vector<float> result(color[0].GetArray(), color[0].GetArray() + 3);
1850     result.push_back(1.0);
1851     return _SampleForAlembic(result);
1852 }
1853 
1854 static
1855 _SampleForAlembic
_CopyCurveBasis(const VtValue & src)1856 _CopyCurveBasis(const VtValue& src)
1857 {
1858     const TfToken& value = src.UncheckedGet<TfToken>();
1859     if (value.IsEmpty() || value == UsdGeomTokens->none) {
1860         return _SampleForAlembic(kNoBasis);
1861     }
1862     if (value == UsdGeomTokens->bezier) {
1863         return _SampleForAlembic(kBezierBasis);
1864     }
1865     if (value == UsdGeomTokens->bspline) {
1866         return _SampleForAlembic(kBsplineBasis);
1867     }
1868     if (value == UsdGeomTokens->catmullRom) {
1869         return _SampleForAlembic(kCatmullromBasis);
1870     }
1871     return _ErrorSampleForAlembic(TfStringPrintf(
1872                             "Unsupported curve basis '%s'",
1873                             value.GetText()));
1874 }
1875 
1876 static
1877 _SampleForAlembic
_CopyCurveType(const VtValue & src)1878 _CopyCurveType(const VtValue& src)
1879 {
1880     const TfToken& value = src.UncheckedGet<TfToken>();
1881     if (value.IsEmpty() || value == UsdGeomTokens->none) {
1882         return _SampleForAlembic(kCubic);
1883     }
1884     if (value == UsdGeomTokens->linear) {
1885         return _SampleForAlembic(kLinear);
1886     }
1887     if (value == UsdGeomTokens->cubic) {
1888         return _SampleForAlembic(kCubic);
1889     }
1890     return _ErrorSampleForAlembic(TfStringPrintf(
1891                             "Unsupported curve type '%s'",
1892                             value.GetText()));
1893 }
1894 
1895 static
1896 _SampleForAlembic
_CopyCurveWrap(const VtValue & src)1897 _CopyCurveWrap(const VtValue& src)
1898 {
1899     const TfToken& value = src.UncheckedGet<TfToken>();
1900     if (value.IsEmpty() || value == UsdGeomTokens->none) {
1901         return _SampleForAlembic(kNonPeriodic);
1902     }
1903     if (value == UsdGeomTokens->nonperiodic) {
1904         return _SampleForAlembic(kNonPeriodic);
1905     }
1906     if (value == UsdGeomTokens->periodic) {
1907         return _SampleForAlembic(kPeriodic);
1908     }
1909     return _ErrorSampleForAlembic(TfStringPrintf(
1910                             "Unsupported curve wrap '%s'",
1911                             value.GetText()));
1912 }
1913 
1914 static
1915 _SampleForAlembic
_CopyKnots(const VtValue & src)1916 _CopyKnots(const VtValue& src)
1917 {
1918     const VtDoubleArray& value = src.UncheckedGet<VtDoubleArray>();
1919     return _SampleForAlembic(std::vector<float>(value.begin(), value.end()));
1920 }
1921 
1922 static
1923 _SampleForAlembic
_CopyOrder(const VtValue & src)1924 _CopyOrder(const VtValue& src)
1925 {
1926     const VtIntArray& value = src.UncheckedGet<VtIntArray>();
1927     return _SampleForAlembic(std::vector<uint8_t>(value.begin(), value.end()));
1928 }
1929 
1930 static
1931 _SampleForAlembic
_CopyPointIds(const VtValue & src)1932 _CopyPointIds(const VtValue& src)
1933 {
1934     const VtInt64Array& value = src.UncheckedGet<VtInt64Array>();
1935     return _SampleForAlembic(std::vector<uint64_t>(value.begin(), value.end()));
1936 }
1937 
1938 // ----------------------------------------------------------------------------
1939 
1940 //
1941 // Property writers
1942 //
1943 
1944 static
1945 VtValue
_GetField(const _PrimWriterContext & context,const TfToken & field,const TfToken & usdName)1946 _GetField(
1947     const _PrimWriterContext& context,
1948     const TfToken& field,
1949     const TfToken& usdName)
1950 {
1951     return usdName.IsEmpty() ? context.GetField(field)
1952                              : context.GetPropertyField(usdName, field);
1953 }
1954 
1955 static
1956 void
_SetBoolMetadata(MetaData * metadata,const _PrimWriterContext & context,const TfToken & field,const TfToken & usdName=TfToken ())1957 _SetBoolMetadata(
1958     MetaData* metadata,
1959     const _PrimWriterContext& context,
1960     const TfToken& field,
1961     const TfToken& usdName = TfToken())
1962 {
1963     VtValue value = _GetField(context, field, usdName);
1964     if (value.IsHolding<bool>()) {
1965         metadata->set(_AmdName(field),
1966                       value.UncheckedGet<bool>() ? "true" : "false");
1967     }
1968 }
1969 
1970 static
1971 void
_SetStringMetadata(MetaData * metadata,const _PrimWriterContext & context,const TfToken & field,const TfToken & usdName=TfToken ())1972 _SetStringMetadata(
1973     MetaData* metadata,
1974     const _PrimWriterContext& context,
1975     const TfToken& field,
1976     const TfToken& usdName = TfToken())
1977 {
1978     VtValue value = _GetField(context, field, usdName);
1979     if (value.IsHolding<std::string>()) {
1980         const std::string& tmp = value.UncheckedGet<std::string>();
1981         if (! tmp.empty()) {
1982             metadata->set(_AmdName(field), tmp);
1983         }
1984     }
1985 }
1986 
1987 static
1988 void
_SetTokenMetadata(MetaData * metadata,const _PrimWriterContext & context,const TfToken & field,const TfToken & usdName=TfToken ())1989 _SetTokenMetadata(
1990     MetaData* metadata,
1991     const _PrimWriterContext& context,
1992     const TfToken& field,
1993     const TfToken& usdName = TfToken())
1994 {
1995     VtValue value = _GetField(context, field, usdName);
1996     if (value.IsHolding<TfToken>()) {
1997         const TfToken& tmp = value.UncheckedGet<TfToken>();
1998         if (! tmp.IsEmpty()) {
1999             metadata->set(_AmdName(field), tmp);
2000         }
2001     }
2002 }
2003 
2004 static
2005 void
_SetDoubleMetadata(MetaData * metadata,const _PrimWriterContext & context,const TfToken & field,const TfToken & usdName=TfToken ())2006 _SetDoubleMetadata(
2007     MetaData* metadata,
2008     const _PrimWriterContext& context,
2009     const TfToken& field,
2010     const TfToken& usdName = TfToken())
2011 {
2012     VtValue value = _GetField(context, field, usdName);
2013     if (value.IsHolding<double>()) {
2014         metadata->set(_AmdName(field), TfStringify(value));
2015     }
2016 }
2017 
2018 static
2019 MetaData
_GetPropertyMetadata(const _PrimWriterContext & context,const TfToken & usdName,const UsdSamples & samples)2020 _GetPropertyMetadata(
2021     const _PrimWriterContext& context,
2022     const TfToken& usdName,
2023     const UsdSamples& samples)
2024 {
2025     MetaData metadata;
2026 
2027     VtValue value;
2028 
2029     // Custom.
2030     _SetBoolMetadata(&metadata, context, SdfFieldKeys->Custom, usdName);
2031 
2032     // Write the usd type for exact reverse conversion if we can't deduce it
2033     // when reading the Alembic.
2034     value = context.GetPropertyField(usdName, SdfFieldKeys->TypeName);
2035     const TfToken typeNameToken =
2036         value.IsHolding<TfToken>() ? value.UncheckedGet<TfToken>() : TfToken();
2037     const SdfValueTypeName typeName =
2038         SdfSchema::GetInstance().FindType(typeNameToken);
2039     const SdfValueTypeName roundTripTypeName =
2040         context.GetSchema().FindConverter(
2041             context.GetSchema().FindConverter(typeName));
2042     if (typeName != roundTripTypeName) {
2043         metadata.set(_AmdName(SdfFieldKeys->TypeName), typeNameToken);
2044     }
2045 
2046     // Note a "winning" Default as a single alembic sample that should come
2047     // back into USD as a Default
2048     if (!samples.IsTimeSampled() && samples.GetNumSamples() == 1) {
2049         metadata.set(_AmdName(UsdAbcCustomMetadata->singleSampleAsDefault),
2050                      "true");
2051     }
2052 
2053     // Set the interpretation if there is one.
2054     const std::string interpretation = _GetInterpretation(typeName);
2055     if (! interpretation.empty()) {
2056         metadata.set("interpretation", interpretation);
2057     }
2058 
2059     // Other Sdf metadata.
2060     _SetStringMetadata(&metadata, context, SdfFieldKeys->DisplayGroup, usdName);
2061     _SetStringMetadata(&metadata, context, SdfFieldKeys->Documentation,usdName);
2062     _SetBoolMetadata(&metadata, context, SdfFieldKeys->Hidden, usdName);
2063     value = context.GetPropertyField(usdName, SdfFieldKeys->Variability);
2064     if (value.IsHolding<SdfVariability>() &&
2065             value.UncheckedGet<SdfVariability>() ==
2066                 SdfVariabilityUniform) {
2067         metadata.set(_AmdName(SdfFieldKeys->Variability), "uniform");
2068     }
2069     value = context.GetPropertyField(usdName, UsdGeomTokens->interpolation);
2070     if (value.IsHolding<TfToken>()) {
2071         SetGeometryScope(metadata,
2072                          _GetGeometryScope(value.UncheckedGet<TfToken>()));
2073     }
2074 
2075     // Custom metadata.
2076     _SetStringMetadata(&metadata, context,
2077                        UsdAbcCustomMetadata->riName, usdName);
2078     _SetStringMetadata(&metadata, context,
2079                        UsdAbcCustomMetadata->riType, usdName);
2080     _SetBoolMetadata(&metadata, context,
2081                      UsdAbcCustomMetadata->gprimDataRender, usdName);
2082 
2083     return metadata;
2084 }
2085 
2086 static
2087 bool
_WriteOutOfSchemaProperty(_PrimWriterContext * context,OCompoundProperty parent,const TfToken & usdName,const std::string & alembicName)2088 _WriteOutOfSchemaProperty(
2089     _PrimWriterContext* context,
2090     OCompoundProperty parent,
2091     const TfToken& usdName,
2092     const std::string& alembicName)
2093 {
2094     // Ignore non-attributes.
2095     if (context->GetSpecType(usdName) != SdfSpecTypeAttribute) {
2096         if (context->IsFlagSet(UsdAbc_AlembicContextFlagNames->verbose)) {
2097             TF_WARN("No conversion for <%s> with spec type '%s'",
2098                     context->GetPath().AppendProperty(usdName).GetText(),
2099                     TfEnum::GetDisplayName(
2100                         context->GetSpecType(usdName)).c_str());
2101         }
2102         return false;
2103     }
2104 
2105     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2106     UsdSamples samples = context->ExtractSamples(usdName);
2107     if (context->GetSchema().IsValid(samples)) {
2108         const SdfValueTypeName& usdType = samples.GetTypeName();
2109         const _WriterSchema::Converter& converter =
2110             context->GetSchema().GetConverter(usdType);
2111         if (context->GetSchema().IsShaped(samples)) {
2112             OArrayProperty property(parent, alembicName,
2113                                     context->GetSchema().GetDataType(samples),
2114                                     _GetPropertyMetadata(*context, usdName,
2115                                                          samples));
2116             for (double time : context->GetSampleTimesUnion()) {
2117                 _Copy(context->GetSchema(), converter, time, samples,&property);
2118             }
2119             property.setTimeSampling(
2120                 context->AddTimeSampling(context->GetSampleTimesUnion()));
2121         }
2122         else {
2123             OScalarProperty property(parent, alembicName,
2124                                      context->GetSchema().GetDataType(samples),
2125                                      _GetPropertyMetadata(*context, usdName,
2126                                                           samples));
2127             for (double time : context->GetSampleTimesUnion()) {
2128                 _Copy(context->GetSchema(), converter, time, samples,&property);
2129             }
2130             property.setTimeSampling(
2131                 context->AddTimeSampling(context->GetSampleTimesUnion()));
2132         }
2133         return true;
2134     }
2135     else {
2136         return false;
2137     }
2138 }
2139 
2140 template <class T>
2141 static
2142 void
_WriteGenericProperty(_PrimWriterContext * context,OCompoundProperty parent,const _WriterSchema::Converter & converter,const DataType & alembicDataType,const TfToken & usdName,const std::string & alembicName)2143 _WriteGenericProperty(
2144     _PrimWriterContext* context,
2145     OCompoundProperty parent,
2146     const _WriterSchema::Converter& converter,
2147     const DataType& alembicDataType,
2148     const TfToken& usdName,
2149     const std::string& alembicName)
2150 {
2151     // Collect the properties we need.
2152     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2153     UsdSamples samples = context->ExtractSamples(usdName);
2154     if (context->GetSchema().IsValid(samples)) {
2155         T property(parent, alembicName, alembicDataType,
2156                    _GetPropertyMetadata(*context, usdName, samples));
2157         for (double time : context->GetSampleTimesUnion()) {
2158             _Copy(context->GetSchema(), converter, time, samples, &property);
2159         }
2160         property.setTimeSampling(
2161             context->AddTimeSampling(context->GetSampleTimesUnion()));
2162     }
2163 }
2164 
2165 static
2166 void
_WriteGenericScalar(_PrimWriterContext * context,const _WriterSchema::Converter & converter,const DataType & alembicDataType,const TfToken & usdName,const std::string & alembicName)2167 _WriteGenericScalar(
2168     _PrimWriterContext* context,
2169     const _WriterSchema::Converter& converter,
2170     const DataType& alembicDataType,
2171     const TfToken& usdName,
2172     const std::string& alembicName)
2173 {
2174     _WriteGenericProperty<OScalarProperty>(context,
2175                                            context->GetParent().GetProperties(),
2176                                            converter, alembicDataType, usdName,
2177                                            alembicName);
2178 }
2179 
2180 /* Not currently used.
2181 static
2182 void
2183 _WriteGenericArray(
2184     _PrimWriterContext* context,
2185     const TfToken& usdType,
2186     const TfToken& usdName,
2187     const std::string& alembicName)
2188 {
2189     // XXX: This doesn't have the correct arguments.
2190     _WriteGenericProperty<OArrayProperty>(context,
2191                                           context->GetParent().GetProperties(),
2192                                           conv, usdName, alembicName);
2193 }
2194 */
2195 
2196 //
2197 // Abstract object writers
2198 //
2199 
2200 // Helper for converting property namespaces into a hierarchy of
2201 // OCompoundProperty.
2202 class _CompoundPropertyTable {
2203 public:
_CompoundPropertyTable(OCompoundProperty root)2204     _CompoundPropertyTable(OCompoundProperty root)
2205 {
2206         _table[TfTokenVector()] = root;
2207     }
2208 
2209     OCompoundProperty FindOrCreate(const TfTokenVector& names);
2210 
2211 private:
2212     OCompoundProperty _FindOrCreate(TfTokenVector& names);
2213 
2214 private:
2215     std::map<TfTokenVector, OCompoundProperty> _table;
2216 };
2217 
2218 OCompoundProperty
FindOrCreate(const TfTokenVector & names)2219 _CompoundPropertyTable::FindOrCreate(const TfTokenVector& names)
2220 {
2221     OCompoundProperty result = _table[names];
2222     if (! result.valid()) {
2223         TfTokenVector tmpNames = names;
2224         return _FindOrCreate(tmpNames);
2225     }
2226     else {
2227         return result;
2228     }
2229 }
2230 
2231 OCompoundProperty
_FindOrCreate(TfTokenVector & names)2232 _CompoundPropertyTable::_FindOrCreate(TfTokenVector& names)
2233 {
2234     OCompoundProperty& result = _table[names];
2235     if (! result.valid()) {
2236         // We don't have an entry for this path.  Recursively get parent
2237         // and add the child.
2238         TfToken name = names.back();
2239         names.pop_back();
2240         OCompoundProperty parent = _FindOrCreate(names);
2241         result = OCompoundProperty(parent, name);
2242     }
2243     return result;
2244 }
2245 
2246 static
2247 void
_WriteNamespacedPropertyGroup(_PrimWriterContext * context,const TfToken & namespaceName,const std::function<OCompoundProperty ()> & getParentProperty)2248 _WriteNamespacedPropertyGroup(
2249     _PrimWriterContext* context,
2250     const TfToken& namespaceName,
2251     const std::function<OCompoundProperty()>& getParentProperty)
2252 {
2253     // First check if there are any properties to convert.  We only ask
2254     // for that property if so, because asking for it will create it on
2255     // demand and we don't want to create it if unnecessary.  Note,
2256     // however, that we don't confirm that conversion will succeed so
2257     // we may still create the property with nothing in it.
2258     bool anyProperties = false;
2259     for (const auto& name : context->GetUnextractedNames()) {
2260         TfTokenVector names = SdfPath::TokenizeIdentifierAsTokens(name);
2261         if (names.size() >= 2 && names[0] == namespaceName) {
2262             anyProperties = true;
2263             break;
2264         }
2265     }
2266 
2267     // Convert everything in the namespace into the parent compound property.
2268     // Strip the namespace name from each name before copying.
2269     if (anyProperties) {
2270         OCompoundProperty parent = getParentProperty();
2271         if (!parent.valid()) {
2272             // We can't get the parent property.  Just put the properties
2273             // at the top level.
2274             parent = context->GetParent().GetProperties();
2275         }
2276 
2277         // Support sub-namespaces as compound properties.
2278         _CompoundPropertyTable subgroups(parent);
2279 
2280         // Convert each property.
2281         // We have to remap primvars:st:indices to primvars:uv:indices.
2282         for (const auto& name : context->GetUnextractedNames()) {
2283             TfTokenVector names =
2284                 name == UsdAbcPropertyNames->stIndices ?
2285                     SdfPath::TokenizeIdentifierAsTokens(
2286                         UsdAbcPropertyNames->uvIndices) :
2287                     SdfPath::TokenizeIdentifierAsTokens(name);
2288             if (names.size() >= 2 && names[0] == namespaceName) {
2289                 // Remove the namespace prefix.
2290                 names.erase(names.begin());
2291 
2292                 // The Alembic name is just the last name (i.e. no namespaces).
2293                 const std::string alembicName = names.back();
2294                 names.pop_back();
2295 
2296                 // Get/create the subgroup compound property.
2297                 OCompoundProperty group = subgroups.FindOrCreate(names);
2298 
2299                 // Write it.
2300                 _WriteOutOfSchemaProperty(context, group, name, alembicName);
2301             }
2302         }
2303     }
2304 }
2305 
2306 static
2307 void
_WriteArbGeomParams(_PrimWriterContext * context)2308 _WriteArbGeomParams(_PrimWriterContext* context)
2309 {
2310     // Convert everything in the primvars namespace to the getArbGeomParams()
2311     // compound property.
2312     const _Parent& parent = context->GetParent();
2313     _WriteNamespacedPropertyGroup(context,
2314                                   UsdAbcPropertyNames->primvars,
2315                                   std::bind(&_Parent::GetArbGeomParams,
2316                                             std::cref(parent)));
2317 }
2318 
2319 static
2320 void
_WriteUserProperties(_PrimWriterContext * context)2321 _WriteUserProperties(_PrimWriterContext* context)
2322 {
2323     // Convert everything in the userProperties namespace to the
2324     // getUserProperties() compound property.
2325     const _Parent& parent = context->GetParent();
2326     _WriteNamespacedPropertyGroup(context,
2327                                   UsdAbcPropertyNames->userProperties,
2328                                   std::bind(&_Parent::GetUserProperties,
2329                                             std::cref(parent)));
2330     }
2331 
2332 static
2333 void
_WriteGprim(_PrimWriterContext * context)2334 _WriteGprim(_PrimWriterContext* context)
2335 {
2336     // extent is handled by GeomBase subclasses automatically.
2337 
2338     // Write the orientation.
2339     _WriteOutOfSchemaProperty(context, context->GetParent().GetProperties(),
2340                               UsdGeomTokens->orientation,
2341                               _AmdName(UsdGeomTokens->orientation));
2342 }
2343 
2344 static
2345 void
_WriteMayaColor(_PrimWriterContext * context)2346 _WriteMayaColor(_PrimWriterContext* context)
2347 {
2348     static const TfToken displayColor("primvars:displayColor");
2349     static const TfToken name("adskDiffuseColor");
2350 
2351     UsdSamples color(context->GetPath(), displayColor);
2352     if (context->GetData().HasSpec(
2353             context->GetPath().AppendProperty(displayColor))) {
2354         color =
2355             UsdSamples(context->GetPath(), displayColor, context->GetData());
2356     }
2357     if (color.IsEmpty()) {
2358         // Copy existing Maya color.
2359         if (! _WriteOutOfSchemaProperty(context,
2360                                         context->GetParent().GetSchema(),
2361                                         name, name)) {
2362             return;
2363         }
2364     }
2365     else {
2366         // Use displayColor.
2367         UsdAbc_TimeSamples sampleTimes;
2368         color.AddTimes(&sampleTimes);
2369 
2370         MetaData metadata;
2371         metadata.set("interpretation", "rgba");
2372 
2373         OScalarProperty property(context->GetParent().GetSchema(),
2374                                  name,
2375                                  DataType(kFloat32POD, 4),
2376                                  metadata);
2377         for (double time : sampleTimes) {
2378             _Copy(context->GetSchema(), _CopyAdskColor, time, color,&property);
2379         }
2380         property.setTimeSampling(context->AddTimeSampling(sampleTimes));
2381 
2382         // Don't try writing the Maya color.
2383         context->ExtractSamples(name);
2384     }
2385 }
2386 
2387 static
2388 void
_WriteUnknownMayaColor(_PrimWriterContext * context)2389 _WriteUnknownMayaColor(_PrimWriterContext* context)
2390 {
2391     // XXX -- Write the Maya color to a .geom OCompoundProperty.
2392 }
2393 
2394 static
2395 void
_WriteImageable(_PrimWriterContext * context)2396 _WriteImageable(_PrimWriterContext* context)
2397 {
2398     _WriteGenericScalar(context, _CopyVisibility, DataType(kInt8POD),
2399                         UsdGeomTokens->visibility, kVisibilityPropertyName);
2400 }
2401 
2402 static
2403 void
_WriteOther(_PrimWriterContext * context)2404 _WriteOther(_PrimWriterContext* context)
2405 {
2406     // Write every unextracted property to Alembic using default converters.
2407     // This handles any property we don't have specific rules for.  Any
2408     // Usd name with namespaces is written to Alembic with the namespaces
2409     // embedded in the name.
2410     //
2411     for (const auto& name : context->GetUnextractedNames()) {
2412         _WriteOutOfSchemaProperty(context,
2413                                   context->GetParent().GetProperties(),
2414                                   name, context->GetAlembicPropertyName(name));
2415     }
2416 }
2417 
2418 //
2419 // Object writers -- these create an OObject.
2420 //
2421 
2422 void
_AddOrderingMetadata(const _PrimWriterContext & context,const TfToken & fieldName,const std::string & metadataName,MetaData * metadata)2423 _AddOrderingMetadata(
2424     const _PrimWriterContext& context,
2425     const TfToken& fieldName,
2426     const std::string& metadataName,
2427     MetaData* metadata)
2428 {
2429     VtValue value = context.GetField(fieldName);
2430     if (value.IsHolding<TfTokenVector>()) {
2431         const TfTokenVector& order = value.UncheckedGet<TfTokenVector>();
2432         if (! order.empty()) {
2433             // Write as space separated names all surrounded by square
2434             // brackets.
2435             metadata->set(metadataName, TfStringify(order));
2436         }
2437     }
2438 }
2439 
2440 static
2441 MetaData
_GetPrimMetadata(const _PrimWriterContext & context)2442 _GetPrimMetadata(const _PrimWriterContext& context)
2443 {
2444     MetaData metadata;
2445 
2446     // Add "over".
2447     if (_IsOver(context)) {
2448         metadata.set(_AmdName(SdfFieldKeys->Specifier), "over");
2449     }
2450 
2451     _SetBoolMetadata(&metadata, context, SdfFieldKeys->Active);
2452     _SetBoolMetadata(&metadata, context, SdfFieldKeys->Hidden);
2453     _SetStringMetadata(&metadata, context, SdfFieldKeys->DisplayGroup);
2454     _SetStringMetadata(&metadata, context, SdfFieldKeys->Documentation);
2455     _SetTokenMetadata(&metadata, context, SdfFieldKeys->Kind);
2456 
2457     // Add name children ordering.
2458     _AddOrderingMetadata(context, SdfFieldKeys->PrimOrder,
2459                          _AmdName(SdfFieldKeys->PrimOrder), &metadata);
2460 
2461     // Add property ordering.
2462     _AddOrderingMetadata(context, SdfFieldKeys->PropertyOrder,
2463                          _AmdName(SdfFieldKeys->PropertyOrder), &metadata);
2464 
2465     return metadata;
2466 }
2467 
2468 static
2469 void
_WriteRoot(_PrimWriterContext * context)2470 _WriteRoot(_PrimWriterContext* context)
2471 {
2472     // Create the Alembic root.
2473     shared_ptr<OObject> root(new OObject(context->GetArchive(), kTop));
2474     context->SetParent(root);
2475 
2476     // Make the root metadata.
2477     MetaData metadata;
2478     _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->StartTimeCode);
2479     _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->EndTimeCode);
2480 
2481     // Always author a value for timeCodesPerSecond and framesPerSecond
2482     // to preserve proper round-tripping from USD->alembic->USD.
2483     //
2484     // First, set them to the corresponding fallback values, then overwrite them
2485     // with the values from the input layer.
2486     //
2487     const SdfSchema &sdfSchema = SdfSchema::GetInstance();
2488     double fallbackTimeCodesPerSecond = sdfSchema.GetFallback(
2489         SdfFieldKeys->TimeCodesPerSecond).Get<double>();
2490     double fallbackFramesPerSecond = sdfSchema.GetFallback(
2491         SdfFieldKeys->FramesPerSecond).Get<double>();
2492 
2493     metadata.set(_AmdName(SdfFieldKeys->TimeCodesPerSecond),
2494                   TfStringify(fallbackTimeCodesPerSecond));
2495     metadata.set(_AmdName(SdfFieldKeys->FramesPerSecond),
2496                   TfStringify(fallbackFramesPerSecond));
2497 
2498     _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->TimeCodesPerSecond);
2499     _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->FramesPerSecond);
2500 
2501     // XXX(Frame->Time): backwards compatibility
2502     _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->StartFrame);
2503     _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->EndFrame);
2504 
2505     _SetTokenMetadata(&metadata, *context, SdfFieldKeys->DefaultPrim);
2506 
2507     _SetTokenMetadata(&metadata, *context, UsdGeomTokens->upAxis);
2508 
2509     // Create a compound property to hang metadata off of.  We'd kinda like
2510     // to put this on the top object but that was created when we opened
2511     // the file, prior to knowing which SdfAbstractData we were writing.
2512     OCompoundProperty prop(root->getProperties(), "Usd", metadata);
2513 }
2514 
2515 template <class T>
2516 static
_ExtractWithFallback(UsdSamples const & samples,double time,const UsdPrimDefinition * primDef,TfToken const & propertyName,T * val)2517 bool _ExtractWithFallback(UsdSamples const &samples, double time,
2518                           const UsdPrimDefinition *primDef,
2519                           TfToken const &propertyName,
2520                           T *val)
2521 {
2522     if (samples.IsEmpty()){
2523         if (!primDef) {
2524             return false;
2525         }
2526         return primDef->GetAttributeFallbackValue(propertyName, val);
2527     }
2528 
2529     const VtValue value = samples.Get(time);
2530 
2531     if (value.IsHolding<T>()) {
2532         *val = value.UncheckedGet<T>();
2533         return true;
2534     } else {
2535         TF_WARN("Expected type '%s', but found '%s' for %s",
2536                 ArchGetDemangled(typeid(T)).c_str(),
2537                 ArchGetDemangled(value.GetTypeName()).c_str(),
2538                 propertyName.GetText());
2539         return false;
2540     }
2541 }
2542 
2543 static
2544 void
_WriteCameraParameters(_PrimWriterContext * context)2545 _WriteCameraParameters(_PrimWriterContext* context)
2546 {
2547     typedef OCamera Type;
2548 
2549     // Create the object and make it the parent.
2550     shared_ptr<Type> object(new Type(context->GetParent(),
2551                                      context->GetAlembicPrimName(),
2552                                      _GetPrimMetadata(*context)));
2553     context->SetParent(object);
2554 
2555     // Should be OK doing a VtValue::Get here, as the only way we should have
2556     // been able to get here is by dispatching on prim typeName.
2557     TfToken primType = context->GetField(SdfFieldKeys->TypeName).Get<TfToken>();
2558 
2559     // Collect the properties we need to compute the frustum.
2560     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2561     UsdSamples focalLength =
2562         context->ExtractSamples(UsdGeomTokens->focalLength,
2563                                 SdfValueTypeNames->Float);
2564     UsdSamples horizontalAperture =
2565         context->ExtractSamples(UsdGeomTokens->horizontalAperture,
2566                                 SdfValueTypeNames->Float);
2567     UsdSamples verticalAperture =
2568         context->ExtractSamples(UsdGeomTokens->verticalAperture,
2569                                 SdfValueTypeNames->Float);
2570     UsdSamples horizontalApertureOffset =
2571         context->ExtractSamples(UsdGeomTokens->horizontalApertureOffset,
2572                                 SdfValueTypeNames->Float);
2573     UsdSamples verticalApertureOffset =
2574         context->ExtractSamples(UsdGeomTokens->verticalApertureOffset,
2575                                 SdfValueTypeNames->Float);
2576 
2577     UsdSamples clippingRange =
2578         context->ExtractSamples(UsdGeomTokens->clippingRange,
2579                                 SdfValueTypeNames->Float2);
2580 
2581     const UsdPrimDefinition *primDef =
2582         UsdSchemaRegistry::GetInstance().FindConcretePrimDefinition(primType);
2583 
2584     // Copy all the samples to set up alembic camera frustum.
2585     typedef CameraSample SampleT;
2586     for (double time : context->GetSampleTimesUnion()) {
2587         // Build the sample.
2588         SampleT sample;
2589 
2590         {
2591             // Horizontal aperture is in cm in ABC, but mm in USD
2592             float value;
2593             if (_ExtractWithFallback(horizontalAperture, time,
2594                                      primDef,
2595                                      UsdGeomTokens->horizontalAperture,
2596                                      &value)){
2597                 sample.setHorizontalAperture(value / 10.0);
2598             }
2599         }
2600 
2601         {
2602             // Vertical aperture is in cm in ABC, but mm in USD
2603             float value;
2604             if (_ExtractWithFallback(verticalAperture, time,
2605                                      primDef, UsdGeomTokens->verticalAperture,
2606                                      &value)){
2607                 sample.setVerticalAperture(value / 10.0);
2608             }
2609         }
2610 
2611         {
2612             // Horizontal aperture is in cm in ABC, but mm in USD
2613             float value;
2614             if (_ExtractWithFallback(horizontalApertureOffset, time,
2615                                      primDef,
2616                                      UsdGeomTokens->horizontalApertureOffset,
2617                                      &value)){
2618                 sample.setHorizontalFilmOffset(value / 10.0);
2619             }
2620         }
2621 
2622         {
2623             // Vertical aperture is in cm in ABC, but mm in USD
2624             float value;
2625             if (_ExtractWithFallback(verticalApertureOffset, time,
2626                                      primDef,
2627                                      UsdGeomTokens->verticalApertureOffset,
2628                                      &value)){
2629                 sample.setVerticalFilmOffset(value / 10.0);
2630             }
2631         }
2632 
2633         {
2634             // Focal length in USD and ABC is both in mm
2635             float value;
2636             if (_ExtractWithFallback(focalLength, time,
2637                                      primDef, UsdGeomTokens->focalLength,
2638                                      &value)){
2639                 sample.setFocalLength(value);
2640             }
2641         }
2642 
2643         {
2644             GfVec2f value;
2645             if (_ExtractWithFallback(clippingRange, time,
2646                                      primDef, UsdGeomTokens->clippingRange,
2647                                      &value)){
2648                 sample.setNearClippingPlane(value[0]);
2649                 sample.setFarClippingPlane(value[1]);
2650             }
2651         }
2652 
2653         // Write the sample.
2654         object->getSchema().set(sample);
2655     }
2656 
2657     // Set the time sampling.
2658     object->getSchema().setTimeSampling(
2659         context->AddTimeSampling(context->GetSampleTimesUnion()));
2660 }
2661 
2662 static
2663 void
_WriteUnknown(_PrimWriterContext * context)2664 _WriteUnknown(_PrimWriterContext* context)
2665 {
2666     typedef OObject Type;
2667 
2668     // Get the standard metadata and add the Usd prim type, if any.
2669     MetaData metadata = _GetPrimMetadata(*context);
2670     const VtValue value = context->GetField(SdfFieldKeys->TypeName);
2671     if (value.IsHolding<TfToken>()) {
2672         const TfToken& typeName = value.UncheckedGet<TfToken>();
2673         if (! typeName.IsEmpty()) {
2674             metadata.set(_AmdName(SdfFieldKeys->TypeName), typeName);
2675         }
2676     }
2677 
2678     // Create the object and make it the parent.
2679     shared_ptr<Type> object(new Type(context->GetParent(),
2680                                      context->GetAlembicPrimName(),
2681                                      metadata));
2682     context->SetParent(object);
2683 }
2684 
2685 static
2686 void
_WriteXform(_PrimWriterContext * context)2687 _WriteXform(_PrimWriterContext* context)
2688 {
2689     // Collect the properties we need.  We'll need these to compute the
2690     // metadata.
2691     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2692 
2693     UsdSamples xformOpOrder = context->ExtractSamples(
2694         UsdGeomTokens->xformOpOrder, SdfValueTypeNames->TokenArray);
2695 
2696     bool hasXformOpOrder = (context->GetSampleTimesUnion().size()>0);
2697 
2698     // Clear samples from xformOpOrder.
2699     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2700 
2701     // XXX: NOTE
2702     // We can't use the GetLocalTranformation API available in UsdGeomXformable
2703     // here, as there is no UsdPrim (or UsdStage) from which we can construct a
2704     // UsdGeomXformable schema object. Hence, for now, if xformOpOrder has a
2705     // value, then assuming that the custom "xformOp:transform" attribute will
2706     // have the composed local transformation in it.
2707     //
2708     // If xformOpOrder has no authored value, then fallback to reading the
2709     // old-style transform attribute.
2710     //
2711     const TfToken &transformAttrName = hasXformOpOrder ?
2712         _tokens->xformOpTransform : _tokens->transform;
2713     const SdfValueTypeName &transformValueType = hasXformOpOrder ?
2714         SdfValueTypeNames->Matrix4d : SdfValueTypeNames->Matrix4d;
2715 
2716     if (hasXformOpOrder) {
2717         // Extract and clear samples from the old-style transform attribute, if
2718         // it exists, so it doesn't get written out as blind data.
2719         context->ExtractSamples(_tokens->transform,
2720                                 SdfValueTypeNames->Matrix4d);
2721         context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2722     }
2723 
2724     UsdSamples transform = context->ExtractSamples(transformAttrName,
2725                                                    transformValueType);
2726 
2727     // At this point, all transform related attributes (including all xformOps)
2728     // should have been extracted. Validate here to make sure there aren't
2729     // any unextracted xformOp attributes.
2730     for (const auto& name : context->GetUnextractedNames()) {
2731         if (UsdGeomXformOp::IsXformOp(name)) {
2732             TF_RUNTIME_ERROR("Found unextracted property '%s' in xformOp "
2733                 "namespace.", name.GetText());
2734         }
2735     }
2736 
2737     // Collect the metadata.  Here we have to combine metadata from the
2738     // prim and from the transform attribute since Alembic will not give
2739     // us a chance to set metadata on the Alembic properties.
2740     MetaData metadata = _GetPrimMetadata(*context);
2741     {
2742         // Get the transform property metadata.
2743         MetaData transformMetadata =
2744             _GetPropertyMetadata(*context, transformAttrName, transform);
2745 
2746         // Merge the property metadata into the prim metadata in a way we
2747         // can extract later for round-tripping.
2748         for (MetaData::const_iterator i  = transformMetadata.begin();
2749                                       i != transformMetadata.end(); ++i) {
2750             if (! i->second.empty()) {
2751                 metadata.set("Usd.transform:" + i->first, i->second);
2752             }
2753         }
2754     }
2755 
2756     // Create the object and make it the parent.
2757     OXformPtr object(new OXform(context->GetParent(),
2758                                 context->GetAlembicPrimName(),
2759                                 metadata));
2760     context->SetParent(object);
2761 
2762     // Copy all the samples.
2763     typedef XformSample SampleT;
2764     SampleT sample;
2765     for (double time : context->GetSampleTimesUnion()) {
2766         // Build the sample.
2767         sample.reset();
2768         _CopyXform(time, transform, &sample);
2769         sample.setInheritsXforms(true);
2770 
2771         // Write it.
2772         object->getSchema().set(sample);
2773     }
2774 
2775     // Set the time sampling.
2776     object->getSchema().setTimeSampling(
2777         context->AddTimeSampling(context->GetSampleTimesUnion()));
2778 }
2779 
2780 static
2781 void
_WriteXformParent(_PrimWriterContext * context)2782 _WriteXformParent(_PrimWriterContext* context)
2783 {
2784     // Used to split transform into a parent object.
2785     _WriteXform(context);
2786 
2787     // Put a "Shape" suffix on the geometry.
2788     context->PushSuffix("Shape");
2789 }
2790 
2791 static
2792 void
_WritePolyMesh(_PrimWriterContext * context)2793 _WritePolyMesh(_PrimWriterContext* context)
2794 {
2795     typedef OPolyMesh Type;
2796 
2797     const _WriterSchema& schema = context->GetSchema();
2798 
2799     // Create the object and make it the parent.
2800     shared_ptr<Type> object(new Type(context->GetParent(),
2801                                      context->GetAlembicPrimName(),
2802                                      _GetPrimMetadata(*context)));
2803     context->SetParent(object);
2804 
2805     // Collect the properties we need.
2806     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2807     UsdSamples extent =
2808         context->ExtractSamples(UsdGeomTokens->extent,
2809                                 SdfValueTypeNames->Float3Array);
2810     UsdSamples points =
2811         context->ExtractSamples(UsdGeomTokens->points,
2812                                 SdfValueTypeNames->Point3fArray);
2813     UsdSamples velocities =
2814         context->ExtractSamples(UsdGeomTokens->velocities,
2815                                 SdfValueTypeNames->Vector3fArray);
2816     UsdSamples faceVertexIndices =
2817         context->ExtractSamples(UsdGeomTokens->faceVertexIndices,
2818                                 SdfValueTypeNames->IntArray);
2819     UsdSamples faceVertexCounts =
2820         context->ExtractSamples(UsdGeomTokens->faceVertexCounts,
2821                                 SdfValueTypeNames->IntArray);
2822     UsdSamples normals =
2823         context->ExtractSamples(UsdGeomTokens->normals,
2824                                 SdfValueTypeNames->Normal3fArray);
2825 
2826     // Default to look for primvars:st with type TexCoord2fArray or Float2Array
2827     UsdSamples uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))?
2828                         (context->ExtractSamples(UsdAbcPropertyNames->st,
2829                                     SdfValueTypeNames->TexCoord2fArray,
2830                                     SdfValueTypeNames->Float2Array)) :
2831                         (context->ExtractSamples(UsdAbcPropertyNames->st,
2832                                     SdfValueTypeNames->TexCoord2fArray));
2833 
2834     // At this point if matching uv set has not been found,
2835     // look for primvars:uv with type TexCoord2fArray or Float2Array
2836     if (uv.IsEmpty()) {
2837         uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))?
2838                  (context->ExtractSamples(UsdAbcPropertyNames->uv,
2839                                      SdfValueTypeNames->TexCoord2fArray,
2840                                      SdfValueTypeNames->Float2Array)) :
2841                  (context->ExtractSamples(UsdAbcPropertyNames->uv,
2842                                      SdfValueTypeNames->TexCoord2fArray));
2843 
2844         context->RemoveSamples(UsdAbcPropertyNames->stIndices);
2845     } else {
2846         // We found a primvars:st, so remove samples with name "primvars:uv"
2847         context->RemoveSamples(UsdAbcPropertyNames->uv);
2848         context->RemoveSamples(UsdAbcPropertyNames->uvIndices);
2849     }
2850 
2851     // Adjust faceVertexIndices for winding order.
2852     _ReverseWindingOrder(context, &faceVertexIndices, faceVertexCounts);
2853 
2854     // Copy all the samples.
2855     typedef Type::schema_type::Sample SampleT;
2856     SampleT sample;
2857     for (double time : context->GetSampleTimesUnion()) {
2858         // Build the sample.
2859         sample.reset();
2860         _CopySelfBounds(time, extent, &sample);
2861         _SampleForAlembic alembicPoints =
2862         _Copy(schema,
2863               time, points,
2864               &sample, &SampleT::setPositions);
2865         _SampleForAlembic alembicVelocities =
2866         _Copy(schema,
2867               time, velocities,
2868               &sample, &SampleT::setVelocities);
2869         _SampleForAlembic alembicFaceIndices =
2870         _Copy(schema,
2871               time, faceVertexIndices,
2872               &sample, &SampleT::setFaceIndices);
2873         _SampleForAlembic alembicFaceCounts =
2874         _Copy(schema,
2875               time, faceVertexCounts,
2876               &sample, &SampleT::setFaceCounts);
2877         _SampleForAlembic alembicNormals =
2878         _Copy<ON3fGeomParam::prop_type::traits_type>(schema,
2879               time, normals,
2880               &sample, &SampleT::setNormals);
2881         _SampleForAlembic alembicUVs =
2882         _Copy<OV2fGeomParam::prop_type::traits_type>(schema,
2883               time, uv,
2884               &sample, &SampleT::setUVs);
2885 
2886         // Write the sample.
2887         object->getSchema().set(sample);
2888     }
2889 
2890     // Alembic doesn't need this since it knows it's a PolyMesh.
2891     context->ExtractSamples(UsdGeomTokens->subdivisionScheme);
2892 
2893     // Set the time sampling.
2894     object->getSchema().setTimeSampling(
2895         context->AddTimeSampling(context->GetSampleTimesUnion()));
2896 }
2897 
2898 static
2899 void
_WriteFaceSet(_PrimWriterContext * context)2900 _WriteFaceSet(_PrimWriterContext* context)
2901 {
2902     typedef OFaceSet Type;
2903 
2904     const _WriterSchema& schema = context->GetSchema();
2905 
2906     // Create the object and make it the parent.
2907     shared_ptr<Type> object(new Type(context->GetParent(),
2908                                      context->GetAlembicPrimName(),
2909                                      _GetPrimMetadata(*context)));
2910     context->SetParent(object);
2911 
2912     // Collect the properties we need.
2913     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2914 
2915     UsdSamples indices =
2916         context->ExtractSamples(UsdGeomTokens->indices,
2917                                 SdfValueTypeNames->IntArray);
2918 
2919     // The familyType is contained in the parent prim, so we
2920     // contruct a new _PrimWriterContext to access it.
2921     SdfPath parentPath = context->GetPath().GetParentPath();
2922     _PrimWriterContext parentPrimContext(context->GetWriterContext(),
2923                                          context->GetParent(),
2924                                          parentPath);
2925 
2926     UsdSamples familyType = parentPrimContext.ExtractSamples(
2927         UsdAbcPropertyNames->defaultFamilyTypeAttributeName,
2928         SdfValueTypeNames->Token);
2929 
2930     // Copy all the samples.
2931     typedef Type::schema_type::Sample SampleT;
2932     SampleT sample;
2933 
2934     for (double time : context->GetSampleTimesUnion()) {
2935         // Build the sample.
2936         sample.reset();
2937         _SampleForAlembic alembicFaces =
2938         _Copy(schema,
2939               time, indices,
2940               &sample, &SampleT::setFaces);
2941 
2942         // Write the sample.
2943         object->getSchema().set(sample);
2944     }
2945 
2946     // It's possible that our default family name "materialBind", is not
2947     // set on the prim. In that case, use kFaceSetNonExclusive.
2948     FaceSetExclusivity faceSetExclusivity = kFaceSetNonExclusive;
2949     if (!familyType.IsEmpty())
2950     {
2951         double time = UsdTimeCode::EarliestTime().GetValue();
2952         const TfToken& value = familyType.Get(time).UncheckedGet<TfToken>();
2953         if (!value.IsEmpty() &&
2954             (value == UsdGeomTokens->partition ||
2955              value == UsdGeomTokens->nonOverlapping)) {
2956             faceSetExclusivity = kFaceSetExclusive;
2957         }
2958     }
2959 
2960     // Face set exclusivity is not a property of the sample. Instead, it's set
2961     // on the object schema and not time sampled.
2962     object->getSchema().setFaceExclusivity(faceSetExclusivity);
2963 
2964     // Set the time sampling.
2965     object->getSchema().setTimeSampling(
2966         context->AddTimeSampling(context->GetSampleTimesUnion()));
2967 }
2968 
2969 // As of Alembic-1.5.1, OSubD::schema_type::Sample has a bug:
2970 // setHoles() actually sets cornerIndices.  The member, m_holes, is
2971 // protected so we subclass and fix setHoles().
2972 // XXX: Remove this when Alembic is fixed.
2973 class MyOSubDSample : public OSubD::schema_type::Sample {
2974 public:
setHoles(const Abc::Int32ArraySample & iHoles)2975     void setHoles( const Abc::Int32ArraySample &iHoles )
2976     { m_holes = iHoles; }
2977 };
2978 
2979 static
2980 void
_WriteSubD(_PrimWriterContext * context)2981 _WriteSubD(_PrimWriterContext* context)
2982 {
2983     typedef OSubD Type;
2984 
2985     const _WriterSchema& schema = context->GetSchema();
2986 
2987     // Create the object and make it the parent.
2988     shared_ptr<Type> object(new Type(context->GetParent(),
2989                                      context->GetAlembicPrimName(),
2990                                      _GetPrimMetadata(*context)));
2991     context->SetParent(object);
2992 
2993     // Collect the properties we need.
2994     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
2995     UsdSamples extent =
2996         context->ExtractSamples(UsdGeomTokens->extent,
2997                                 SdfValueTypeNames->Float3Array);
2998     UsdSamples points =
2999         context->ExtractSamples(UsdGeomTokens->points,
3000                                 SdfValueTypeNames->Point3fArray);
3001     UsdSamples velocities =
3002         context->ExtractSamples(UsdGeomTokens->velocities,
3003                                 SdfValueTypeNames->Vector3fArray);
3004     UsdSamples faceVertexIndices =
3005         context->ExtractSamples(UsdGeomTokens->faceVertexIndices,
3006                                 SdfValueTypeNames->IntArray);
3007     UsdSamples faceVertexCounts =
3008         context->ExtractSamples(UsdGeomTokens->faceVertexCounts,
3009                                 SdfValueTypeNames->IntArray);
3010     UsdSamples subdivisionScheme =
3011         context->ExtractSamples(UsdGeomTokens->subdivisionScheme,
3012                                 SdfValueTypeNames->Token);
3013     UsdSamples interpolateBoundary =
3014         context->ExtractSamples(UsdGeomTokens->interpolateBoundary,
3015                                 SdfValueTypeNames->Token);
3016     UsdSamples faceVaryingLinearInterpolation =
3017         context->ExtractSamples(UsdGeomTokens->faceVaryingLinearInterpolation,
3018                                 SdfValueTypeNames->Token);
3019     UsdSamples holeIndices =
3020         context->ExtractSamples(UsdGeomTokens->holeIndices,
3021                                 SdfValueTypeNames->IntArray);
3022     UsdSamples cornerIndices =
3023         context->ExtractSamples(UsdGeomTokens->cornerIndices,
3024                                 SdfValueTypeNames->IntArray);
3025     UsdSamples cornerSharpnesses =
3026         context->ExtractSamples(UsdGeomTokens->cornerSharpnesses,
3027                                 SdfValueTypeNames->FloatArray);
3028     UsdSamples creaseIndices =
3029         context->ExtractSamples(UsdGeomTokens->creaseIndices,
3030                                 SdfValueTypeNames->IntArray);
3031     UsdSamples creaseLengths =
3032         context->ExtractSamples(UsdGeomTokens->creaseLengths,
3033                                 SdfValueTypeNames->IntArray);
3034     UsdSamples creaseSharpnesses =
3035         context->ExtractSamples(UsdGeomTokens->creaseSharpnesses,
3036                                 SdfValueTypeNames->FloatArray);
3037 
3038     // Default to look for primvars:st with type TexCoord2fArray or Float2Array
3039     UsdSamples uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))?
3040                         (context->ExtractSamples(UsdAbcPropertyNames->st,
3041                                     SdfValueTypeNames->TexCoord2fArray,
3042                                     SdfValueTypeNames->Float2Array)) :
3043                         (context->ExtractSamples(UsdAbcPropertyNames->st,
3044                                     SdfValueTypeNames->TexCoord2fArray));
3045 
3046     // At this point if matching uv set has not been found,
3047     // look for primvars:uv with type TexCoord2fArray or Float2Array
3048     if (uv.IsEmpty()) {
3049         uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))?
3050                  (context->ExtractSamples(UsdAbcPropertyNames->uv,
3051                                      SdfValueTypeNames->TexCoord2fArray,
3052                                      SdfValueTypeNames->Float2Array)) :
3053                  (context->ExtractSamples(UsdAbcPropertyNames->uv,
3054                                      SdfValueTypeNames->TexCoord2fArray));
3055 
3056         context->RemoveSamples(UsdAbcPropertyNames->stIndices);
3057     } else {
3058         // We found a primvars:st, so remove samples with name "primvars:uv"
3059         context->RemoveSamples(UsdAbcPropertyNames->uv);
3060         context->RemoveSamples(UsdAbcPropertyNames->uvIndices);
3061     }
3062 
3063     // Adjust faceVertexIndices for winding order.
3064     _ReverseWindingOrder(context, &faceVertexIndices, faceVertexCounts);
3065 
3066     // Copy all the samples.
3067     typedef MyOSubDSample MySampleT;
3068     typedef Type::schema_type::Sample SampleT;
3069     MySampleT mySample;
3070     SampleT& sample = mySample;
3071     for (double time : context->GetSampleTimesUnion()) {
3072         // Build the sample.  Usd defaults both interpolateBoundary and
3073         // faceVaryingLinearInterpolation to edgeAndCorner (1 in both cases)
3074         // but Alembic defaults to none (0) and bilinear (0) respectively,
3075         // so set these first in case we have no opinion (converters will
3076         // not be invoked and no value assigned if the Usd value is absent).
3077         sample.reset();
3078 
3079         sample.setInterpolateBoundary(1);
3080         sample.setFaceVaryingInterpolateBoundary(1);
3081 
3082         _CopySelfBounds(time, extent, &sample);
3083         _SampleForAlembic alembicPositions =
3084         _Copy(schema,
3085               time, points,
3086               &sample, &SampleT::setPositions);
3087         _SampleForAlembic alembicVelocities =
3088         _Copy(schema,
3089               time, velocities,
3090               &sample, &SampleT::setVelocities);
3091         _SampleForAlembic alembicFaceIndices =
3092         _Copy(schema,
3093               time, faceVertexIndices,
3094               &sample, &SampleT::setFaceIndices);
3095         _SampleForAlembic alembicFaceCounts =
3096         _Copy(schema,
3097               time, faceVertexCounts,
3098               &sample, &SampleT::setFaceCounts);
3099         _Copy(schema,
3100               _CopySubdivisionScheme,
3101               time, subdivisionScheme,
3102               &sample, &SampleT::setSubdivisionScheme);
3103         _Copy(schema,
3104               _CopyInterpolateBoundary,
3105               time, interpolateBoundary,
3106               &sample, &SampleT::setInterpolateBoundary);
3107         _Copy(schema,
3108               _CopyFaceVaryingInterpolateBoundary,
3109               time, faceVaryingLinearInterpolation,
3110               &sample, &SampleT::setFaceVaryingInterpolateBoundary);
3111         _SampleForAlembic alembicHoles =
3112         _Copy(schema,
3113               time, holeIndices,
3114               &mySample, &MySampleT::setHoles);
3115         _SampleForAlembic alembicCornerIndices =
3116         _Copy(schema,
3117               time, cornerIndices,
3118               &sample, &SampleT::setCornerIndices);
3119         _SampleForAlembic alembicCornerSharpnesses =
3120         _Copy(schema,
3121               time, cornerSharpnesses,
3122               &sample, &SampleT::setCornerSharpnesses);
3123         _SampleForAlembic alembicCreaseIndices =
3124         _Copy(schema,
3125               time, creaseIndices,
3126               &sample, &SampleT::setCreaseIndices);
3127         _SampleForAlembic alembicCreaseLengths =
3128         _Copy(schema,
3129               time, creaseLengths,
3130               &sample, &SampleT::setCreaseLengths);
3131         _SampleForAlembic alembicCreaseSharpnesses =
3132         _Copy(schema,
3133               time, creaseSharpnesses,
3134               &sample, &SampleT::setCreaseSharpnesses);
3135         _SampleForAlembic alembicUVs =
3136         _Copy<OV2fGeomParam::prop_type::traits_type>(schema,
3137               time, uv,
3138               &sample, &SampleT::setUVs);
3139 
3140         // Write the sample.
3141         object->getSchema().set(sample);
3142     }
3143 
3144     // Set the time sampling.
3145     object->getSchema().setTimeSampling(
3146         context->AddTimeSampling(context->GetSampleTimesUnion()));
3147 }
3148 
3149 static
3150 void
_WriteNurbsCurves(_PrimWriterContext * context)3151 _WriteNurbsCurves(_PrimWriterContext* context)
3152 {
3153     typedef OCurves Type;
3154 
3155     const _WriterSchema& schema = context->GetSchema();
3156 
3157     // Create the object and make it the parent.
3158     shared_ptr<Type> object(new Type(context->GetParent(),
3159                                      context->GetAlembicPrimName(),
3160                                      _GetPrimMetadata(*context)));
3161     context->SetParent(object);
3162 
3163     // Collect the properties we need.
3164     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
3165     UsdSamples extent =
3166         context->ExtractSamples(UsdGeomTokens->extent,
3167                                 SdfValueTypeNames->Float3Array);
3168     UsdSamples points =
3169         context->ExtractSamples(UsdGeomTokens->points,
3170                                 SdfValueTypeNames->Point3fArray);
3171     UsdSamples velocities =
3172         context->ExtractSamples(UsdGeomTokens->velocities,
3173                                 SdfValueTypeNames->Vector3fArray);
3174     UsdSamples normals =
3175         context->ExtractSamples(UsdGeomTokens->normals,
3176                                 SdfValueTypeNames->Normal3fArray);
3177     UsdSamples curveVertexCounts =
3178         context->ExtractSamples(UsdGeomTokens->curveVertexCounts,
3179                                 SdfValueTypeNames->IntArray);
3180     UsdSamples widths =
3181         context->ExtractSamples(UsdGeomTokens->widths,
3182                                 SdfValueTypeNames->FloatArray);
3183     UsdSamples knots =
3184         context->ExtractSamples(UsdGeomTokens->knots,
3185                                 SdfValueTypeNames->DoubleArray);
3186     UsdSamples order =
3187         context->ExtractSamples(UsdGeomTokens->order,
3188                                 SdfValueTypeNames->IntArray);
3189 
3190     // Copy all the samples.
3191     typedef Type::schema_type::Sample SampleT;
3192     SampleT sample;
3193     for (double time : context->GetSampleTimesUnion()) {
3194         // Build the sample.
3195         sample.reset();
3196         _CopySelfBounds(time, extent, &sample);
3197         _SampleForAlembic alembicPositions =
3198         _Copy(schema,
3199               time, points,
3200               &sample, &SampleT::setPositions);
3201         _SampleForAlembic alembicVelocities =
3202         _Copy(schema,
3203               time, velocities,
3204               &sample, &SampleT::setVelocities);
3205         _SampleForAlembic alembicNormals =
3206         _Copy<ON3fGeomParam::prop_type::traits_type>(schema,
3207               time, normals,
3208               &sample, &SampleT::setNormals);
3209         _SampleForAlembic alembicCurveVertexCounts =
3210         _Copy(schema,
3211               time, curveVertexCounts,
3212               &sample, &SampleT::setCurvesNumVertices);
3213         _SampleForAlembic alembicWidths =
3214         _Copy<OFloatGeomParam::prop_type::traits_type>(schema,
3215               time, widths,
3216               &sample, &SampleT::setWidths);
3217         _SampleForAlembic alembicKnots =
3218         _Copy(schema,
3219               _CopyKnots,
3220               time, knots,
3221               &sample, &SampleT::setKnots);
3222         _SampleForAlembic alembicOrders =
3223         _Copy(schema,
3224               _CopyOrder,
3225               time, order,
3226               &sample, &SampleT::setOrders);
3227 
3228         // This is how Alembic knows it's a NURBS curve.
3229         sample.setType(kVariableOrder);
3230 
3231         // Write the sample.
3232         object->getSchema().set(sample);
3233     }
3234 
3235     // Set the time sampling.
3236     object->getSchema().setTimeSampling(
3237         context->AddTimeSampling(context->GetSampleTimesUnion()));
3238 }
3239 
3240 static
3241 void
_WriteBasisCurves(_PrimWriterContext * context)3242 _WriteBasisCurves(_PrimWriterContext* context)
3243 {
3244     typedef OCurves Type;
3245 
3246     const _WriterSchema& schema = context->GetSchema();
3247 
3248     // Create the object and make it the parent.
3249     shared_ptr<Type> object(new Type(context->GetParent(),
3250                                      context->GetAlembicPrimName(),
3251                                      _GetPrimMetadata(*context)));
3252     context->SetParent(object);
3253 
3254     // Collect the properties we need.
3255     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
3256     UsdSamples extent =
3257         context->ExtractSamples(UsdGeomTokens->extent,
3258                                 SdfValueTypeNames->Float3Array);
3259     UsdSamples points =
3260         context->ExtractSamples(UsdGeomTokens->points,
3261                                 SdfValueTypeNames->Point3fArray);
3262     UsdSamples velocities =
3263         context->ExtractSamples(UsdGeomTokens->velocities,
3264                                 SdfValueTypeNames->Vector3fArray);
3265     UsdSamples normals =
3266         context->ExtractSamples(UsdGeomTokens->normals,
3267                                 SdfValueTypeNames->Normal3fArray);
3268     UsdSamples curveVertexCounts =
3269         context->ExtractSamples(UsdGeomTokens->curveVertexCounts,
3270                                 SdfValueTypeNames->IntArray);
3271     UsdSamples widths =
3272         context->ExtractSamples(UsdGeomTokens->widths,
3273                                 SdfValueTypeNames->FloatArray);
3274     UsdSamples basis =
3275         context->ExtractSamples(UsdGeomTokens->basis,
3276                                 SdfValueTypeNames->Token);
3277     UsdSamples type =
3278         context->ExtractSamples(UsdGeomTokens->type,
3279                                 SdfValueTypeNames->Token);
3280     UsdSamples wrap =
3281         context->ExtractSamples(UsdGeomTokens->wrap,
3282                                 SdfValueTypeNames->Token);
3283 
3284     // Copy all the samples.
3285     typedef Type::schema_type::Sample SampleT;
3286     SampleT sample;
3287     for (double time : context->GetSampleTimesUnion()) {
3288         // Build the sample.
3289         sample.reset();
3290         _CopySelfBounds(time, extent, &sample);
3291         _SampleForAlembic alembicPositions =
3292         _Copy(schema,
3293               time, points,
3294               &sample, &SampleT::setPositions);
3295         _SampleForAlembic alembicVelocities =
3296         _Copy(schema,
3297               time, velocities,
3298               &sample, &SampleT::setVelocities);
3299         _SampleForAlembic alembicNormals =
3300         _Copy<ON3fGeomParam::prop_type::traits_type>(schema,
3301               time, normals,
3302               &sample, &SampleT::setNormals);
3303         _SampleForAlembic alembicCurveVertexCounts =
3304         _Copy(schema,
3305               time, curveVertexCounts,
3306               &sample, &SampleT::setCurvesNumVertices);
3307         _SampleForAlembic alembicWidths =
3308         _Copy<OFloatGeomParam::prop_type::traits_type>(schema,
3309               time, widths,
3310               &sample, &SampleT::setWidths);
3311         _Copy(schema,
3312               _CopyCurveBasis,
3313               time, basis,
3314               &sample, &SampleT::setBasis);
3315         _Copy(schema,
3316               _CopyCurveType,
3317               time, type,
3318               &sample, &SampleT::setType);
3319         _Copy(schema,
3320               _CopyCurveWrap,
3321               time, wrap,
3322               &sample, &SampleT::setWrap);
3323 
3324         // Write the sample.
3325         object->getSchema().set(sample);
3326     }
3327 
3328     // Set the time sampling.
3329     object->getSchema().setTimeSampling(
3330         context->AddTimeSampling(context->GetSampleTimesUnion()));
3331 }
3332 
3333 static
3334 void
_WriteHermiteCurves(_PrimWriterContext * context)3335 _WriteHermiteCurves(_PrimWriterContext* context)
3336 {
3337     typedef OCurves Type;
3338 
3339     const _WriterSchema& schema = context->GetSchema();
3340 
3341     // Create the object and make it the parent.
3342     shared_ptr<Type> object(new Type(context->GetParent(),
3343                                      context->GetAlembicPrimName(),
3344                                      _GetPrimMetadata(*context)));
3345     context->SetParent(object);
3346 
3347     // Collect the properties we need.
3348     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
3349     UsdSamples extent =
3350         context->ExtractSamples(UsdGeomTokens->extent,
3351                                 SdfValueTypeNames->Float3Array);
3352     UsdSamples points =
3353         context->ExtractSamples(UsdGeomTokens->points,
3354                                 SdfValueTypeNames->Point3fArray);
3355     UsdSamples tangents =
3356         context->ExtractSamples(UsdGeomTokens->tangents,
3357                                 SdfValueTypeNames->Vector3fArray);
3358     UsdSamples velocities =
3359         context->ExtractSamples(UsdGeomTokens->velocities,
3360                                 SdfValueTypeNames->Vector3fArray);
3361     UsdSamples normals =
3362         context->ExtractSamples(UsdGeomTokens->normals,
3363                                 SdfValueTypeNames->Normal3fArray);
3364     UsdSamples curveVertexCounts =
3365         context->ExtractSamples(UsdGeomTokens->curveVertexCounts,
3366                                 SdfValueTypeNames->IntArray);
3367     UsdSamples widths =
3368         context->ExtractSamples(UsdGeomTokens->widths,
3369                                 SdfValueTypeNames->FloatArray);
3370     if (!velocities.IsEmpty()) {
3371         // Velocities has the same shape as points / positions.
3372         // In Abc positions encodes both points and tangents but in Usd, they
3373         // are separate arrays. To address, we would either need to add
3374         // tangentVelocities to the USD schema or provide alembic with
3375         // 0 valued velocities for the tangent elements. Let's identify
3376         // some use cases before figuring out how to handle this.
3377         TF_WARN(
3378             "Writing '%s' from HermiteCurves to AbcGeom::OCurvesSchema is "
3379             "undefined.",
3380             velocities.GetPath().GetText());
3381     }
3382 
3383     // Copy all the samples.
3384     typedef Type::schema_type::Sample SampleT;
3385     SampleT sample;
3386     for (double time : context->GetSampleTimesUnion()) {
3387         // Build the sample.
3388         sample.reset();
3389         _CopySelfBounds(time, extent, &sample);
3390 
3391         // We need to interleave points and tangents to
3392         // output to alembic's curve type for hermite curves.
3393         VtValue pointsValue = points.Get(time);
3394         VtValue tangentsValue = tangents.Get(time);
3395         _SampleForAlembic alembicPoints;
3396         if (auto pointsAndTangents =
3397                 UsdGeomHermiteCurves::PointAndTangentArrays(
3398                     pointsValue.Get<VtVec3fArray>(),
3399                     tangentsValue.Get<VtVec3fArray>())) {
3400             // This is a copy of _Copy
3401             static const int extent = P3fArraySample::traits_type::extent;
3402             typedef PODTraitsFromEnum<
3403                 P3fArraySample::traits_type::pod_enum>::value_type P3fPodType;
3404             typedef TypedArraySample<P3fArraySample::traits_type>
3405                 P3fAlembicSample;
3406             typedef P3fArraySample::traits_type::value_type P3fValueType;
3407             VtVec3fArray interleaved = pointsAndTangents.Interleave();
3408             const SdfValueTypeName& usdType = SdfValueTypeNames->Point3fArray;
3409             const _WriterSchema::Converter& converter =
3410                 schema.GetConverter(usdType);
3411             alembicPoints = _MakeSample<P3fPodType, extent>(
3412                 schema, converter, usdType, VtValue(interleaved), false);
3413 
3414             if (_CheckSample(alembicPoints, points, usdType)) {
3415                 sample.setPositions(
3416                     P3fAlembicSample(alembicPoints.GetDataAs<P3fValueType>(),
3417                                      alembicPoints.GetCount() / extent));
3418             }
3419         }
3420         _SampleForAlembic alembicNormals =
3421         _Copy<ON3fGeomParam::prop_type::traits_type>(schema,
3422               time, normals,
3423               &sample, &SampleT::setNormals);
3424         _SampleForAlembic alembicCurveVertexCounts =
3425         _Copy(schema,
3426               time, curveVertexCounts,
3427               &sample, &SampleT::setCurvesNumVertices);
3428         _SampleForAlembic alembicWidths =
3429         _Copy<OFloatGeomParam::prop_type::traits_type>(schema,
3430               time, widths,
3431               &sample, &SampleT::setWidths);
3432         sample.setBasis(kHermiteBasis);
3433         sample.setType(kCubic);
3434         sample.setWrap(kNonPeriodic);
3435         // Write the sample.
3436         object->getSchema().set(sample);
3437     }
3438 
3439     // Set the time sampling.
3440     object->getSchema().setTimeSampling(
3441         context->AddTimeSampling(context->GetSampleTimesUnion()));
3442 }
3443 
3444 static
3445 void
_WritePoints(_PrimWriterContext * context)3446 _WritePoints(_PrimWriterContext* context)
3447 {
3448     typedef OPoints Type;
3449 
3450     const _WriterSchema& schema = context->GetSchema();
3451 
3452     // Create the object and make it the parent.
3453     shared_ptr<Type> object(new Type(context->GetParent(),
3454                                      context->GetAlembicPrimName(),
3455                                      _GetPrimMetadata(*context)));
3456     context->SetParent(object);
3457 
3458     // Collect the properties we need.
3459     context->SetSampleTimesUnion(UsdAbc_TimeSamples());
3460     UsdSamples extent =
3461         context->ExtractSamples(UsdGeomTokens->extent,
3462                                 SdfValueTypeNames->Float3Array);
3463     UsdSamples points =
3464         context->ExtractSamples(UsdGeomTokens->points,
3465                                 SdfValueTypeNames->Point3fArray);
3466     UsdSamples velocities =
3467         context->ExtractSamples(UsdGeomTokens->velocities,
3468                                 SdfValueTypeNames->Vector3fArray);
3469     UsdSamples widths =
3470         context->ExtractSamples(UsdGeomTokens->widths,
3471                                 SdfValueTypeNames->FloatArray);
3472     UsdSamples ids =
3473         context->ExtractSamples(UsdGeomTokens->ids,
3474                                 SdfValueTypeNames->Int64Array);
3475 
3476     // Copy all the samples.
3477     typedef Type::schema_type::Sample SampleT;
3478     SampleT sample;
3479     bool first = true;
3480     for (double time : context->GetSampleTimesUnion()) {
3481         // Build the sample.
3482         sample.reset();
3483         _CopySelfBounds(time, extent, &sample);
3484         _SampleForAlembic alembicPoints =
3485         _Copy(schema,
3486               time, points,
3487               &sample, &SampleT::setPositions);
3488         _SampleForAlembic alembicVelocities =
3489         _Copy(schema,
3490               time, velocities,
3491               &sample, &SampleT::setVelocities);
3492         _SampleForAlembic alembicWidths =
3493         _Copy<OFloatGeomParam::prop_type::traits_type>(schema,
3494               time, widths,
3495               &sample, &SampleT::setWidths);
3496         _SampleForAlembic alembicIds =
3497         _Copy(schema,
3498               _CopyPointIds,
3499               time, ids,
3500               &sample, &SampleT::setIds);
3501 
3502         // Alembic requires ids.  We only need to write one and it'll
3503         // be reused for all the other samples.  We also use a valid
3504         // but empty array because we don't actually have any data.
3505         if (first && !sample.getIds()) {
3506             static const uint64_t data = 0;
3507             first = false;
3508             sample.setIds(UInt64ArraySample(&data, 0));
3509         }
3510 
3511         // Write the sample.
3512         object->getSchema().set(sample);
3513     }
3514 
3515     // Set the time sampling.
3516     object->getSchema().setTimeSampling(
3517         context->AddTimeSampling(context->GetSampleTimesUnion()));
3518 }
3519 
3520 static
3521 TfToken
_ComputeTypeName(const _WriterContext & context,const SdfPath & path)3522 _ComputeTypeName(
3523     const _WriterContext& context,
3524     const SdfPath& path)
3525 {
3526     // Special case.
3527     if (path == SdfPath::AbsoluteRootPath()) {
3528         return UsdAbcPrimTypeNames->PseudoRoot;
3529     }
3530 
3531     // General case.
3532     VtValue value = context.GetData().Get(path, SdfFieldKeys->TypeName);
3533     if (! value.IsHolding<TfToken>()) {
3534         return TfToken();
3535     }
3536     TfToken typeName = value.UncheckedGet<TfToken>();
3537 
3538     // Special cases.
3539     if (typeName == UsdAbcPrimTypeNames->Mesh) {
3540 
3541         SdfPath propPath(path.GetPrimPath().AppendProperty(
3542                              UsdGeomTokens->subdivisionScheme));
3543         value = context.GetData().Get(propPath, SdfFieldKeys->Default);
3544         if (value.IsHolding<TfToken>() &&
3545                 value.UncheckedGet<TfToken>() == "none") {
3546             typeName = UsdAbcPrimTypeNames->PolyMesh;
3547         }
3548     }
3549 
3550     return typeName;
3551 }
3552 
3553 static
3554 void
_WritePrim(_WriterContext & context,const _Parent & parent,const SdfPath & path)3555 _WritePrim(
3556     _WriterContext& context,
3557     const _Parent& parent,
3558     const SdfPath& path)
3559 {
3560     _Parent prim;
3561     {
3562         // Compute the type name.
3563         const TfToken typeName = _ComputeTypeName(context, path);
3564 
3565         // Write the properties.
3566         _PrimWriterContext primContext(context, parent, path);
3567         for (const auto& writer :
3568                  context.GetSchema().GetPrimWriters(typeName)) {
3569             TRACE_SCOPE("UsdAbc_AlembicDataWriter:_WritePrim");
3570             writer(&primContext);
3571         }
3572         prim = primContext.GetParent();
3573     }
3574 
3575     // Write the name children.
3576     const VtValue childrenNames =
3577         context.GetData().Get(path, SdfChildrenKeys->PrimChildren);
3578     if (childrenNames.IsHolding<TfTokenVector>()) {
3579         for (const auto& childName :
3580                  childrenNames.UncheckedGet<TfTokenVector>()) {
3581             _WritePrim(context, prim, path.AppendChild(childName));
3582         }
3583     }
3584 }
3585 
3586 // ----------------------------------------------------------------------------
3587 
3588 //
3589 // Schema builder
3590 //
3591 
3592 struct _WriterSchemaBuilder {
3593     _WriterSchema schema;
3594 
3595     _WriterSchemaBuilder();
3596 };
3597 
_WriterSchemaBuilder()3598 _WriterSchemaBuilder::_WriterSchemaBuilder()
3599 {
3600     schema.AddType(UsdAbcPrimTypeNames->Scope)
3601         .AppendWriter(_WriteUnknown)
3602         .AppendWriter(_WriteImageable)
3603         .AppendWriter(_WriteArbGeomParams)
3604         .AppendWriter(_WriteUserProperties)
3605         .AppendWriter(_WriteOther)
3606         ;
3607     schema.AddType(UsdAbcPrimTypeNames->Xform)
3608         .AppendWriter(_WriteXform)
3609         .AppendWriter(_WriteImageable)
3610         .AppendWriter(_WriteArbGeomParams)
3611         .AppendWriter(_WriteUserProperties)
3612         .AppendWriter(_WriteOther)
3613         ;
3614     schema.AddType(UsdAbcPrimTypeNames->Mesh)
3615         .AppendWriter(_WriteXformParent)
3616         .AppendWriter(_WriteSubD)
3617         .AppendWriter(_WriteMayaColor)
3618         .AppendWriter(_WriteGprim)
3619         .AppendWriter(_WriteImageable)
3620         .AppendWriter(_WriteArbGeomParams)
3621         .AppendWriter(_WriteUserProperties)
3622         .AppendWriter(_WriteOther)
3623         ;
3624     schema.AddType(UsdAbcPrimTypeNames->PolyMesh)
3625         .AppendWriter(_WriteXformParent)
3626         .AppendWriter(_WritePolyMesh)
3627         .AppendWriter(_WriteMayaColor)
3628         .AppendWriter(_WriteGprim)
3629         .AppendWriter(_WriteImageable)
3630         .AppendWriter(_WriteArbGeomParams)
3631         .AppendWriter(_WriteUserProperties)
3632         .AppendWriter(_WriteOther)
3633         ;
3634     schema.AddType(UsdAbcPrimTypeNames->NurbsCurves)
3635         .AppendWriter(_WriteXformParent)
3636         .AppendWriter(_WriteNurbsCurves)
3637         .AppendWriter(_WriteMayaColor)
3638         .AppendWriter(_WriteGprim)
3639         .AppendWriter(_WriteImageable)
3640         .AppendWriter(_WriteArbGeomParams)
3641         .AppendWriter(_WriteUserProperties)
3642         .AppendWriter(_WriteOther)
3643         ;
3644     schema.AddType(UsdAbcPrimTypeNames->BasisCurves)
3645         .AppendWriter(_WriteXformParent)
3646         .AppendWriter(_WriteBasisCurves)
3647         .AppendWriter(_WriteMayaColor)
3648         .AppendWriter(_WriteGprim)
3649         .AppendWriter(_WriteImageable)
3650         .AppendWriter(_WriteArbGeomParams)
3651         .AppendWriter(_WriteUserProperties)
3652         .AppendWriter(_WriteOther)
3653         ;
3654     schema.AddType(UsdAbcPrimTypeNames->HermiteCurves)
3655         .AppendWriter(_WriteXformParent)
3656         .AppendWriter(_WriteHermiteCurves)
3657         .AppendWriter(_WriteMayaColor)
3658         .AppendWriter(_WriteGprim)
3659         .AppendWriter(_WriteImageable)
3660         .AppendWriter(_WriteArbGeomParams)
3661         .AppendWriter(_WriteUserProperties)
3662         .AppendWriter(_WriteOther)
3663         ;
3664     schema.AddType(UsdAbcPrimTypeNames->Points)
3665         .AppendWriter(_WriteXformParent)
3666         .AppendWriter(_WritePoints)
3667         .AppendWriter(_WriteMayaColor)
3668         .AppendWriter(_WriteGprim)
3669         .AppendWriter(_WriteImageable)
3670         .AppendWriter(_WriteArbGeomParams)
3671         .AppendWriter(_WriteUserProperties)
3672         .AppendWriter(_WriteOther)
3673         ;
3674     schema.AddType(UsdAbcPrimTypeNames->Camera)
3675         .AppendWriter(_WriteXformParent)
3676         .AppendWriter(_WriteCameraParameters)
3677         .AppendWriter(_WriteArbGeomParams)
3678         .AppendWriter(_WriteUserProperties)
3679         .AppendWriter(_WriteOther)
3680         ;
3681     schema.AddType(UsdAbcPrimTypeNames->GeomSubset)
3682         .AppendWriter(_WriteFaceSet)
3683         ;
3684 
3685     // This handles the root.
3686     schema.AddType(UsdAbcPrimTypeNames->PseudoRoot)
3687         .AppendWriter(_WriteRoot)
3688         ;
3689 
3690     // This handles overs with no type and any unknown prim type.
3691     schema.AddFallbackType()
3692         .AppendWriter(_WriteUnknown)
3693         .AppendWriter(_WriteUnknownMayaColor)
3694         .AppendWriter(_WriteGprim)
3695         .AppendWriter(_WriteImageable)
3696         .AppendWriter(_WriteArbGeomParams)
3697         .AppendWriter(_WriteUserProperties)
3698         .AppendWriter(_WriteOther)
3699         ;
3700 }
3701 
3702 } // anonymous namespace
3703 
3704 static
3705 const _WriterSchema&
_GetSchema()3706 _GetSchema()
3707 {
3708     static _WriterSchemaBuilder builder;
3709     return builder.schema;
3710 }
3711 
3712 //
3713 // UsdAbc_AlembicDataWriter
3714 //
3715 
3716 class UsdAbc_AlembicDataWriterImpl : public _WriterContext { };
3717 
UsdAbc_AlembicDataWriter()3718 UsdAbc_AlembicDataWriter::UsdAbc_AlembicDataWriter() :
3719     _impl(new UsdAbc_AlembicDataWriterImpl)
3720 {
3721     // Do nothing
3722 }
3723 
~UsdAbc_AlembicDataWriter()3724 UsdAbc_AlembicDataWriter::~UsdAbc_AlembicDataWriter()
3725 {
3726     Close();
3727 }
3728 
3729 bool
Open(const std::string & filePath,const std::string & comment)3730 UsdAbc_AlembicDataWriter::Open(
3731     const std::string& filePath,
3732     const std::string& comment)
3733 {
3734     TRACE_FUNCTION();
3735 
3736     _errorLog.clear();
3737 
3738     const std::string dir = TfGetPathName(filePath);
3739     if (!dir.empty() && !TfIsDir(dir) && !TfMakeDirs(dir)) {
3740         TF_RUNTIME_ERROR("Could not create directory '%s'", dir.c_str());
3741         return false;
3742     }
3743 
3744     try {
3745         _impl->SetArchive(
3746             CreateArchiveWithInfo(Alembic::AbcCoreOgawa::WriteArchive(),
3747                                   filePath, writerName, comment));
3748         return true;
3749     }
3750     catch (std::exception& e) {
3751         _errorLog.append(e.what());
3752         _errorLog.append("\n");
3753         return false;
3754     }
3755 }
3756 
3757 bool
Write(const SdfAbstractDataConstPtr & data)3758 UsdAbc_AlembicDataWriter::Write(const SdfAbstractDataConstPtr& data)
3759 {
3760     TRACE_FUNCTION();
3761 
3762     try {
3763         if (_impl->GetArchive().valid() && data) {
3764             const _WriterSchema& schema = _GetSchema();
3765             _impl->SetSchema(&schema);
3766             _impl->SetData(data);
3767             _WritePrim(*_impl, _Parent(), SdfPath::AbsoluteRootPath());
3768         }
3769         return true;
3770     }
3771     catch (std::exception& e) {
3772         _errorLog.append(e.what());
3773         _errorLog.append("\n");
3774         return false;
3775     }
3776 }
3777 
3778 bool
Close()3779 UsdAbc_AlembicDataWriter::Close()
3780 {
3781     TRACE_FUNCTION();
3782 
3783     // Alembic does not appear to be robust when closing an archive.
3784     // ~AwImpl does real writes and the held std::ostream is configured
3785     // to throw exceptions, so any exceptions when writing should leak
3786     // from the d'tor.  This is a fatal error.
3787     //
3788     // For now we just destroy the archive and don't bother looking for
3789     // errors.
3790     _impl->SetArchive(OArchive());
3791     return true;
3792 }
3793 
3794 std::string
GetErrors() const3795 UsdAbc_AlembicDataWriter::GetErrors() const
3796 {
3797     return _errorLog;
3798 }
3799 
3800 void
SetFlag(const TfToken & flagName,bool set)3801 UsdAbc_AlembicDataWriter::SetFlag(const TfToken& flagName, bool set)
3802 {
3803     _impl->SetFlag(flagName, set);
3804 }
3805 
3806 PXR_NAMESPACE_CLOSE_SCOPE
3807 
3808