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