Skip to content

Alembic

Export

The exporter follows these broad steps:

  1. Create Time Samples: when exporting more than one frame, Blender constructs two lists of frame numbers to export (one for transforms, and one for shapes), converts from frame numbers to samples, and passes them to the Alembic library. There can be more than one "time sampling", for example when transforms are subsampled 10x per frame, but mesh shapes only 1x per frame.
  2. Construct Writer Objects: the objects in the scene are inspected and writer objects are created in the correct hierarchy.
  3. Write Data: for every sample in the list of time stamples, the correct frame is set and the writer objects are called upon to write their data.

Minimal Exporter Code Example

The Alembic library is using C++ object references to manage the writing of data. This means that when a variable in C++ goes out of scope, the object is finalized and can no longer be written to or even obtained from the Alembic library. This is different from the USD library, where USD objects can always be obtained by their path in the USD Stage.

Writing data consists of a few steps:

  1. Add the object by creating the correct C++ variable. This can be OXForm transform or OPolyMesh mesh. This is what defines the type of object in Alembic. The constructor also receives the name in Alembic, the parent object, and the index of the time sampling to be used for this object.
  2. Get the Schema by creating another C++ variable. The schema is what defines the avaiable properties in the Alembic object, and how to convert them to C++ values. Examples are OXformSchema and OPolyMeshSchema. In the above examples you create those by simpling calling transform.getSchema() or mesh.getSchema(). The schema holds a reference to its owning object, so to prevent the Alembic library from finalizing the object you only have to keep a reference to either the schema or the object itself.
  3. Write the Sample by creating another C++ variable of the right type, setting its properties, and calling the_schema.set(the_sample). This sample variable can be recreated for every frame or reused, here it's not important to keep a reference around. Calling the schema's set() function magically forwards the time sample index for the object. This is why in Blender's exporter you won't find a call to tell Alembic the current frame/time. This time sample index is maintained per object.

Here is an example file that writes an animated transform:

#include <fstream>
#include <iostream>

#include <Alembic/AbcCoreOgawa/All.h>
#include <Alembic/AbcGeom/All.h>

using Alembic::Abc::ErrorHandler;
using Alembic::Abc::kWrapExisting;
using Alembic::Abc::OArchive;
using Alembic::Abc::OObject;
using Alembic::Abc::TimeSampling;
using Alembic::Abc::V3d;
using Alembic::AbcGeom::OXform;
using Alembic::AbcGeom::OXformSchema;
using Alembic::AbcGeom::XformSample;

OArchive create_archive(std::ostream *ostream)
{
  Alembic::Abc::MetaData abc_metadata;
  abc_metadata.set(Alembic::Abc::kUserDescriptionKey, "Example");

  Alembic::AbcCoreOgawa::WriteArchive archive_writer;
  Alembic::AbcCoreAbstract::ArchiveWriterPtr writer_ptr = archive_writer(ostream, abc_metadata);
  return OArchive(writer_ptr, kWrapExisting, ErrorHandler::kThrowPolicy);
}

int main(void)
{
  std::ofstream outfile;
  outfile.open("minimal_example.abc", std::ios::out | std::ios::binary);

  OArchive archive = create_archive(&outfile);
  OObject abc_top = archive.getTop();

  TimeSampling time_sampling(1.0 / 24.0, 0.0); /* Time per sample, start time. */
  archive.addTimeSampling(time_sampling);
  OXform transform(abc_top, "transform", 1);

  for (int sample = 0; sample < 10; sample++) {
    V3d translation(sample * 2, 0, 0);
    XformSample xform_sample;
    xform_sample.setTranslation(translation);
    transform.getSchema().set(xform_sample);
  }

  return 0;
}