// // Copyright 2016-2019 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // /// \file alembicWriter.cpp #include "pxr/pxr.h" #include "pxr/usd/plugin/usdAbc/alembicWriter.h" #include "pxr/usd/plugin/usdAbc/alembicUtil.h" #include "pxr/usd/usdGeom/hermiteCurves.h" #include "pxr/usd/usdGeom/tokens.h" #include "pxr/usd/usdGeom/xformOp.h" #include "pxr/usd/sdf/schema.h" #include "pxr/usd/usd/schemaRegistry.h" #include "pxr/base/trace/trace.h" #include "pxr/base/tf/enum.h" #include "pxr/base/tf/envSetting.h" #include "pxr/base/tf/ostreamMethods.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include PXR_NAMESPACE_OPEN_SCOPE // The name of this exporter, embedded in written Alembic files. static const char* writerName = "UsdAbc_AlembicData"; TF_DEFINE_PRIVATE_TOKENS( _tokens, (transform) ((xformOpTransform, "xformOp:transform")) ); TF_DEFINE_ENV_SETTING(USD_ABC_READ_FLOAT2_AS_UV, true, "Turn to false to disable reading float2 arrays as uv sets"); namespace { using namespace ::Alembic::AbcGeom; using namespace UsdAbc_AlembicUtil; // The SdfAbstractData time samples type. // XXX: SdfAbstractData should typedef this. typedef std::set UsdAbc_TimeSamples; struct _Subtract { double operator()(double x, double y) const { return x - y; } }; static GeometryScope _GetGeometryScope(const TfToken& interpolation) { static const TfToken constant("constant"); static const TfToken uniform("uniform"); static const TfToken varying("varying"); static const TfToken vertex("vertex"); static const TfToken faceVarying("faceVarying"); if (interpolation.IsEmpty() || interpolation == constant) { return kConstantScope; } if (interpolation == uniform) { return kUniformScope; } if (interpolation == varying) { return kVaryingScope; } if (interpolation == vertex) { return kVertexScope; } if (interpolation == faceVarying) { return kFacevaryingScope; } return kUnknownScope; } // // UsdSamples // /// \class UsdSamples /// \brief Wraps time samples from a Usd property. /// /// Wraps time samples or a default in a Usd property, providing a uniform /// interface. class UsdSamples { public: UsdSamples(const SdfPath& primPath, const TfToken& propertyName); /// Construct from a property. If the property has time samples use /// those, otherwise use the default as a single time sample at time /// zero. If there's no default then return an empty set. /// /// This validates the samples to ensure they all have the same type. UsdSamples(const SdfPath& primPath, const TfToken& propertyName, const SdfAbstractData& data); /// Returns a path. SdfPath GetPath() const; /// Returns \c true iff there are no samples. bool IsEmpty() const; /// Returns the number of samples. size_t GetNumSamples() const; /// Returns \c true iff the property is time sampled, \c false if /// the value was taken from the default or if there were no opinions. bool IsTimeSampled() const; /// Returns the type name of the samples. const SdfValueTypeName& GetTypeName() const; /// Returns a field on the property. VtValue GetField(const TfToken& name) const; /// Returns the sample closest to time \p time. const VtValue& Get(double time) const; /// Adds the set of all sample times to \p times. void AddTimes(UsdAbc_TimeSamples* times) const; /// Returns the sample map. const SdfTimeSampleMap& GetSamples() const; /// Sets the samples to \p samples. The contents of \p samples is /// undefined after the call. void TakeSamples(SdfTimeSampleMap& samples); private: bool _Validate(); void _Clear(); private: SdfPath _propPath; const SdfAbstractData* _data; boost::shared_ptr _value; boost::shared_ptr _local; const SdfTimeSampleMap* _samples; bool _timeSampled; SdfValueTypeName _typeName; }; UsdSamples::UsdSamples(const SdfPath& primPath, const TfToken& propertyName) : _propPath(primPath.AppendProperty(propertyName)), _data(NULL) { _Clear(); } UsdSamples::UsdSamples( const SdfPath& primPath, const TfToken& propertyName, const SdfAbstractData& data) : _propPath(primPath.AppendProperty(propertyName)), _data(&data) { VtValue value; if (data.Has(_propPath, SdfFieldKeys->TimeSamples, &value)) { if (TF_VERIFY(value.IsHolding())) { _value.reset(new VtValue); _value->Swap(value); _samples = &_value->UncheckedGet(); _timeSampled = true; } else { _Clear(); return; } } else if (data.Has(_propPath, SdfFieldKeys->Default, &value)) { _local.reset(new SdfTimeSampleMap); (*_local)[0.0].Swap(value); _samples = _local.get(); _timeSampled = false; } else { _Clear(); return; } if (TF_VERIFY(data.Has(_propPath, SdfFieldKeys->TypeName, &value), "No type name on <%s>", _propPath.GetText())) { if (TF_VERIFY(value.IsHolding())) { _typeName = SdfSchema::GetInstance(). FindType(value.UncheckedGet()); _Validate(); } else { _Clear(); } } else { _Clear(); } } bool UsdSamples::_Validate() { const TfType type = _typeName.GetType(); const TfType backupType = (type == TfType::Find()) ? TfType::Find() : type; restart: for (const auto& v : *_samples) { if (v.second.GetType() != type) { if (!TF_VERIFY(v.second.GetType() == backupType, "Expected sample at <%s> time %f of type '%s', " "got '%s'", GetPath().GetText(), v.first, type.GetTypeName().c_str(), v.second.GetType().GetTypeName().c_str())) { _Clear(); return false; } else { // Make sure we have a local copy. if (!_local) { _local.reset(new SdfTimeSampleMap(*_samples)); _samples = _local.get(); goto restart; } // Convert double to float. (*_local)[v.first] = static_cast(v.second.UncheckedGet()); } } } return true; } void UsdSamples::_Clear() { _value.reset(); _local.reset(new SdfTimeSampleMap); _samples = _local.get(); _timeSampled = false; _typeName = SdfValueTypeName(); } SdfPath UsdSamples::GetPath() const { return _propPath; } bool UsdSamples::IsEmpty() const { return _samples->empty(); } size_t UsdSamples::GetNumSamples() const { return _samples->size(); } bool UsdSamples::IsTimeSampled() const { return _timeSampled; } const SdfValueTypeName& UsdSamples::GetTypeName() const { return _typeName; } VtValue UsdSamples::GetField(const TfToken& name) const { return _data->Get(_propPath, name); } const VtValue& UsdSamples::Get(double time) const { if (IsEmpty()) { static const VtValue empty; return empty; } else { SdfTimeSampleMap::const_iterator i = _samples->lower_bound(time); return i == _samples->end() ? _samples->rbegin()->second : i->second; } } void UsdSamples::AddTimes(UsdAbc_TimeSamples* times) const { for (const auto& v : *_samples) { times->insert(v.first); } } const SdfTimeSampleMap& UsdSamples::GetSamples() const { return *_samples; } void UsdSamples::TakeSamples(SdfTimeSampleMap& samples) { if (!_local) { _value.reset(); _local.reset(new SdfTimeSampleMap); } _local.get()->swap(samples); _samples = _local.get(); _Validate(); } // // _Parent // /// \class _Parent /// \brief Encapsulates an Alembic parent object /// /// This mainly exists to extract certain properties from objects that /// have them. The Alembic type hierarchy and templating prevents us /// from dynamic casting to a type that can provide these properties /// (there isn't a single type to cast to, instead there's a templated /// class and the template argument depends on the actual type of the /// object). This object holds enough type information to get what we /// want. class _Parent { public: /// Construct invalid parent. _Parent() : _object(new _Prim(shared_ptr(new OObject))) { } /// Construct from an Alembic shared pointer to an OObject subclass. template _Parent(const shared_ptr& prim) : _object(new _Prim(static_pointer_cast(prim))) { } /// Construct from an Alembic shared pointer to a supported /// schema based OSchemaObject. _Parent(const shared_ptr& prim): _object(new _GeomPrim(prim)) { } _Parent(const shared_ptr& prim): _object(new _GeomPrim(prim)) { } _Parent(const shared_ptr& prim): _object(new _GeomPrim(prim)) { } _Parent(const shared_ptr& prim): _object(new _GeomPrim(prim)) { } _Parent(const shared_ptr& prim): _object(new _GeomPrim(prim)) { } _Parent(const shared_ptr& prim): _object(new _GeomPrim(prim)) { } /// Returns the OObject. operator OObject&() const; /// Returns the OCompoundProperty holding the object's properties. OCompoundProperty GetProperties() const; /// Returns the OCompoundProperty holding the object's schema. OCompoundProperty GetSchema() const; /// Returns the OCompoundProperty holding the ".arbGeomParams" property. /// This returns an invalid property if the object isn't geometric. OCompoundProperty GetArbGeomParams() const; /// Returns the OCompoundProperty holding the ".userProperties" property. /// This returns an invalid property if the object isn't geometric. OCompoundProperty GetUserProperties() const; private: class _Prim { public: explicit _Prim(const shared_ptr& object) : _object(object) { } virtual ~_Prim(); const shared_ptr& GetObjectPtr() const { return _object; } virtual OCompoundProperty GetSchema() const; virtual OCompoundProperty GetArbGeomParams() const; virtual OCompoundProperty GetUserProperties() const; private: shared_ptr _object; }; template class _GeomPrim : public _Prim { public: explicit _GeomPrim(const shared_ptr& object) : _Prim(object) { } virtual ~_GeomPrim() { } virtual OCompoundProperty GetSchema() const; virtual OCompoundProperty GetArbGeomParams() const; virtual OCompoundProperty GetUserProperties() const; }; private: shared_ptr<_Prim> _object; }; _Parent::_Prim::~_Prim() { // Do nothing } OCompoundProperty _Parent::_Prim::GetSchema() const { return OCompoundProperty(); } OCompoundProperty _Parent::_Prim::GetArbGeomParams() const { return OCompoundProperty(); } OCompoundProperty _Parent::_Prim::GetUserProperties() const { return OCompoundProperty(); } template OCompoundProperty _Parent::_GeomPrim::GetSchema() const { return static_pointer_cast(GetObjectPtr())->getSchema(); } template OCompoundProperty _Parent::_GeomPrim::GetArbGeomParams() const { return static_pointer_cast(GetObjectPtr())->getSchema().getArbGeomParams(); } template OCompoundProperty _Parent::_GeomPrim::GetUserProperties() const { return static_pointer_cast(GetObjectPtr())->getSchema().getUserProperties(); } _Parent::operator OObject&() const { return *_object->GetObjectPtr(); } OCompoundProperty _Parent::GetProperties() const { return _object->GetObjectPtr()->getProperties(); } OCompoundProperty _Parent::GetSchema() const { return _object->GetSchema(); } OCompoundProperty _Parent::GetArbGeomParams() const { return _object->GetArbGeomParams(); } OCompoundProperty _Parent::GetUserProperties() const { return _object->GetUserProperties(); } // // _WriterSchema // class _PrimWriterContext; /// \class _WriterSchema /// \brief The Alembic to Usd schema. /// /// This class stores functions to write a Usd prim to Alembic keyed by /// type. Each type can have multiple writers, each affected by the /// previous via a \c _PrimWriterContext. class _WriterSchema { public: typedef std::function PrimWriter; typedef std::vector PrimWriterVector; typedef UsdAbc_AlembicDataConversion::FromUsdConverter Converter; _WriterSchema(); /// Returns the prim writers for the given type. Returns an empty /// vector if the type isn't known. const PrimWriterVector& GetPrimWriters(const TfToken&) const; // Helper for defining types. class TypeRef { public: TypeRef(PrimWriterVector* writers) : _writers(writers) { } TypeRef& AppendWriter(const PrimWriter& writer) { _writers->push_back(writer); return *this; } private: PrimWriterVector* _writers; }; /// Adds a type and returns a helper for defining it. template TypeRef AddType(T name) { return TypeRef(&_writers[TfToken(name)]); } /// Adds the fallback type and returns a helper for defining it. TypeRef AddFallbackType() { return AddType(TfToken()); } /// Returns \c true iff the samples are valid. bool IsValid(const UsdSamples&) const; /// Returns \c true iff the samples are a shaped type. bool IsShaped(const UsdSamples&) const; /// Returns the Alembic DataType suitable for the values in \p samples. DataType GetDataType(const UsdSamples& samples) const; /// Returns the (default) conversion for the Alembic property type with /// name \p typeName. SdfValueTypeName FindConverter(const UsdAbc_AlembicType& typeName) const; /// Returns the (default) conversion for the Usd property type with /// name \p typeName. UsdAbc_AlembicType FindConverter(const SdfValueTypeName& typeName) const; /// Returns the conversion function for the given conversion. const Converter& GetConverter(const SdfValueTypeName& typeName) const; private: const UsdAbc_AlembicConversions _conversions; typedef std::map _WriterMap; _WriterMap _writers; }; _WriterSchema::_WriterSchema() { // Do nothing } const _WriterSchema::PrimWriterVector& _WriterSchema::GetPrimWriters(const TfToken& name) const { _WriterMap::const_iterator i = _writers.find(name); if (i != _writers.end()) { return i->second; } i = _writers.find(TfToken()); if (i != _writers.end()) { return i->second; } static const PrimWriterVector empty; return empty; } bool _WriterSchema::IsValid(const UsdSamples& samples) const { return GetConverter(samples.GetTypeName()) ? true : false; } bool _WriterSchema::IsShaped(const UsdSamples& samples) const { return samples.GetTypeName().IsArray(); } DataType _WriterSchema::GetDataType(const UsdSamples& samples) const { return FindConverter(samples.GetTypeName()).GetDataType(); } SdfValueTypeName _WriterSchema::FindConverter(const UsdAbc_AlembicType& typeName) const { return _conversions.data.FindConverter(typeName); } UsdAbc_AlembicType _WriterSchema::FindConverter(const SdfValueTypeName& typeName) const { return _conversions.data.FindConverter(typeName); } const _WriterSchema::Converter& _WriterSchema::GetConverter(const SdfValueTypeName& typeName) const { return _conversions.data.GetConverter(typeName); } /// \class _WriterContext /// \brief The Alembic to Usd writer context. /// /// This object holds information used by the writer for a given archive /// and Usd data. class _WriterContext { public: _WriterContext(); /// Returns the archive. void SetArchive(const OArchive& archive); /// Returns the archive. const OArchive& GetArchive() const { return _archive; } /// Returns the archive. OArchive& GetArchive() { return _archive; } /// Sets the writer schema. void SetSchema(const _WriterSchema* schema) { _schema = schema; } /// Returns the writer schema. const _WriterSchema& GetSchema() const { return *_schema; } /// Set the Usd data that we will translate. Also resets _timeScale to /// data's timeCodesPerSecond void SetData(const SdfAbstractDataConstPtr& data) { _data = data; VtValue tcps; if (data->Has(SdfPath::AbsoluteRootPath(), SdfFieldKeys->TimeCodesPerSecond, &tcps)) { if (tcps.IsHolding()) { _timeScale = tcps.UncheckedGet(); } } } /// Returns the Usd data. const SdfAbstractData& GetData() const { return *boost::get_pointer(_data);} /// Sets or resets the flag named \p flagName. void SetFlag(const TfToken& flagName, bool set); /// Returns \c true iff a flag is in the set. bool IsFlagSet(const TfToken& flagName) const; /// Adds/returns a time sampling. uint32_t AddTimeSampling(const UsdAbc_TimeSamples&); private: // Conversion options. double _timeScale; // Scale Alembic time by this factor. double _timeOffset; // Offset Alembic->Usd time (after scale). std::set _flags; // Output state. OArchive _archive; const _WriterSchema* _schema; SdfAbstractDataConstPtr _data; // The set of time samplings we've created. We tend to reuse the same // samplings a lot so caching these avoids reanalyzing the samples to // determine their kind. This also avoids having Alembic check for // duplicate time samplings. This maps a set of samples to the index // of the time sampling in the archive. Index 0 is reserved by // Alembic to mean uniform starting at 0, cycling at 1. std::map _timeSamplings; }; _WriterContext::_WriterContext() : _timeScale(24.0), // Usd is frames, Alembic is seconds. _timeOffset(0.0), // Time 0.0 to frame 0. _schema(NULL) { // Do nothing } void _WriterContext::SetArchive(const OArchive& archive) { _archive = archive; _timeSamplings.clear(); } void _WriterContext::SetFlag(const TfToken& flagName, bool set) { if (set) { _flags.insert(flagName); } else { _flags.erase(flagName); } } bool _WriterContext::IsFlagSet(const TfToken& flagName) const { return _flags.count(flagName); } uint32_t _WriterContext::AddTimeSampling(const UsdAbc_TimeSamples& inSamples) { // Handle empty case. if (inSamples.empty()) { // No samples -> identity time sampling. return 0; } // Get the cached index. If not zero then we already have this one. std::map::const_iterator tsi = _timeSamplings.find(inSamples); if (tsi != _timeSamplings.end()) { return tsi->second; } uint32_t& index = _timeSamplings[inSamples]; // Scale and offset samples. UsdAbc_TimeSamples samples; for (double time : inSamples) { samples.insert((time - _timeOffset) / _timeScale); } // Handy iterators. i refers to the first element and n to end. // Initially j refers to the second element. UsdAbc_TimeSamples::const_iterator j = samples.begin(); const UsdAbc_TimeSamples::const_iterator i = j++; const UsdAbc_TimeSamples::const_iterator n = samples.end(); // Handle other special cases. Note that we store the index returned // by Alembic. Alembic will compare our TimeSampling() against all of // the ones it's seen so far and return the index of the existing one, // if any. So we can map multiple time samples sets to the same // time sampling index. if (samples.size() == 1) { // One sample -> uniform starting at the sample, arbitrary cycle time. return index = _archive.addTimeSampling(TimeSampling(1, *i)); } if (samples.size() == 2) { // Two samples -> uniform. Cyclic and acyclic would also work but // uniform is probably more likely to match an existing sampling. return index = _archive.addTimeSampling(TimeSampling(*j - *i, *i)); } // Figure out if the samples are uniform (T0 + N * dT for integer N), // cyclic (Ti + N * dT for integer N and samples i=[0,M]), or acyclic // (Ti for samples i=[0,M]). In all cases, the total number of samples // is an independent variable and doesn't affect the choice. That is, // 1 1/2 cycles is cyclic even though we don't complete a cycle. Note, // however, that any acyclic sequence is also a cyclic sequence of one // less sample (which we'll call the trivial cyclic sequence); we // choose the acyclic type. // // First find the deltas between samples. std::vector dt; std::transform(j, n, i, std::back_inserter(dt), _Subtract()); // Scan forward in dt for element M that matches dt[0] then check that // dt[0..M-1] == dt[M..2M-1). If that checks out then check that the // cycle continues until we run out of samples. Otherwise repeat from // M + 1 until we find a cycle or run out of samples. Don't check // dt.back() to avoid trivial cyclic. We adjust j to point to the // time sample at the start of the delta dt[k]. size_t k = 1; const size_t m = dt.size(); TimeSamplingType timeSamplingType(TimeSamplingType::kAcyclic); for (; k != m - 1; ++j, ++k) { // Check for a cycle by comparing s[i] == s[i + k] for i in // [0..N-k-1] where N = len(s) and k is the cycle length. if (std::equal(dt.begin() + k, dt.begin() + m, dt.begin())) { // Cyclic or uniform (which is cyclic with samps/cycle == 1). timeSamplingType = TimeSamplingType(k, *j - *i); break; } } // If we're still acyclic then use every sample. if (timeSamplingType.isAcyclic()) { j = n; } // Cyclic or acyclic. return index = _archive.addTimeSampling( TimeSampling(timeSamplingType, std::vector(i, j))); } /// \class _PrimWriterContext /// \brief The Alembic to Usd prim writer context. /// /// This object holds information used by the writer for a given prim. /// Each prim writer can modify the context to change the behavior of /// later writers for that prim. class _PrimWriterContext { public: typedef _Parent Parent; _PrimWriterContext(_WriterContext&, const Parent& parent, const SdfPath& path); /// Return the path to this prim. SdfPath GetPath() const; /// Returns the Usd field from the prim. VtValue GetField(const TfToken& fieldName) const; /// Returns the Usd field from the named property. VtValue GetPropertyField(const TfToken& propertyName, const TfToken& fieldName) const; /// Returns the archive. OArchive& GetArchive(); /// Returns the writer schema. const _WriterSchema& GetSchema() const; /// Returns the abstract data. const SdfAbstractData& GetData() const; /// Returns the spec type for the named property. SdfSpecType GetSpecType(const TfToken& propertyName) const; /// Tests a flag. bool IsFlagSet(const TfToken& flagName) const; /// Adds/returns a time sampling. uint32_t AddTimeSampling(const UsdAbc_TimeSamples&); /// Returns the parent object. const Parent& GetParent() const; /// Sets the parent object. void SetParent(const Parent& parent); /// Causes \c GetAlembicPrimName() to have the suffix appended. void PushSuffix(const std::string& suffix); /// Returns a prim name that is valid Alembic, isn't in use, and has /// all suffixes appended. std::string GetAlembicPrimName() const; /// Returns an Alembic name for \p name that is valid in Alembic and /// isn't in use. std::string GetAlembicPropertyName(const TfToken& name) const; /// Sets the union of extracted sample times to \p timeSamples. void SetSampleTimesUnion(const UsdAbc_TimeSamples& timeSamples); /// Returns the union of extracted sample times. const UsdAbc_TimeSamples& GetSampleTimesUnion() const; /// Returns the samples for a Usd property. If the property doesn't /// exist or has already been extracted then this returns an empty /// samples object. The property is extracted from the context so /// it cannot be extracted again. The sample times union is updated /// to include the sample times from the returned object. UsdSamples ExtractSamples(const TfToken& name) { return _ExtractSamples(name, {}); } /// Returns the samples for a Usd property. If the property doesn't /// exist or has already been extracted then this returns an empty /// samples object. The property is extracted from the context so /// it cannot be extracted again. The sample times union is updated /// to include the sample times from the returned object. This /// verifies that the property is holding a value of either the given type // or the alternative type; /// if not it returns an empty samples object. UsdSamples ExtractSamples(const TfToken& name, const SdfValueTypeName& type, const SdfValueTypeName& alternativeType) { return _ExtractSamples(name, {type, alternativeType}); } /// Returns the samples for a Usd property. If the property doesn't /// exist or has already been extracted then this returns an empty /// samples object. The property is extracted from the context so /// it cannot be extracted again. The sample times union is updated /// to include the sample times from the returned object. This /// verifies that the property is holding a value of the given type; /// if not it returns an empty samples object. UsdSamples ExtractSamples(const TfToken& name, const SdfValueTypeName& type) { return _ExtractSamples(name, {type}); } /// Removes samples for a Usd property. The property cannot be extracted /// after removal. void RemoveSamples(const TfToken& name) { auto i = std::find(_unextracted.begin(), _unextracted.end(), name); if (i != _unextracted.end()) { _unextracted.erase(i); } } /// Returns the names of properties that have not been extracted yet /// in Usd property order. TfTokenVector GetUnextractedNames() const; /// Return the _WriterContext associated with this prim. _WriterContext& GetWriterContext() const; private: UsdSamples _ExtractSamples(const TfToken& name); UsdSamples _ExtractSamples(const TfToken& name, const std::vector &types) { UsdSamples result = _ExtractSamples(name); if (!result.IsEmpty() && !types.empty()) { SdfValueTypeName resultTypeName = result.GetTypeName(); if (find(types.begin(), types.end(), resultTypeName) == types.end()) { TF_WARN("Property '%s' did not have expected type (got '%s')", GetPath().AppendProperty(name).GetText(), resultTypeName.GetAsToken().GetText()); return UsdSamples(GetPath(), name); } } result.AddTimes(&_sampleTimes); return result; } private: typedef std::vector SdfValueTypeNameVector; _WriterContext& _context; Parent _parent; SdfPath _path; std::string _suffix; UsdAbc_TimeSamples _sampleTimes; TfTokenVector _unextracted; }; _PrimWriterContext::_PrimWriterContext( _WriterContext& context, const Parent& parent, const SdfPath& path) : _context(context), _parent(parent), _path(path) { // Fill _unextracted with all of the property names. VtValue tmp; if (_context.GetData().Has( _path, SdfChildrenKeys->PropertyChildren, &tmp)) { if (tmp.IsHolding()) { _unextracted = tmp.UncheckedGet(); } } } SdfPath _PrimWriterContext::GetPath() const { return _path.IsPropertyPath() ? _path.GetParentPath() : _path; } VtValue _PrimWriterContext::GetField(const TfToken& fieldName) const { return _context.GetData().Get(_path, fieldName); } VtValue _PrimWriterContext::GetPropertyField( const TfToken& propertyName, const TfToken& fieldName) const { return _context.GetData().Get( GetPath().AppendProperty(propertyName), fieldName); } OArchive& _PrimWriterContext::GetArchive() { return _context.GetArchive(); } const _WriterSchema& _PrimWriterContext::GetSchema() const { return _context.GetSchema(); } const SdfAbstractData& _PrimWriterContext::GetData() const { return _context.GetData(); } SdfSpecType _PrimWriterContext::GetSpecType(const TfToken& propertyName) const { return _context.GetData().GetSpecType( GetPath().AppendProperty(propertyName)); } bool _PrimWriterContext::IsFlagSet(const TfToken& flagName) const { return _context.IsFlagSet(flagName); } uint32_t _PrimWriterContext::AddTimeSampling(const UsdAbc_TimeSamples& samples) { return _context.AddTimeSampling(samples); } const _PrimWriterContext::Parent& _PrimWriterContext::GetParent() const { return _parent; } void _PrimWriterContext::SetParent(const Parent& parent) { _parent = parent; } void _PrimWriterContext::PushSuffix(const std::string& suffix) { _suffix += suffix; } std::string _PrimWriterContext::GetAlembicPrimName() const { // Valid Alembic prim name set is a superset of valid Usd prim names. // XXX: Should verify this name is not in use, however we know // we're not given how we use it (we only add a suffix to // an only child). return GetPath().GetName() + _suffix; } std::string _PrimWriterContext::GetAlembicPropertyName(const TfToken& name) const { // Valid Alembic property name set is a superset of valid Usd property // names. Alembic accepts the Usd namespace delimiter as-is. return name.GetString(); } void _PrimWriterContext::SetSampleTimesUnion(const UsdAbc_TimeSamples& samples) { _sampleTimes = samples; } const UsdAbc_TimeSamples& _PrimWriterContext::GetSampleTimesUnion() const { return _sampleTimes; } UsdSamples _PrimWriterContext::_ExtractSamples(const TfToken& name) { TfTokenVector::iterator i = std::find(_unextracted.begin(), _unextracted.end(), name); if (i != _unextracted.end()) { _unextracted.erase(i); return UsdSamples(GetPath(), name, GetData()); } return UsdSamples(GetPath(), name); } TfTokenVector _PrimWriterContext::GetUnextractedNames() const { return _unextracted; } _WriterContext& _PrimWriterContext::GetWriterContext() const { return _context; } // ---------------------------------------------------------------------------- // // Utilities // /// Returns the Alembic metadata name for a Usd metadata field name. static std::string _AmdName(const std::string& name) { return "Usd:" + name; } static bool _IsOver(const _PrimWriterContext& context) { if (context.GetField(SdfFieldKeys->TypeName).IsEmpty()) { return true; } const VtValue value = context.GetField(SdfFieldKeys->Specifier); return ! value.IsHolding() || value.UncheckedGet() == SdfSpecifierOver; } // Reverse the order of the subsequences in \p valuesMap where the subsequence // lengths are given by \p counts. template static void _ReverseWindingOrder(UsdSamples* valuesMap, const UsdSamples& countsMap) { typedef VtArray ValueArray; typedef VtArray CountArray; SdfTimeSampleMap result; for (const auto& v : valuesMap->GetSamples()) { const VtValue& valuesValue = v.second; const VtValue& countsValue = countsMap.Get(v.first); if (! TF_VERIFY(valuesValue.IsHolding())) { continue; } if (! TF_VERIFY(countsValue.IsHolding())) { continue; } ValueArray values = valuesValue.UncheckedGet(); const CountArray& counts = countsValue.UncheckedGet(); if (! UsdAbc_ReverseOrderImpl(values, counts)) { continue; } result[v.first].Swap(values); } valuesMap->TakeSamples(result); } // Adjust faceVertexIndices for winding order if orientation is right-handed. static void _ReverseWindingOrder( const _PrimWriterContext* context, UsdSamples* faceVertexIndices, const UsdSamples& faceVertexCounts) { // Get property value. We must reverse the winding order if it's // right-handed in Usd. (Alembic is always left-handed.) // XXX: Should probably check time samples, too, but we expect // this to be uniform. const VtValue value = context->GetPropertyField(UsdGeomTokens->orientation, SdfFieldKeys->Default); if (! value.IsHolding() || value.UncheckedGet() != UsdGeomTokens->leftHanded) { _ReverseWindingOrder(faceVertexIndices, faceVertexCounts); } } static std::string _GetInterpretation(const SdfValueTypeName& typeName) { const TfToken& roleName = typeName.GetRole(); if (roleName == SdfValueRoleNames->Point) { return "point"; } if (roleName == SdfValueRoleNames->Normal) { return "normal"; } if (roleName == SdfValueRoleNames->Vector) { return "vector"; } if (roleName == SdfValueRoleNames->Color) { if (typeName == SdfValueTypeNames->Float4 || typeName == SdfValueTypeNames->Double4) { return "rgba"; } return "rgb"; } if (roleName == SdfValueRoleNames->Transform) { return "matrix"; } if (typeName == SdfValueTypeNames->Quatd || typeName == SdfValueTypeNames->Quatf) { return "quat"; } return std::string(); } static std::string _Stringify(const DataType& type) { if (type.getExtent() > 1) { return TfStringPrintf("%s[%d]",PODName(type.getPod()),type.getExtent()); } else { return PODName(type.getPod()); } } // Make a sample, converting the Usd value to the given Alembic data type. // If the given conversion doesn't perform that conversion then fail. // // Note: This is the one and only place we invoke a converter to create a // _SampleForAlembic. static _SampleForAlembic _MakeSample( const _WriterSchema& schema, const _WriterSchema::Converter& converter, const SdfValueTypeName& usdType, const VtValue& usdValue, const DataType& expectedAlembicType, bool skipAlembicTypeCheck = false) { TRACE_SCOPE("UsdAbc_AlembicDataWriter:_MakeSample"); // Done if there is no value. if (usdValue.IsEmpty()) { return _SampleForAlembic(); } // This catches when the programmer fails to supply a conversion function // for the conversion. if (! converter) { return _ErrorSampleForAlembic(TfStringPrintf( "No conversion for '%s'", usdType.GetAsToken().GetText())); } // This catches when the programmer supplies a conversion that doesn't // yield the correct Alembic type. This should never happen. if (! skipAlembicTypeCheck) { const UsdAbc_AlembicType actualAlembicType = schema.FindConverter(usdType); if (actualAlembicType.GetDataType() != expectedAlembicType) { return _ErrorSampleForAlembic(TfStringPrintf( "Internal error: trying to convert '%s' to '%s'", usdType.GetAsToken().GetText(), _Stringify(expectedAlembicType).c_str())); } } // This catches when the Usd property doesn't have the expected type. // This should never happen because we check properties for the // expected type when we extract samples from the _PrimWriterContext, // or we choose our conversion based on the type of the samples. const SdfValueTypeName actualUsdType = SdfSchema::GetInstance().FindType(usdValue); if (actualUsdType != usdType) { // Handle role types. These have a different name but the same // value type. if (usdType.GetType() != actualUsdType.GetType()) { return _ErrorSampleForAlembic(TfStringPrintf( "Internal error: Trying to use conversion for '%s' to " "convert from '%s'", usdType.GetAsToken().GetText(), actualUsdType.GetAsToken().GetText())); } } // Convert. _SampleForAlembic result(converter(usdValue)); // Check extent. if (expectedAlembicType.getExtent() != 1) { if (result.GetCount() % expectedAlembicType.getExtent() != 0) { return _ErrorSampleForAlembic(TfStringPrintf( "Internal error: didn't get a multiple of the extent " "(%zd %% %d = %zd)", result.GetCount(), expectedAlembicType.getExtent(), result.GetCount() % expectedAlembicType.getExtent())); } } return result; } // Helper to handle enum types in the following _MakeSample() function. template struct PodEnum { static const PlainOldDataType pod_enum = PODTraitsFromType::pod_enum; }; template struct PodEnum::value>::type> { static const PlainOldDataType pod_enum = kUint8POD; }; // Make a sample, converting the Usd value to the given data type, T. // If the given conversion doesn't perform that conversion then fail. template static _SampleForAlembic _MakeSample( const _WriterSchema& schema, const _WriterSchema::Converter& converter, const SdfValueTypeName& usdType, const VtValue& usdValue, bool skipAlembicTypeCheck = false) { return _MakeSample(schema, converter, usdType, usdValue, DataType(PodEnum::pod_enum), skipAlembicTypeCheck); } template static _SampleForAlembic _MakeSample( const _WriterSchema& schema, const _WriterSchema::Converter& converter, const SdfValueTypeName& usdType, const VtValue& usdValue, bool skipAlembicTypeCheck = false) { return _MakeSample(schema, converter, usdType, usdValue, DataType(PODTraitsFromType::pod_enum, N), skipAlembicTypeCheck); } static bool _CheckSample( const _SampleForAlembic& sample, const UsdSamples& samples, const SdfValueTypeName& usdType) { std::string message; if (sample.IsError(&message)) { TF_WARN("Can't convert from '%s' on <%s>: %s", usdType.GetAsToken().GetText(), samples.GetPath().GetText(), message.c_str()); return false; } return static_cast(sample); } // An object we can use for mapping in _MakeIndexed. It holds a pointer to // its data. It's not local to _MakeIndexed because we use it as a template // argument. template struct _MakeIndexedValue { const POD* value; _MakeIndexedValue(const POD* value_) : value(value_) { } // Copy value to \p dst. void CopyTo(POD* dst) const { std::copy(value, value + extent, dst); } // Compare for equality. bool operator==(const _MakeIndexedValue& rhs) const { return std::equal(value, value + extent, rhs.value); } // Compare for less-than. bool operator<(const _MakeIndexedValue& rhs) const { for (size_t i = 0; i != extent; ++i) { if (value[i] < rhs.value[i]) { return true; } if (rhs.value[i] < value[i]) { return false; } } return false; } }; /// Make the values indexed. This stores only unique values and makes /// an index vector with an element for each original value indexing the /// unique value. template static void _MakeIndexed(_SampleForAlembic* values) { // A map of values to an index. typedef _MakeIndexedValue Value; typedef std::map IndexMap; typedef std::pair IndexMapResult; typedef _SampleForAlembic::IndexArray IndexArray; typedef IndexArray::value_type Index; // Allocate a vector of indices with the right size. const size_t n = values->GetCount() / extent; _SampleForAlembic::IndexArrayPtr indicesPtr(new IndexArray(n, 0)); IndexArray& indices = *indicesPtr; // Find unique values. Index index = 0; IndexMap indexMap; std::vector unique; const POD* ptr = values->GetDataAs(); for (size_t i = 0; i != n; ptr += extent, ++i) { const IndexMapResult result = indexMap.insert(std::make_pair(Value(ptr), index)); if (result.second) { // Found a unique value. unique.push_back(result.first->first); ++index; } indices[i] = result.first->second; } // If there are enough duplicates use indexing otherwise don't. if (n * sizeof(POD[extent]) <= unique.size() * sizeof(POD[extent]) + n * sizeof(uint32_t)) { return; } // Build the result. const size_t numPODs = extent * unique.size(); boost::shared_array uniqueBuffer(new POD[numPODs]); for (size_t i = 0, n = unique.size(); i != n; ++i) { unique[i].CopyTo(uniqueBuffer.get() + i * extent); } // Create a new sample object with the indexes. _SampleForAlembic result(uniqueBuffer, numPODs); result.SetIndices(indicesPtr); // Cut over. *values = result; } // // Data copy/conversion functions. // // Copy to a T. template void _Copy( const _WriterSchema& schema, double time, const UsdSamples& samples, DST* dst, R (DST::*method)(T)) { typedef typename boost::remove_const< typename boost::remove_reference::type >::type SampleValueType; const SdfValueTypeName& usdType = samples.GetTypeName(); const _WriterSchema::Converter& converter = schema.GetConverter(usdType); // Make a sample holding a value of type SampleValueType. _SampleForAlembic sample = _MakeSample(schema, converter, usdType, samples.Get(time)); if (! _CheckSample(sample, samples, usdType)) { return; } // Write to dst. (dst->*method)(*sample.GetDataAs()); } // Copy to a T with explicit converter. template void _Copy( const _WriterSchema& schema, const _WriterSchema::Converter& converter, double time, const UsdSamples& samples, DST* dst, R (DST::*method)(T)) { typedef typename boost::remove_const< typename boost::remove_reference::type >::type SampleValueType; const SdfValueTypeName& usdType = samples.GetTypeName(); // Make a sample holding a value of type SampleValueType. static const bool skipAlembicTypeCheck = true; _SampleForAlembic sample = _MakeSample(schema, converter, usdType, samples.Get(time), skipAlembicTypeCheck); if (! _CheckSample(sample, samples, usdType)) { return; } // Write to dst. (dst->*method)(*sample.GetDataAs()); } // Copy to a TypedArraySample. The client *must* hold the returned // _SampleForAlembic until the sample is finally consumed. template _SampleForAlembic _Copy( const _WriterSchema& schema, const _WriterSchema::Converter& converter, double time, const UsdSamples& samples, DST* dst, R (DST::*method)(const TypedArraySample&), bool skipAlembicTypeCheck = true) { typedef T SampleValueTraits; typedef typename SampleValueTraits::value_type Type; typedef TypedArraySample AlembicSample; // The property is ultimately an array of this type where each element // has length extent. typedef typename PODTraitsFromEnum< SampleValueTraits::pod_enum>::value_type PodType; static const int extent = SampleValueTraits::extent; const SdfValueTypeName& usdType = samples.GetTypeName(); // Make a sample holding an array of type PodType. _SampleForAlembic sample = _MakeSample(schema, converter, usdType, samples.Get(time), skipAlembicTypeCheck); if (! _CheckSample(sample, samples, usdType)) { return sample; } // Write to dst. (dst->*method)(AlembicSample(sample.GetDataAs(), sample.GetCount() / extent)); return sample; } // Copy to a TypedArraySample. The client *must* hold the returned // _SampleForAlembic until the sample is finally consumed. template _SampleForAlembic _Copy( const _WriterSchema& schema, double time, const UsdSamples& samples, DST* dst, R (DST::*method)(const TypedArraySample&)) { static const bool skipAlembicTypeCheck = true; return _Copy(schema, schema.GetConverter(samples.GetTypeName()), time, samples, dst, method, !skipAlembicTypeCheck); } // Copy to a OTypedGeomParam::Sample. The client *must* hold the returned // _SampleForAlembic until the sample is finally consumed. // XXX: For some reason the compiler can't deduce the type T so clients // are forced to provide it. template _SampleForAlembic _Copy( const _WriterSchema& schema, double time, const UsdSamples& valueSamples, DST* sample, R (DST::*method)(const typename OTypedGeomParam::Sample&)) { typedef T SampleValueTraits; typedef typename SampleValueTraits::value_type Type; typedef typename OTypedGeomParam::Sample AlembicSample; typedef TypedArraySample AlembicValueSample; // The property is ultimately an array of this type where each element // has length extent. typedef typename PODTraitsFromEnum< SampleValueTraits::pod_enum>::value_type PodType; static const int extent = SampleValueTraits::extent; const SdfValueTypeName& usdType = valueSamples.GetTypeName(); const _WriterSchema::Converter& converter = schema.GetConverter(usdType); // Make a sample holding an array of type PodType. _SampleForAlembic vals = _MakeSample(schema, converter, usdType, valueSamples.Get(time)); if (! _CheckSample(vals, valueSamples, usdType)) { return vals; } // Get the interpolation. VtValue value = valueSamples.GetField(UsdGeomTokens->interpolation); const GeometryScope geoScope = value.IsHolding() ? _GetGeometryScope(value.UncheckedGet()) : kUnknownScope; // Make the values indexed if desired. _MakeIndexed(&vals); // Get the indices. if (_SampleForAlembic::IndexArrayPtr indicesPtr = vals.GetIndices()) { // Note that the indices pointer is valid so long as vals is valid. const _SampleForAlembic::IndexArray& indices = *indicesPtr; UInt32ArraySample indicesSample(&indices[0], indices.size()); (sample->*method)( AlembicSample( AlembicValueSample(vals.GetDataAs(), vals.GetCount() / extent), indicesSample, geoScope)); } else { (sample->*method)( AlembicSample( AlembicValueSample(vals.GetDataAs(), vals.GetCount() / extent), geoScope)); } return vals; } // Copy to a scalar property. static void _Copy( const _WriterSchema& schema, const _WriterSchema::Converter& converter, double time, const UsdSamples& samples, OScalarProperty* property) { const SdfValueTypeName& usdType = samples.GetTypeName(); const DataType& dataType = property->getDataType(); // Make a sample holding a value of the type expected by the property. static const bool skipAlembicTypeCheck = true; _SampleForAlembic sample = _MakeSample(schema, converter, usdType, samples.Get(time), dataType, skipAlembicTypeCheck); if (! _CheckSample(sample, samples, usdType)) { return; } // Write to dst. property->set(sample.GetData()); } // Copy to an array property. static void _Copy( const _WriterSchema& schema, const _WriterSchema::Converter& converter, double time, const UsdSamples& samples, OArrayProperty* property) { const SdfValueTypeName& usdType = samples.GetTypeName(); const DataType& dataType = property->getDataType(); // Make a sample holding an array of the type expected by the property. static const bool skipAlembicTypeCheck = true; _SampleForAlembic sample = _MakeSample(schema, converter, usdType, samples.Get(time), dataType, skipAlembicTypeCheck); if (! _CheckSample(sample, samples, usdType)) { return; } // Write to dst. Dimensions count(sample.GetCount() / dataType.getExtent()); property->set(ArraySample(sample.GetData(), dataType, count)); } static void _CopyXform( double time, const UsdSamples& samples, XformSample* sample) { const VtValue value = samples.Get(time); if (value.IsHolding()) { const GfMatrix4d& transform = value.UncheckedGet(); sample->addOp( XformOp(kMatrixOperation, kMatrixHint), M44d(reinterpret_cast(transform.GetArray()))); } else { TF_WARN("Expected type 'GfMatrix4d', got '%s'", ArchGetDemangled(value.GetTypeName()).c_str()); } } template static void _CopySelfBounds( double time, const UsdSamples& samples, DST* sample) { const VtValue value = samples.Get(time); if (value.IsHolding >()) { const VtArray& a = value.UncheckedGet >(); Box3d box(V3d(a[0][0], a[0][1], a[0][2]), V3d(a[1][0], a[1][1], a[1][2])); sample->setSelfBounds(box); } else { TF_WARN("Expected type 'VtArray', got '%s'", ArchGetDemangled(value.GetTypeName()).c_str()); } } static _SampleForAlembic _CopyVisibility(const VtValue& src) { const TfToken& value = src.UncheckedGet(); if (value.IsEmpty() || value == UsdGeomTokens->inherited) { return _SampleForAlembic(int8_t(kVisibilityDeferred)); } if (value == UsdGeomTokens->invisible) { return _SampleForAlembic(int8_t(kVisibilityHidden)); } return _ErrorSampleForAlembic(TfStringPrintf( "Unsupported invisibility '%s'", value.GetText())); } static _SampleForAlembic _CopySubdivisionScheme(const VtValue& src) { const TfToken& value = src.UncheckedGet(); if (value.IsEmpty() || value == UsdGeomTokens->catmullClark) { return _SampleForAlembic(std::string("catmull-clark")); } if (value == UsdGeomTokens->loop) { return _SampleForAlembic(std::string("loop")); } if (value == UsdGeomTokens->bilinear) { return _SampleForAlembic(std::string("bilinear")); } return _ErrorSampleForAlembic(TfStringPrintf( "Unsupported subdivisionScheme '%s'", value.GetText())); } static _SampleForAlembic _CopyInterpolateBoundary(const VtValue& src) { const TfToken& value = src.UncheckedGet(); if (value.IsEmpty() || value == UsdGeomTokens->edgeAndCorner) { return _SampleForAlembic(int32_t(1)); } if (value == UsdGeomTokens->none) { return _SampleForAlembic(int32_t(0)); } if (value == UsdGeomTokens->edgeOnly) { return _SampleForAlembic(int32_t(2)); } return _ErrorSampleForAlembic(TfStringPrintf( "Unsupported interpolateBoundary '%s'", value.GetText())); } static _SampleForAlembic _CopyFaceVaryingInterpolateBoundary(const VtValue& src) { const TfToken& value = src.UncheckedGet(); if (value.IsEmpty() || value == UsdGeomTokens->cornersPlus1 || value == UsdGeomTokens->cornersOnly || value == UsdGeomTokens->cornersPlus2) { return _SampleForAlembic(int32_t(1)); } if (value == UsdGeomTokens->all) { return _SampleForAlembic(int32_t(0)); } if (value == UsdGeomTokens->none) { return _SampleForAlembic(int32_t(2)); } if (value == UsdGeomTokens->boundaries) { return _SampleForAlembic(int32_t(3)); } return _ErrorSampleForAlembic(TfStringPrintf( "Unsupported faceVaryingLinearInterpolation '%s'", value.GetText())); } static _SampleForAlembic _CopyAdskColor(const VtValue& src) { const VtArray& color = src.UncheckedGet >(); std::vector result(color[0].GetArray(), color[0].GetArray() + 3); result.push_back(1.0); return _SampleForAlembic(result); } static _SampleForAlembic _CopyCurveBasis(const VtValue& src) { const TfToken& value = src.UncheckedGet(); if (value.IsEmpty() || value == UsdGeomTokens->none) { return _SampleForAlembic(kNoBasis); } if (value == UsdGeomTokens->bezier) { return _SampleForAlembic(kBezierBasis); } if (value == UsdGeomTokens->bspline) { return _SampleForAlembic(kBsplineBasis); } if (value == UsdGeomTokens->catmullRom) { return _SampleForAlembic(kCatmullromBasis); } return _ErrorSampleForAlembic(TfStringPrintf( "Unsupported curve basis '%s'", value.GetText())); } static _SampleForAlembic _CopyCurveType(const VtValue& src) { const TfToken& value = src.UncheckedGet(); if (value.IsEmpty() || value == UsdGeomTokens->none) { return _SampleForAlembic(kCubic); } if (value == UsdGeomTokens->linear) { return _SampleForAlembic(kLinear); } if (value == UsdGeomTokens->cubic) { return _SampleForAlembic(kCubic); } return _ErrorSampleForAlembic(TfStringPrintf( "Unsupported curve type '%s'", value.GetText())); } static _SampleForAlembic _CopyCurveWrap(const VtValue& src) { const TfToken& value = src.UncheckedGet(); if (value.IsEmpty() || value == UsdGeomTokens->none) { return _SampleForAlembic(kNonPeriodic); } if (value == UsdGeomTokens->nonperiodic) { return _SampleForAlembic(kNonPeriodic); } if (value == UsdGeomTokens->periodic) { return _SampleForAlembic(kPeriodic); } return _ErrorSampleForAlembic(TfStringPrintf( "Unsupported curve wrap '%s'", value.GetText())); } static _SampleForAlembic _CopyKnots(const VtValue& src) { const VtDoubleArray& value = src.UncheckedGet(); return _SampleForAlembic(std::vector(value.begin(), value.end())); } static _SampleForAlembic _CopyOrder(const VtValue& src) { const VtIntArray& value = src.UncheckedGet(); return _SampleForAlembic(std::vector(value.begin(), value.end())); } static _SampleForAlembic _CopyPointIds(const VtValue& src) { const VtInt64Array& value = src.UncheckedGet(); return _SampleForAlembic(std::vector(value.begin(), value.end())); } // ---------------------------------------------------------------------------- // // Property writers // static VtValue _GetField( const _PrimWriterContext& context, const TfToken& field, const TfToken& usdName) { return usdName.IsEmpty() ? context.GetField(field) : context.GetPropertyField(usdName, field); } static void _SetBoolMetadata( MetaData* metadata, const _PrimWriterContext& context, const TfToken& field, const TfToken& usdName = TfToken()) { VtValue value = _GetField(context, field, usdName); if (value.IsHolding()) { metadata->set(_AmdName(field), value.UncheckedGet() ? "true" : "false"); } } static void _SetStringMetadata( MetaData* metadata, const _PrimWriterContext& context, const TfToken& field, const TfToken& usdName = TfToken()) { VtValue value = _GetField(context, field, usdName); if (value.IsHolding()) { const std::string& tmp = value.UncheckedGet(); if (! tmp.empty()) { metadata->set(_AmdName(field), tmp); } } } static void _SetTokenMetadata( MetaData* metadata, const _PrimWriterContext& context, const TfToken& field, const TfToken& usdName = TfToken()) { VtValue value = _GetField(context, field, usdName); if (value.IsHolding()) { const TfToken& tmp = value.UncheckedGet(); if (! tmp.IsEmpty()) { metadata->set(_AmdName(field), tmp); } } } static void _SetDoubleMetadata( MetaData* metadata, const _PrimWriterContext& context, const TfToken& field, const TfToken& usdName = TfToken()) { VtValue value = _GetField(context, field, usdName); if (value.IsHolding()) { metadata->set(_AmdName(field), TfStringify(value)); } } static MetaData _GetPropertyMetadata( const _PrimWriterContext& context, const TfToken& usdName, const UsdSamples& samples) { MetaData metadata; VtValue value; // Custom. _SetBoolMetadata(&metadata, context, SdfFieldKeys->Custom, usdName); // Write the usd type for exact reverse conversion if we can't deduce it // when reading the Alembic. value = context.GetPropertyField(usdName, SdfFieldKeys->TypeName); const TfToken typeNameToken = value.IsHolding() ? value.UncheckedGet() : TfToken(); const SdfValueTypeName typeName = SdfSchema::GetInstance().FindType(typeNameToken); const SdfValueTypeName roundTripTypeName = context.GetSchema().FindConverter( context.GetSchema().FindConverter(typeName)); if (typeName != roundTripTypeName) { metadata.set(_AmdName(SdfFieldKeys->TypeName), typeNameToken); } // Note a "winning" Default as a single alembic sample that should come // back into USD as a Default if (!samples.IsTimeSampled() && samples.GetNumSamples() == 1) { metadata.set(_AmdName(UsdAbcCustomMetadata->singleSampleAsDefault), "true"); } // Set the interpretation if there is one. const std::string interpretation = _GetInterpretation(typeName); if (! interpretation.empty()) { metadata.set("interpretation", interpretation); } // Other Sdf metadata. _SetStringMetadata(&metadata, context, SdfFieldKeys->DisplayGroup, usdName); _SetStringMetadata(&metadata, context, SdfFieldKeys->Documentation,usdName); _SetBoolMetadata(&metadata, context, SdfFieldKeys->Hidden, usdName); value = context.GetPropertyField(usdName, SdfFieldKeys->Variability); if (value.IsHolding() && value.UncheckedGet() == SdfVariabilityUniform) { metadata.set(_AmdName(SdfFieldKeys->Variability), "uniform"); } value = context.GetPropertyField(usdName, UsdGeomTokens->interpolation); if (value.IsHolding()) { SetGeometryScope(metadata, _GetGeometryScope(value.UncheckedGet())); } // Custom metadata. _SetStringMetadata(&metadata, context, UsdAbcCustomMetadata->riName, usdName); _SetStringMetadata(&metadata, context, UsdAbcCustomMetadata->riType, usdName); _SetBoolMetadata(&metadata, context, UsdAbcCustomMetadata->gprimDataRender, usdName); return metadata; } static bool _WriteOutOfSchemaProperty( _PrimWriterContext* context, OCompoundProperty parent, const TfToken& usdName, const std::string& alembicName) { // Ignore non-attributes. if (context->GetSpecType(usdName) != SdfSpecTypeAttribute) { if (context->IsFlagSet(UsdAbc_AlembicContextFlagNames->verbose)) { TF_WARN("No conversion for <%s> with spec type '%s'", context->GetPath().AppendProperty(usdName).GetText(), TfEnum::GetDisplayName( context->GetSpecType(usdName)).c_str()); } return false; } context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples samples = context->ExtractSamples(usdName); if (context->GetSchema().IsValid(samples)) { const SdfValueTypeName& usdType = samples.GetTypeName(); const _WriterSchema::Converter& converter = context->GetSchema().GetConverter(usdType); if (context->GetSchema().IsShaped(samples)) { OArrayProperty property(parent, alembicName, context->GetSchema().GetDataType(samples), _GetPropertyMetadata(*context, usdName, samples)); for (double time : context->GetSampleTimesUnion()) { _Copy(context->GetSchema(), converter, time, samples,&property); } property.setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } else { OScalarProperty property(parent, alembicName, context->GetSchema().GetDataType(samples), _GetPropertyMetadata(*context, usdName, samples)); for (double time : context->GetSampleTimesUnion()) { _Copy(context->GetSchema(), converter, time, samples,&property); } property.setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } return true; } else { return false; } } template static void _WriteGenericProperty( _PrimWriterContext* context, OCompoundProperty parent, const _WriterSchema::Converter& converter, const DataType& alembicDataType, const TfToken& usdName, const std::string& alembicName) { // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples samples = context->ExtractSamples(usdName); if (context->GetSchema().IsValid(samples)) { T property(parent, alembicName, alembicDataType, _GetPropertyMetadata(*context, usdName, samples)); for (double time : context->GetSampleTimesUnion()) { _Copy(context->GetSchema(), converter, time, samples, &property); } property.setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } } static void _WriteGenericScalar( _PrimWriterContext* context, const _WriterSchema::Converter& converter, const DataType& alembicDataType, const TfToken& usdName, const std::string& alembicName) { _WriteGenericProperty(context, context->GetParent().GetProperties(), converter, alembicDataType, usdName, alembicName); } /* Not currently used. static void _WriteGenericArray( _PrimWriterContext* context, const TfToken& usdType, const TfToken& usdName, const std::string& alembicName) { // XXX: This doesn't have the correct arguments. _WriteGenericProperty(context, context->GetParent().GetProperties(), conv, usdName, alembicName); } */ // // Abstract object writers // // Helper for converting property namespaces into a hierarchy of // OCompoundProperty. class _CompoundPropertyTable { public: _CompoundPropertyTable(OCompoundProperty root) { _table[TfTokenVector()] = root; } OCompoundProperty FindOrCreate(const TfTokenVector& names); private: OCompoundProperty _FindOrCreate(TfTokenVector& names); private: std::map _table; }; OCompoundProperty _CompoundPropertyTable::FindOrCreate(const TfTokenVector& names) { OCompoundProperty result = _table[names]; if (! result.valid()) { TfTokenVector tmpNames = names; return _FindOrCreate(tmpNames); } else { return result; } } OCompoundProperty _CompoundPropertyTable::_FindOrCreate(TfTokenVector& names) { OCompoundProperty& result = _table[names]; if (! result.valid()) { // We don't have an entry for this path. Recursively get parent // and add the child. TfToken name = names.back(); names.pop_back(); OCompoundProperty parent = _FindOrCreate(names); result = OCompoundProperty(parent, name); } return result; } static void _WriteNamespacedPropertyGroup( _PrimWriterContext* context, const TfToken& namespaceName, const std::function& getParentProperty) { // First check if there are any properties to convert. We only ask // for that property if so, because asking for it will create it on // demand and we don't want to create it if unnecessary. Note, // however, that we don't confirm that conversion will succeed so // we may still create the property with nothing in it. bool anyProperties = false; for (const auto& name : context->GetUnextractedNames()) { TfTokenVector names = SdfPath::TokenizeIdentifierAsTokens(name); if (names.size() >= 2 && names[0] == namespaceName) { anyProperties = true; break; } } // Convert everything in the namespace into the parent compound property. // Strip the namespace name from each name before copying. if (anyProperties) { OCompoundProperty parent = getParentProperty(); if (!parent.valid()) { // We can't get the parent property. Just put the properties // at the top level. parent = context->GetParent().GetProperties(); } // Support sub-namespaces as compound properties. _CompoundPropertyTable subgroups(parent); // Convert each property. // We have to remap primvars:st:indices to primvars:uv:indices. for (const auto& name : context->GetUnextractedNames()) { TfTokenVector names = name == UsdAbcPropertyNames->stIndices ? SdfPath::TokenizeIdentifierAsTokens( UsdAbcPropertyNames->uvIndices) : SdfPath::TokenizeIdentifierAsTokens(name); if (names.size() >= 2 && names[0] == namespaceName) { // Remove the namespace prefix. names.erase(names.begin()); // The Alembic name is just the last name (i.e. no namespaces). const std::string alembicName = names.back(); names.pop_back(); // Get/create the subgroup compound property. OCompoundProperty group = subgroups.FindOrCreate(names); // Write it. _WriteOutOfSchemaProperty(context, group, name, alembicName); } } } } static void _WriteArbGeomParams(_PrimWriterContext* context) { // Convert everything in the primvars namespace to the getArbGeomParams() // compound property. const _Parent& parent = context->GetParent(); _WriteNamespacedPropertyGroup(context, UsdAbcPropertyNames->primvars, std::bind(&_Parent::GetArbGeomParams, std::cref(parent))); } static void _WriteUserProperties(_PrimWriterContext* context) { // Convert everything in the userProperties namespace to the // getUserProperties() compound property. const _Parent& parent = context->GetParent(); _WriteNamespacedPropertyGroup(context, UsdAbcPropertyNames->userProperties, std::bind(&_Parent::GetUserProperties, std::cref(parent))); } static void _WriteGprim(_PrimWriterContext* context) { // extent is handled by GeomBase subclasses automatically. // Write the orientation. _WriteOutOfSchemaProperty(context, context->GetParent().GetProperties(), UsdGeomTokens->orientation, _AmdName(UsdGeomTokens->orientation)); } static void _WriteMayaColor(_PrimWriterContext* context) { static const TfToken displayColor("primvars:displayColor"); static const TfToken name("adskDiffuseColor"); UsdSamples color(context->GetPath(), displayColor); if (context->GetData().HasSpec( context->GetPath().AppendProperty(displayColor))) { color = UsdSamples(context->GetPath(), displayColor, context->GetData()); } if (color.IsEmpty()) { // Copy existing Maya color. if (! _WriteOutOfSchemaProperty(context, context->GetParent().GetSchema(), name, name)) { return; } } else { // Use displayColor. UsdAbc_TimeSamples sampleTimes; color.AddTimes(&sampleTimes); MetaData metadata; metadata.set("interpretation", "rgba"); OScalarProperty property(context->GetParent().GetSchema(), name, DataType(kFloat32POD, 4), metadata); for (double time : sampleTimes) { _Copy(context->GetSchema(), _CopyAdskColor, time, color,&property); } property.setTimeSampling(context->AddTimeSampling(sampleTimes)); // Don't try writing the Maya color. context->ExtractSamples(name); } } static void _WriteUnknownMayaColor(_PrimWriterContext* context) { // XXX -- Write the Maya color to a .geom OCompoundProperty. } static void _WriteImageable(_PrimWriterContext* context) { _WriteGenericScalar(context, _CopyVisibility, DataType(kInt8POD), UsdGeomTokens->visibility, kVisibilityPropertyName); } static void _WriteOther(_PrimWriterContext* context) { // Write every unextracted property to Alembic using default converters. // This handles any property we don't have specific rules for. Any // Usd name with namespaces is written to Alembic with the namespaces // embedded in the name. // for (const auto& name : context->GetUnextractedNames()) { _WriteOutOfSchemaProperty(context, context->GetParent().GetProperties(), name, context->GetAlembicPropertyName(name)); } } // // Object writers -- these create an OObject. // void _AddOrderingMetadata( const _PrimWriterContext& context, const TfToken& fieldName, const std::string& metadataName, MetaData* metadata) { VtValue value = context.GetField(fieldName); if (value.IsHolding()) { const TfTokenVector& order = value.UncheckedGet(); if (! order.empty()) { // Write as space separated names all surrounded by square // brackets. metadata->set(metadataName, TfStringify(order)); } } } static MetaData _GetPrimMetadata(const _PrimWriterContext& context) { MetaData metadata; // Add "over". if (_IsOver(context)) { metadata.set(_AmdName(SdfFieldKeys->Specifier), "over"); } _SetBoolMetadata(&metadata, context, SdfFieldKeys->Active); _SetBoolMetadata(&metadata, context, SdfFieldKeys->Hidden); _SetStringMetadata(&metadata, context, SdfFieldKeys->DisplayGroup); _SetStringMetadata(&metadata, context, SdfFieldKeys->Documentation); _SetTokenMetadata(&metadata, context, SdfFieldKeys->Kind); // Add name children ordering. _AddOrderingMetadata(context, SdfFieldKeys->PrimOrder, _AmdName(SdfFieldKeys->PrimOrder), &metadata); // Add property ordering. _AddOrderingMetadata(context, SdfFieldKeys->PropertyOrder, _AmdName(SdfFieldKeys->PropertyOrder), &metadata); return metadata; } static void _WriteRoot(_PrimWriterContext* context) { // Create the Alembic root. shared_ptr root(new OObject(context->GetArchive(), kTop)); context->SetParent(root); // Make the root metadata. MetaData metadata; _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->StartTimeCode); _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->EndTimeCode); // Always author a value for timeCodesPerSecond and framesPerSecond // to preserve proper round-tripping from USD->alembic->USD. // // First, set them to the corresponding fallback values, then overwrite them // with the values from the input layer. // const SdfSchema &sdfSchema = SdfSchema::GetInstance(); double fallbackTimeCodesPerSecond = sdfSchema.GetFallback( SdfFieldKeys->TimeCodesPerSecond).Get(); double fallbackFramesPerSecond = sdfSchema.GetFallback( SdfFieldKeys->FramesPerSecond).Get(); metadata.set(_AmdName(SdfFieldKeys->TimeCodesPerSecond), TfStringify(fallbackTimeCodesPerSecond)); metadata.set(_AmdName(SdfFieldKeys->FramesPerSecond), TfStringify(fallbackFramesPerSecond)); _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->TimeCodesPerSecond); _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->FramesPerSecond); // XXX(Frame->Time): backwards compatibility _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->StartFrame); _SetDoubleMetadata(&metadata, *context, SdfFieldKeys->EndFrame); _SetTokenMetadata(&metadata, *context, SdfFieldKeys->DefaultPrim); _SetTokenMetadata(&metadata, *context, UsdGeomTokens->upAxis); // Create a compound property to hang metadata off of. We'd kinda like // to put this on the top object but that was created when we opened // the file, prior to knowing which SdfAbstractData we were writing. OCompoundProperty prop(root->getProperties(), "Usd", metadata); } template static bool _ExtractWithFallback(UsdSamples const &samples, double time, const UsdPrimDefinition *primDef, TfToken const &propertyName, T *val) { if (samples.IsEmpty()){ if (!primDef) { return false; } return primDef->GetAttributeFallbackValue(propertyName, val); } const VtValue value = samples.Get(time); if (value.IsHolding()) { *val = value.UncheckedGet(); return true; } else { TF_WARN("Expected type '%s', but found '%s' for %s", ArchGetDemangled(typeid(T)).c_str(), ArchGetDemangled(value.GetTypeName()).c_str(), propertyName.GetText()); return false; } } static void _WriteCameraParameters(_PrimWriterContext* context) { typedef OCamera Type; // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Should be OK doing a VtValue::Get here, as the only way we should have // been able to get here is by dispatching on prim typeName. TfToken primType = context->GetField(SdfFieldKeys->TypeName).Get(); // Collect the properties we need to compute the frustum. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples focalLength = context->ExtractSamples(UsdGeomTokens->focalLength, SdfValueTypeNames->Float); UsdSamples horizontalAperture = context->ExtractSamples(UsdGeomTokens->horizontalAperture, SdfValueTypeNames->Float); UsdSamples verticalAperture = context->ExtractSamples(UsdGeomTokens->verticalAperture, SdfValueTypeNames->Float); UsdSamples horizontalApertureOffset = context->ExtractSamples(UsdGeomTokens->horizontalApertureOffset, SdfValueTypeNames->Float); UsdSamples verticalApertureOffset = context->ExtractSamples(UsdGeomTokens->verticalApertureOffset, SdfValueTypeNames->Float); UsdSamples clippingRange = context->ExtractSamples(UsdGeomTokens->clippingRange, SdfValueTypeNames->Float2); const UsdPrimDefinition *primDef = UsdSchemaRegistry::GetInstance().FindConcretePrimDefinition(primType); // Copy all the samples to set up alembic camera frustum. typedef CameraSample SampleT; for (double time : context->GetSampleTimesUnion()) { // Build the sample. SampleT sample; { // Horizontal aperture is in cm in ABC, but mm in USD float value; if (_ExtractWithFallback(horizontalAperture, time, primDef, UsdGeomTokens->horizontalAperture, &value)){ sample.setHorizontalAperture(value / 10.0); } } { // Vertical aperture is in cm in ABC, but mm in USD float value; if (_ExtractWithFallback(verticalAperture, time, primDef, UsdGeomTokens->verticalAperture, &value)){ sample.setVerticalAperture(value / 10.0); } } { // Horizontal aperture is in cm in ABC, but mm in USD float value; if (_ExtractWithFallback(horizontalApertureOffset, time, primDef, UsdGeomTokens->horizontalApertureOffset, &value)){ sample.setHorizontalFilmOffset(value / 10.0); } } { // Vertical aperture is in cm in ABC, but mm in USD float value; if (_ExtractWithFallback(verticalApertureOffset, time, primDef, UsdGeomTokens->verticalApertureOffset, &value)){ sample.setVerticalFilmOffset(value / 10.0); } } { // Focal length in USD and ABC is both in mm float value; if (_ExtractWithFallback(focalLength, time, primDef, UsdGeomTokens->focalLength, &value)){ sample.setFocalLength(value); } } { GfVec2f value; if (_ExtractWithFallback(clippingRange, time, primDef, UsdGeomTokens->clippingRange, &value)){ sample.setNearClippingPlane(value[0]); sample.setFarClippingPlane(value[1]); } } // Write the sample. object->getSchema().set(sample); } // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static void _WriteUnknown(_PrimWriterContext* context) { typedef OObject Type; // Get the standard metadata and add the Usd prim type, if any. MetaData metadata = _GetPrimMetadata(*context); const VtValue value = context->GetField(SdfFieldKeys->TypeName); if (value.IsHolding()) { const TfToken& typeName = value.UncheckedGet(); if (! typeName.IsEmpty()) { metadata.set(_AmdName(SdfFieldKeys->TypeName), typeName); } } // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), metadata)); context->SetParent(object); } static void _WriteXform(_PrimWriterContext* context) { // Collect the properties we need. We'll need these to compute the // metadata. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples xformOpOrder = context->ExtractSamples( UsdGeomTokens->xformOpOrder, SdfValueTypeNames->TokenArray); bool hasXformOpOrder = (context->GetSampleTimesUnion().size()>0); // Clear samples from xformOpOrder. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); // XXX: NOTE // We can't use the GetLocalTranformation API available in UsdGeomXformable // here, as there is no UsdPrim (or UsdStage) from which we can construct a // UsdGeomXformable schema object. Hence, for now, if xformOpOrder has a // value, then assuming that the custom "xformOp:transform" attribute will // have the composed local transformation in it. // // If xformOpOrder has no authored value, then fallback to reading the // old-style transform attribute. // const TfToken &transformAttrName = hasXformOpOrder ? _tokens->xformOpTransform : _tokens->transform; const SdfValueTypeName &transformValueType = hasXformOpOrder ? SdfValueTypeNames->Matrix4d : SdfValueTypeNames->Matrix4d; if (hasXformOpOrder) { // Extract and clear samples from the old-style transform attribute, if // it exists, so it doesn't get written out as blind data. context->ExtractSamples(_tokens->transform, SdfValueTypeNames->Matrix4d); context->SetSampleTimesUnion(UsdAbc_TimeSamples()); } UsdSamples transform = context->ExtractSamples(transformAttrName, transformValueType); // At this point, all transform related attributes (including all xformOps) // should have been extracted. Validate here to make sure there aren't // any unextracted xformOp attributes. for (const auto& name : context->GetUnextractedNames()) { if (UsdGeomXformOp::IsXformOp(name)) { TF_RUNTIME_ERROR("Found unextracted property '%s' in xformOp " "namespace.", name.GetText()); } } // Collect the metadata. Here we have to combine metadata from the // prim and from the transform attribute since Alembic will not give // us a chance to set metadata on the Alembic properties. MetaData metadata = _GetPrimMetadata(*context); { // Get the transform property metadata. MetaData transformMetadata = _GetPropertyMetadata(*context, transformAttrName, transform); // Merge the property metadata into the prim metadata in a way we // can extract later for round-tripping. for (MetaData::const_iterator i = transformMetadata.begin(); i != transformMetadata.end(); ++i) { if (! i->second.empty()) { metadata.set("Usd.transform:" + i->first, i->second); } } } // Create the object and make it the parent. OXformPtr object(new OXform(context->GetParent(), context->GetAlembicPrimName(), metadata)); context->SetParent(object); // Copy all the samples. typedef XformSample SampleT; SampleT sample; for (double time : context->GetSampleTimesUnion()) { // Build the sample. sample.reset(); _CopyXform(time, transform, &sample); sample.setInheritsXforms(true); // Write it. object->getSchema().set(sample); } // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static void _WriteXformParent(_PrimWriterContext* context) { // Used to split transform into a parent object. _WriteXform(context); // Put a "Shape" suffix on the geometry. context->PushSuffix("Shape"); } static void _WritePolyMesh(_PrimWriterContext* context) { typedef OPolyMesh Type; const _WriterSchema& schema = context->GetSchema(); // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples extent = context->ExtractSamples(UsdGeomTokens->extent, SdfValueTypeNames->Float3Array); UsdSamples points = context->ExtractSamples(UsdGeomTokens->points, SdfValueTypeNames->Point3fArray); UsdSamples velocities = context->ExtractSamples(UsdGeomTokens->velocities, SdfValueTypeNames->Vector3fArray); UsdSamples faceVertexIndices = context->ExtractSamples(UsdGeomTokens->faceVertexIndices, SdfValueTypeNames->IntArray); UsdSamples faceVertexCounts = context->ExtractSamples(UsdGeomTokens->faceVertexCounts, SdfValueTypeNames->IntArray); UsdSamples normals = context->ExtractSamples(UsdGeomTokens->normals, SdfValueTypeNames->Normal3fArray); // Default to look for primvars:st with type TexCoord2fArray or Float2Array UsdSamples uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))? (context->ExtractSamples(UsdAbcPropertyNames->st, SdfValueTypeNames->TexCoord2fArray, SdfValueTypeNames->Float2Array)) : (context->ExtractSamples(UsdAbcPropertyNames->st, SdfValueTypeNames->TexCoord2fArray)); // At this point if matching uv set has not been found, // look for primvars:uv with type TexCoord2fArray or Float2Array if (uv.IsEmpty()) { uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))? (context->ExtractSamples(UsdAbcPropertyNames->uv, SdfValueTypeNames->TexCoord2fArray, SdfValueTypeNames->Float2Array)) : (context->ExtractSamples(UsdAbcPropertyNames->uv, SdfValueTypeNames->TexCoord2fArray)); context->RemoveSamples(UsdAbcPropertyNames->stIndices); } else { // We found a primvars:st, so remove samples with name "primvars:uv" context->RemoveSamples(UsdAbcPropertyNames->uv); context->RemoveSamples(UsdAbcPropertyNames->uvIndices); } // Adjust faceVertexIndices for winding order. _ReverseWindingOrder(context, &faceVertexIndices, faceVertexCounts); // Copy all the samples. typedef Type::schema_type::Sample SampleT; SampleT sample; for (double time : context->GetSampleTimesUnion()) { // Build the sample. sample.reset(); _CopySelfBounds(time, extent, &sample); _SampleForAlembic alembicPoints = _Copy(schema, time, points, &sample, &SampleT::setPositions); _SampleForAlembic alembicVelocities = _Copy(schema, time, velocities, &sample, &SampleT::setVelocities); _SampleForAlembic alembicFaceIndices = _Copy(schema, time, faceVertexIndices, &sample, &SampleT::setFaceIndices); _SampleForAlembic alembicFaceCounts = _Copy(schema, time, faceVertexCounts, &sample, &SampleT::setFaceCounts); _SampleForAlembic alembicNormals = _Copy(schema, time, normals, &sample, &SampleT::setNormals); _SampleForAlembic alembicUVs = _Copy(schema, time, uv, &sample, &SampleT::setUVs); // Write the sample. object->getSchema().set(sample); } // Alembic doesn't need this since it knows it's a PolyMesh. context->ExtractSamples(UsdGeomTokens->subdivisionScheme); // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static void _WriteFaceSet(_PrimWriterContext* context) { typedef OFaceSet Type; const _WriterSchema& schema = context->GetSchema(); // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples indices = context->ExtractSamples(UsdGeomTokens->indices, SdfValueTypeNames->IntArray); // The familyType is contained in the parent prim, so we // contruct a new _PrimWriterContext to access it. SdfPath parentPath = context->GetPath().GetParentPath(); _PrimWriterContext parentPrimContext(context->GetWriterContext(), context->GetParent(), parentPath); UsdSamples familyType = parentPrimContext.ExtractSamples( UsdAbcPropertyNames->defaultFamilyTypeAttributeName, SdfValueTypeNames->Token); // Copy all the samples. typedef Type::schema_type::Sample SampleT; SampleT sample; for (double time : context->GetSampleTimesUnion()) { // Build the sample. sample.reset(); _SampleForAlembic alembicFaces = _Copy(schema, time, indices, &sample, &SampleT::setFaces); // Write the sample. object->getSchema().set(sample); } // It's possible that our default family name "materialBind", is not // set on the prim. In that case, use kFaceSetNonExclusive. FaceSetExclusivity faceSetExclusivity = kFaceSetNonExclusive; if (!familyType.IsEmpty()) { double time = UsdTimeCode::EarliestTime().GetValue(); const TfToken& value = familyType.Get(time).UncheckedGet(); if (!value.IsEmpty() && (value == UsdGeomTokens->partition || value == UsdGeomTokens->nonOverlapping)) { faceSetExclusivity = kFaceSetExclusive; } } // Face set exclusivity is not a property of the sample. Instead, it's set // on the object schema and not time sampled. object->getSchema().setFaceExclusivity(faceSetExclusivity); // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } // As of Alembic-1.5.1, OSubD::schema_type::Sample has a bug: // setHoles() actually sets cornerIndices. The member, m_holes, is // protected so we subclass and fix setHoles(). // XXX: Remove this when Alembic is fixed. class MyOSubDSample : public OSubD::schema_type::Sample { public: void setHoles( const Abc::Int32ArraySample &iHoles ) { m_holes = iHoles; } }; static void _WriteSubD(_PrimWriterContext* context) { typedef OSubD Type; const _WriterSchema& schema = context->GetSchema(); // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples extent = context->ExtractSamples(UsdGeomTokens->extent, SdfValueTypeNames->Float3Array); UsdSamples points = context->ExtractSamples(UsdGeomTokens->points, SdfValueTypeNames->Point3fArray); UsdSamples velocities = context->ExtractSamples(UsdGeomTokens->velocities, SdfValueTypeNames->Vector3fArray); UsdSamples faceVertexIndices = context->ExtractSamples(UsdGeomTokens->faceVertexIndices, SdfValueTypeNames->IntArray); UsdSamples faceVertexCounts = context->ExtractSamples(UsdGeomTokens->faceVertexCounts, SdfValueTypeNames->IntArray); UsdSamples subdivisionScheme = context->ExtractSamples(UsdGeomTokens->subdivisionScheme, SdfValueTypeNames->Token); UsdSamples interpolateBoundary = context->ExtractSamples(UsdGeomTokens->interpolateBoundary, SdfValueTypeNames->Token); UsdSamples faceVaryingLinearInterpolation = context->ExtractSamples(UsdGeomTokens->faceVaryingLinearInterpolation, SdfValueTypeNames->Token); UsdSamples holeIndices = context->ExtractSamples(UsdGeomTokens->holeIndices, SdfValueTypeNames->IntArray); UsdSamples cornerIndices = context->ExtractSamples(UsdGeomTokens->cornerIndices, SdfValueTypeNames->IntArray); UsdSamples cornerSharpnesses = context->ExtractSamples(UsdGeomTokens->cornerSharpnesses, SdfValueTypeNames->FloatArray); UsdSamples creaseIndices = context->ExtractSamples(UsdGeomTokens->creaseIndices, SdfValueTypeNames->IntArray); UsdSamples creaseLengths = context->ExtractSamples(UsdGeomTokens->creaseLengths, SdfValueTypeNames->IntArray); UsdSamples creaseSharpnesses = context->ExtractSamples(UsdGeomTokens->creaseSharpnesses, SdfValueTypeNames->FloatArray); // Default to look for primvars:st with type TexCoord2fArray or Float2Array UsdSamples uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))? (context->ExtractSamples(UsdAbcPropertyNames->st, SdfValueTypeNames->TexCoord2fArray, SdfValueTypeNames->Float2Array)) : (context->ExtractSamples(UsdAbcPropertyNames->st, SdfValueTypeNames->TexCoord2fArray)); // At this point if matching uv set has not been found, // look for primvars:uv with type TexCoord2fArray or Float2Array if (uv.IsEmpty()) { uv = (TfGetEnvSetting(USD_ABC_READ_FLOAT2_AS_UV))? (context->ExtractSamples(UsdAbcPropertyNames->uv, SdfValueTypeNames->TexCoord2fArray, SdfValueTypeNames->Float2Array)) : (context->ExtractSamples(UsdAbcPropertyNames->uv, SdfValueTypeNames->TexCoord2fArray)); context->RemoveSamples(UsdAbcPropertyNames->stIndices); } else { // We found a primvars:st, so remove samples with name "primvars:uv" context->RemoveSamples(UsdAbcPropertyNames->uv); context->RemoveSamples(UsdAbcPropertyNames->uvIndices); } // Adjust faceVertexIndices for winding order. _ReverseWindingOrder(context, &faceVertexIndices, faceVertexCounts); // Copy all the samples. typedef MyOSubDSample MySampleT; typedef Type::schema_type::Sample SampleT; MySampleT mySample; SampleT& sample = mySample; for (double time : context->GetSampleTimesUnion()) { // Build the sample. Usd defaults both interpolateBoundary and // faceVaryingLinearInterpolation to edgeAndCorner (1 in both cases) // but Alembic defaults to none (0) and bilinear (0) respectively, // so set these first in case we have no opinion (converters will // not be invoked and no value assigned if the Usd value is absent). sample.reset(); sample.setInterpolateBoundary(1); sample.setFaceVaryingInterpolateBoundary(1); _CopySelfBounds(time, extent, &sample); _SampleForAlembic alembicPositions = _Copy(schema, time, points, &sample, &SampleT::setPositions); _SampleForAlembic alembicVelocities = _Copy(schema, time, velocities, &sample, &SampleT::setVelocities); _SampleForAlembic alembicFaceIndices = _Copy(schema, time, faceVertexIndices, &sample, &SampleT::setFaceIndices); _SampleForAlembic alembicFaceCounts = _Copy(schema, time, faceVertexCounts, &sample, &SampleT::setFaceCounts); _Copy(schema, _CopySubdivisionScheme, time, subdivisionScheme, &sample, &SampleT::setSubdivisionScheme); _Copy(schema, _CopyInterpolateBoundary, time, interpolateBoundary, &sample, &SampleT::setInterpolateBoundary); _Copy(schema, _CopyFaceVaryingInterpolateBoundary, time, faceVaryingLinearInterpolation, &sample, &SampleT::setFaceVaryingInterpolateBoundary); _SampleForAlembic alembicHoles = _Copy(schema, time, holeIndices, &mySample, &MySampleT::setHoles); _SampleForAlembic alembicCornerIndices = _Copy(schema, time, cornerIndices, &sample, &SampleT::setCornerIndices); _SampleForAlembic alembicCornerSharpnesses = _Copy(schema, time, cornerSharpnesses, &sample, &SampleT::setCornerSharpnesses); _SampleForAlembic alembicCreaseIndices = _Copy(schema, time, creaseIndices, &sample, &SampleT::setCreaseIndices); _SampleForAlembic alembicCreaseLengths = _Copy(schema, time, creaseLengths, &sample, &SampleT::setCreaseLengths); _SampleForAlembic alembicCreaseSharpnesses = _Copy(schema, time, creaseSharpnesses, &sample, &SampleT::setCreaseSharpnesses); _SampleForAlembic alembicUVs = _Copy(schema, time, uv, &sample, &SampleT::setUVs); // Write the sample. object->getSchema().set(sample); } // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static void _WriteNurbsCurves(_PrimWriterContext* context) { typedef OCurves Type; const _WriterSchema& schema = context->GetSchema(); // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples extent = context->ExtractSamples(UsdGeomTokens->extent, SdfValueTypeNames->Float3Array); UsdSamples points = context->ExtractSamples(UsdGeomTokens->points, SdfValueTypeNames->Point3fArray); UsdSamples velocities = context->ExtractSamples(UsdGeomTokens->velocities, SdfValueTypeNames->Vector3fArray); UsdSamples normals = context->ExtractSamples(UsdGeomTokens->normals, SdfValueTypeNames->Normal3fArray); UsdSamples curveVertexCounts = context->ExtractSamples(UsdGeomTokens->curveVertexCounts, SdfValueTypeNames->IntArray); UsdSamples widths = context->ExtractSamples(UsdGeomTokens->widths, SdfValueTypeNames->FloatArray); UsdSamples knots = context->ExtractSamples(UsdGeomTokens->knots, SdfValueTypeNames->DoubleArray); UsdSamples order = context->ExtractSamples(UsdGeomTokens->order, SdfValueTypeNames->IntArray); // Copy all the samples. typedef Type::schema_type::Sample SampleT; SampleT sample; for (double time : context->GetSampleTimesUnion()) { // Build the sample. sample.reset(); _CopySelfBounds(time, extent, &sample); _SampleForAlembic alembicPositions = _Copy(schema, time, points, &sample, &SampleT::setPositions); _SampleForAlembic alembicVelocities = _Copy(schema, time, velocities, &sample, &SampleT::setVelocities); _SampleForAlembic alembicNormals = _Copy(schema, time, normals, &sample, &SampleT::setNormals); _SampleForAlembic alembicCurveVertexCounts = _Copy(schema, time, curveVertexCounts, &sample, &SampleT::setCurvesNumVertices); _SampleForAlembic alembicWidths = _Copy(schema, time, widths, &sample, &SampleT::setWidths); _SampleForAlembic alembicKnots = _Copy(schema, _CopyKnots, time, knots, &sample, &SampleT::setKnots); _SampleForAlembic alembicOrders = _Copy(schema, _CopyOrder, time, order, &sample, &SampleT::setOrders); // This is how Alembic knows it's a NURBS curve. sample.setType(kVariableOrder); // Write the sample. object->getSchema().set(sample); } // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static void _WriteBasisCurves(_PrimWriterContext* context) { typedef OCurves Type; const _WriterSchema& schema = context->GetSchema(); // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples extent = context->ExtractSamples(UsdGeomTokens->extent, SdfValueTypeNames->Float3Array); UsdSamples points = context->ExtractSamples(UsdGeomTokens->points, SdfValueTypeNames->Point3fArray); UsdSamples velocities = context->ExtractSamples(UsdGeomTokens->velocities, SdfValueTypeNames->Vector3fArray); UsdSamples normals = context->ExtractSamples(UsdGeomTokens->normals, SdfValueTypeNames->Normal3fArray); UsdSamples curveVertexCounts = context->ExtractSamples(UsdGeomTokens->curveVertexCounts, SdfValueTypeNames->IntArray); UsdSamples widths = context->ExtractSamples(UsdGeomTokens->widths, SdfValueTypeNames->FloatArray); UsdSamples basis = context->ExtractSamples(UsdGeomTokens->basis, SdfValueTypeNames->Token); UsdSamples type = context->ExtractSamples(UsdGeomTokens->type, SdfValueTypeNames->Token); UsdSamples wrap = context->ExtractSamples(UsdGeomTokens->wrap, SdfValueTypeNames->Token); // Copy all the samples. typedef Type::schema_type::Sample SampleT; SampleT sample; for (double time : context->GetSampleTimesUnion()) { // Build the sample. sample.reset(); _CopySelfBounds(time, extent, &sample); _SampleForAlembic alembicPositions = _Copy(schema, time, points, &sample, &SampleT::setPositions); _SampleForAlembic alembicVelocities = _Copy(schema, time, velocities, &sample, &SampleT::setVelocities); _SampleForAlembic alembicNormals = _Copy(schema, time, normals, &sample, &SampleT::setNormals); _SampleForAlembic alembicCurveVertexCounts = _Copy(schema, time, curveVertexCounts, &sample, &SampleT::setCurvesNumVertices); _SampleForAlembic alembicWidths = _Copy(schema, time, widths, &sample, &SampleT::setWidths); _Copy(schema, _CopyCurveBasis, time, basis, &sample, &SampleT::setBasis); _Copy(schema, _CopyCurveType, time, type, &sample, &SampleT::setType); _Copy(schema, _CopyCurveWrap, time, wrap, &sample, &SampleT::setWrap); // Write the sample. object->getSchema().set(sample); } // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static void _WriteHermiteCurves(_PrimWriterContext* context) { typedef OCurves Type; const _WriterSchema& schema = context->GetSchema(); // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples extent = context->ExtractSamples(UsdGeomTokens->extent, SdfValueTypeNames->Float3Array); UsdSamples points = context->ExtractSamples(UsdGeomTokens->points, SdfValueTypeNames->Point3fArray); UsdSamples tangents = context->ExtractSamples(UsdGeomTokens->tangents, SdfValueTypeNames->Vector3fArray); UsdSamples velocities = context->ExtractSamples(UsdGeomTokens->velocities, SdfValueTypeNames->Vector3fArray); UsdSamples normals = context->ExtractSamples(UsdGeomTokens->normals, SdfValueTypeNames->Normal3fArray); UsdSamples curveVertexCounts = context->ExtractSamples(UsdGeomTokens->curveVertexCounts, SdfValueTypeNames->IntArray); UsdSamples widths = context->ExtractSamples(UsdGeomTokens->widths, SdfValueTypeNames->FloatArray); if (!velocities.IsEmpty()) { // Velocities has the same shape as points / positions. // In Abc positions encodes both points and tangents but in Usd, they // are separate arrays. To address, we would either need to add // tangentVelocities to the USD schema or provide alembic with // 0 valued velocities for the tangent elements. Let's identify // some use cases before figuring out how to handle this. TF_WARN( "Writing '%s' from HermiteCurves to AbcGeom::OCurvesSchema is " "undefined.", velocities.GetPath().GetText()); } // Copy all the samples. typedef Type::schema_type::Sample SampleT; SampleT sample; for (double time : context->GetSampleTimesUnion()) { // Build the sample. sample.reset(); _CopySelfBounds(time, extent, &sample); // We need to interleave points and tangents to // output to alembic's curve type for hermite curves. VtValue pointsValue = points.Get(time); VtValue tangentsValue = tangents.Get(time); _SampleForAlembic alembicPoints; if (auto pointsAndTangents = UsdGeomHermiteCurves::PointAndTangentArrays( pointsValue.Get(), tangentsValue.Get())) { // This is a copy of _Copy static const int extent = P3fArraySample::traits_type::extent; typedef PODTraitsFromEnum< P3fArraySample::traits_type::pod_enum>::value_type P3fPodType; typedef TypedArraySample P3fAlembicSample; typedef P3fArraySample::traits_type::value_type P3fValueType; VtVec3fArray interleaved = pointsAndTangents.Interleave(); const SdfValueTypeName& usdType = SdfValueTypeNames->Point3fArray; const _WriterSchema::Converter& converter = schema.GetConverter(usdType); alembicPoints = _MakeSample( schema, converter, usdType, VtValue(interleaved), false); if (_CheckSample(alembicPoints, points, usdType)) { sample.setPositions( P3fAlembicSample(alembicPoints.GetDataAs(), alembicPoints.GetCount() / extent)); } } _SampleForAlembic alembicNormals = _Copy(schema, time, normals, &sample, &SampleT::setNormals); _SampleForAlembic alembicCurveVertexCounts = _Copy(schema, time, curveVertexCounts, &sample, &SampleT::setCurvesNumVertices); _SampleForAlembic alembicWidths = _Copy(schema, time, widths, &sample, &SampleT::setWidths); sample.setBasis(kHermiteBasis); sample.setType(kCubic); sample.setWrap(kNonPeriodic); // Write the sample. object->getSchema().set(sample); } // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static void _WritePoints(_PrimWriterContext* context) { typedef OPoints Type; const _WriterSchema& schema = context->GetSchema(); // Create the object and make it the parent. shared_ptr object(new Type(context->GetParent(), context->GetAlembicPrimName(), _GetPrimMetadata(*context))); context->SetParent(object); // Collect the properties we need. context->SetSampleTimesUnion(UsdAbc_TimeSamples()); UsdSamples extent = context->ExtractSamples(UsdGeomTokens->extent, SdfValueTypeNames->Float3Array); UsdSamples points = context->ExtractSamples(UsdGeomTokens->points, SdfValueTypeNames->Point3fArray); UsdSamples velocities = context->ExtractSamples(UsdGeomTokens->velocities, SdfValueTypeNames->Vector3fArray); UsdSamples widths = context->ExtractSamples(UsdGeomTokens->widths, SdfValueTypeNames->FloatArray); UsdSamples ids = context->ExtractSamples(UsdGeomTokens->ids, SdfValueTypeNames->Int64Array); // Copy all the samples. typedef Type::schema_type::Sample SampleT; SampleT sample; bool first = true; for (double time : context->GetSampleTimesUnion()) { // Build the sample. sample.reset(); _CopySelfBounds(time, extent, &sample); _SampleForAlembic alembicPoints = _Copy(schema, time, points, &sample, &SampleT::setPositions); _SampleForAlembic alembicVelocities = _Copy(schema, time, velocities, &sample, &SampleT::setVelocities); _SampleForAlembic alembicWidths = _Copy(schema, time, widths, &sample, &SampleT::setWidths); _SampleForAlembic alembicIds = _Copy(schema, _CopyPointIds, time, ids, &sample, &SampleT::setIds); // Alembic requires ids. We only need to write one and it'll // be reused for all the other samples. We also use a valid // but empty array because we don't actually have any data. if (first && !sample.getIds()) { static const uint64_t data = 0; first = false; sample.setIds(UInt64ArraySample(&data, 0)); } // Write the sample. object->getSchema().set(sample); } // Set the time sampling. object->getSchema().setTimeSampling( context->AddTimeSampling(context->GetSampleTimesUnion())); } static TfToken _ComputeTypeName( const _WriterContext& context, const SdfPath& path) { // Special case. if (path == SdfPath::AbsoluteRootPath()) { return UsdAbcPrimTypeNames->PseudoRoot; } // General case. VtValue value = context.GetData().Get(path, SdfFieldKeys->TypeName); if (! value.IsHolding()) { return TfToken(); } TfToken typeName = value.UncheckedGet(); // Special cases. if (typeName == UsdAbcPrimTypeNames->Mesh) { SdfPath propPath(path.GetPrimPath().AppendProperty( UsdGeomTokens->subdivisionScheme)); value = context.GetData().Get(propPath, SdfFieldKeys->Default); if (value.IsHolding() && value.UncheckedGet() == "none") { typeName = UsdAbcPrimTypeNames->PolyMesh; } } return typeName; } static void _WritePrim( _WriterContext& context, const _Parent& parent, const SdfPath& path) { _Parent prim; { // Compute the type name. const TfToken typeName = _ComputeTypeName(context, path); // Write the properties. _PrimWriterContext primContext(context, parent, path); for (const auto& writer : context.GetSchema().GetPrimWriters(typeName)) { TRACE_SCOPE("UsdAbc_AlembicDataWriter:_WritePrim"); writer(&primContext); } prim = primContext.GetParent(); } // Write the name children. const VtValue childrenNames = context.GetData().Get(path, SdfChildrenKeys->PrimChildren); if (childrenNames.IsHolding()) { for (const auto& childName : childrenNames.UncheckedGet()) { _WritePrim(context, prim, path.AppendChild(childName)); } } } // ---------------------------------------------------------------------------- // // Schema builder // struct _WriterSchemaBuilder { _WriterSchema schema; _WriterSchemaBuilder(); }; _WriterSchemaBuilder::_WriterSchemaBuilder() { schema.AddType(UsdAbcPrimTypeNames->Scope) .AppendWriter(_WriteUnknown) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->Xform) .AppendWriter(_WriteXform) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->Mesh) .AppendWriter(_WriteXformParent) .AppendWriter(_WriteSubD) .AppendWriter(_WriteMayaColor) .AppendWriter(_WriteGprim) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->PolyMesh) .AppendWriter(_WriteXformParent) .AppendWriter(_WritePolyMesh) .AppendWriter(_WriteMayaColor) .AppendWriter(_WriteGprim) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->NurbsCurves) .AppendWriter(_WriteXformParent) .AppendWriter(_WriteNurbsCurves) .AppendWriter(_WriteMayaColor) .AppendWriter(_WriteGprim) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->BasisCurves) .AppendWriter(_WriteXformParent) .AppendWriter(_WriteBasisCurves) .AppendWriter(_WriteMayaColor) .AppendWriter(_WriteGprim) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->HermiteCurves) .AppendWriter(_WriteXformParent) .AppendWriter(_WriteHermiteCurves) .AppendWriter(_WriteMayaColor) .AppendWriter(_WriteGprim) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->Points) .AppendWriter(_WriteXformParent) .AppendWriter(_WritePoints) .AppendWriter(_WriteMayaColor) .AppendWriter(_WriteGprim) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->Camera) .AppendWriter(_WriteXformParent) .AppendWriter(_WriteCameraParameters) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; schema.AddType(UsdAbcPrimTypeNames->GeomSubset) .AppendWriter(_WriteFaceSet) ; // This handles the root. schema.AddType(UsdAbcPrimTypeNames->PseudoRoot) .AppendWriter(_WriteRoot) ; // This handles overs with no type and any unknown prim type. schema.AddFallbackType() .AppendWriter(_WriteUnknown) .AppendWriter(_WriteUnknownMayaColor) .AppendWriter(_WriteGprim) .AppendWriter(_WriteImageable) .AppendWriter(_WriteArbGeomParams) .AppendWriter(_WriteUserProperties) .AppendWriter(_WriteOther) ; } } // anonymous namespace static const _WriterSchema& _GetSchema() { static _WriterSchemaBuilder builder; return builder.schema; } // // UsdAbc_AlembicDataWriter // class UsdAbc_AlembicDataWriterImpl : public _WriterContext { }; UsdAbc_AlembicDataWriter::UsdAbc_AlembicDataWriter() : _impl(new UsdAbc_AlembicDataWriterImpl) { // Do nothing } UsdAbc_AlembicDataWriter::~UsdAbc_AlembicDataWriter() { Close(); } bool UsdAbc_AlembicDataWriter::Open( const std::string& filePath, const std::string& comment) { TRACE_FUNCTION(); _errorLog.clear(); try { _impl->SetArchive( CreateArchiveWithInfo(Alembic::AbcCoreOgawa::WriteArchive(), filePath, writerName, comment)); return true; } catch (std::exception& e) { _errorLog.append(e.what()); _errorLog.append("\n"); return false; } } bool UsdAbc_AlembicDataWriter::Write(const SdfAbstractDataConstPtr& data) { TRACE_FUNCTION(); try { if (_impl->GetArchive().valid() && data) { const _WriterSchema& schema = _GetSchema(); _impl->SetSchema(&schema); _impl->SetData(data); _WritePrim(*_impl, _Parent(), SdfPath::AbsoluteRootPath()); } return true; } catch (std::exception& e) { _errorLog.append(e.what()); _errorLog.append("\n"); return false; } } bool UsdAbc_AlembicDataWriter::Close() { TRACE_FUNCTION(); // Alembic does not appear to be robust when closing an archive. // ~AwImpl does real writes and the held std::ostream is configured // to throw exceptions, so any exceptions when writing should leak // from the d'tor. This is a fatal error. // // For now we just destroy the archive and don't bother looking for // errors. _impl->SetArchive(OArchive()); return true; } std::string UsdAbc_AlembicDataWriter::GetErrors() const { return _errorLog; } void UsdAbc_AlembicDataWriter::SetFlag(const TfToken& flagName, bool set) { _impl->SetFlag(flagName, set); } PXR_NAMESPACE_CLOSE_SCOPE