bool getBasicSchemaDataFromObject(Alembic::Abc::IObject &object, BasicSchemaData &bsd)
{
	ESS_PROFILE_SCOPE("getBasicSchemaDataFromObject"); 
	const Alembic::Abc::MetaData &md = object.getMetaData();
	if(Alembic::AbcGeom::IXform::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__XFORM, Alembic::AbcGeom::IXform(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	else if(Alembic::AbcGeom::IPolyMesh::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__POLYMESH, Alembic::AbcGeom::IPolyMesh(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	else if(Alembic::AbcGeom::ICurves::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__CURVES, Alembic::AbcGeom::ICurves(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	else if(Alembic::AbcGeom::INuPatch::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__NUPATCH, Alembic::AbcGeom::INuPatch(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	else if(Alembic::AbcGeom::IPoints::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__POINTS, Alembic::AbcGeom::IPoints(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	else if(Alembic::AbcGeom::ISubD::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__SUBDIV, Alembic::AbcGeom::ISubD(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	else if(Alembic::AbcGeom::ICamera::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__CAMERA, Alembic::AbcGeom::ICamera(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	else if(Alembic::AbcGeom::IFaceSet::matches(md))
		return __getBasicSchemaDataFromObject(BasicSchemaData::__FACESET, Alembic::AbcGeom::IFaceSet(object,Alembic::Abc::kWrapExisting).getSchema(), bsd);

	return false;
};
bool AlembicWriteJob::ObjectExists(const MObject &in_Ref)
{
  ESS_PROFILE_SCOPE("AlembicWriteJob::ObjectExists");

  std::string fullName(getFullNameFromRef(in_Ref).asChar());
  return mapObjects.find(fullName) != mapObjects.end();
}
Exemple #3
0
MStatus AlembicResolvePathCommand::doIt(const MArgList & args)
{
   ESS_PROFILE_SCOPE("AlembicResolvePathCommand::doIt");
   MStatus status = MS::kSuccess;
   MArgParser argData(syntax(), args, &status);

   if (argData.isFlagSet("help"))
   {
      MGlobal::displayInfo("[ExocortexAlembic]: ExocortexAlembic_resolvePath command:");
      MGlobal::displayInfo("                    -f : provide an unresolved fileName (string)");
      return MS::kSuccess;
   }

   if(!argData.isFlagSet("fileNameArg"))
   {
      // TODO: display dialog
      MGlobal::displayError("[ExocortexAlembic] No fileName specified.");
      return status;
   }

   // get the filename arg
   MString fileName = argData.flagArgumentString("fileNameArg",0);

   setResult(resolvePath(fileName));

   return status;
}
bool archiveExists(std::string const& path)
{
  ESS_PROFILE_SCOPE("archiveExists");
  std::string resolvedPath = resolvePath(path);
  std::map<std::string, AlembicArchiveInfo>::iterator it;
  it = gArchives.find(resolvedPath);
  return it != gArchives.end();
}
std::string addArchive(Alembic::Abc::IArchive * archive)
{
    ESS_PROFILE_SCOPE("addArchive");
   AlembicArchiveInfo info;
   info.archive = archive;
   gArchives.insert(std::pair<std::string,AlembicArchiveInfo>(archive->getName(),info));
   return archive->getName().c_str();
}
bool createAbcArchiveCache( Abc::IArchive *pArchive, AbcArchiveCache* fullNameToObjectCache, CommonProgressBar *pBar ) {
	ESS_PROFILE_SCOPE("createAbcArchiveCache");
	EC_LOG_INFO( "Creating AbcArchiveCache for archive: " << pArchive->getName() );

	runonce();

	Abc::IObject top = pArchive->getTop();
	return addObjectToCache( fullNameToObjectCache, top, "", pBar ) != 0;
}
int getRefArchive(std::string const& path)
{
  ESS_PROFILE_SCOPE("getRefArchive");
  std::string resolvedPath = resolvePath(path);
  std::map<std::string, AlembicArchiveInfo>::iterator it;
  it = gArchives.find(resolvedPath);
  if (it == gArchives.end()) return -1;
  return it->second.refCount;
}
Abc::IObject getObjectFromArchive(std::string const& path, std::string const& identifier)
{
   ESS_PROFILE_SCOPE("getObjectFromArchive");
	AbcObjectCache* objectCache = getObjectCacheFromArchive(path,identifier );
	if( objectCache == NULL ) {
		return Alembic::Abc::IObject();
	}
	return objectCache->obj;
}
void deleteAllArchives()
{
   ESS_PROFILE_SCOPE("deleteAllArchives");
   for(std::map<std::string,AlembicArchiveInfo>::iterator it = gArchives.begin(); it != gArchives.end(); ++it)
   {
      it->second.archive->reset();
      delete(it->second.archive);
   }
   gArchives.clear();
}
void AlembicWriteJob::SetOption(const MString &in_Name, const MString &in_Value)
{
  ESS_PROFILE_SCOPE("AlembicWriteJob::SetOption");
  std::map<std::string, std::string>::iterator it =
      mOptions.find(in_Name.asChar());
  if (it == mOptions.end())
    mOptions.insert(std::pair<std::string, std::string>(in_Name.asChar(),
                                                        in_Value.asChar()));
  else {
    it->second = in_Value.asChar();
  }
}
void deleteArchive(std::string const& path)
{
  ESS_PROFILE_SCOPE("deleteArchive");
  std::string resolvedPath = resolvePath(path);
  std::map<std::string, AlembicArchiveInfo>::iterator it;
  it = gArchives.find(resolvedPath);
  if (it == gArchives.end()) return;

  EC_LOG_INFO("Closing Abc Archive: " << it->second.archive->getName());
  it->second.archive->reset();
  delete (it->second.archive);
  gArchives.erase(it);
}
int decRefArchive(std::string const& path)
{
  ESS_PROFILE_SCOPE("decRefArchive");
  std::string resolvedPath = resolvePath(path);
  std::map<std::string, AlembicArchiveInfo>::iterator it;
  it = gArchives.find(resolvedPath);
  if (it == gArchives.end()) return -1;
  it->second.refCount--;
#ifdef _DEBUG
  EC_LOG_INFO("ref count (d): " << it->second.refCount);
#endif
  return it->second.refCount;
}
std::string resolvePath( std::string const& originalPath ) {
   ESS_PROFILE_SCOPE("resolvePath");
/*   static std::map<std::string,std::string> s_originalToResolvedPath;

   if( s_originalToResolvedPath.find( originalPath ) != s_originalToResolvedPath.end() ) {
	   return s_originalToResolvedPath[ originalPath ];
   }*/

   std::string resolvedPath = resolvePath_Internal( EnvVariables::replace( originalPath ) );

   //s_originalToResolvedPath.insert( std::pair<std::string,std::string>( originalPath, resolvedPath ) );

   return resolvedPath;
}
bool AlembicWriteJob::AddObject(AlembicObjectPtr in_Obj)
{
  ESS_PROFILE_SCOPE("AlembicWriteJob::AddObject");
  if (!in_Obj) {
    return false;
  }
  const MObject &in_Ref = in_Obj->GetRef();
  if (in_Ref.isNull()) {
    return false;
  }

  const std::string fullName(getFullNameFromRef(in_Ref).asChar());
  mapObjects.insert(
      pairStrAbcObj(fullName, in_Obj));  // add it in the multi map!
  return true;
}
Alembic::Abc::TimeSamplingPtr getTimeSamplingFromObject(
    Alembic::Abc::IObject& object)
{
  ESS_PROFILE_SCOPE("getTimeSamplingFromObject");
  const Alembic::Abc::MetaData& md = object.getMetaData();
  if (Alembic::AbcGeom::IXform::matches(md)) {
    return Alembic::AbcGeom::IXform(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::IPolyMesh::matches(md)) {
    return Alembic::AbcGeom::IPolyMesh(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::ICurves::matches(md)) {
    return Alembic::AbcGeom::ICurves(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::INuPatch::matches(md)) {
    return Alembic::AbcGeom::INuPatch(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::IPoints::matches(md)) {
    return Alembic::AbcGeom::IPoints(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::ISubD::matches(md)) {
    return Alembic::AbcGeom::ISubD(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::ICamera::matches(md)) {
    return Alembic::AbcGeom::ICamera(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::IFaceSet::matches(md)) {
    return Alembic::AbcGeom::IFaceSet(object, Alembic::Abc::kWrapExisting)
        .getSchema()
        .getTimeSampling();
  }
  return Alembic::Abc::TimeSamplingPtr();
}
bool isObjectConstant(Alembic::Abc::IObject& object)
{
  ESS_PROFILE_SCOPE("isObjectConstant");
  const Alembic::Abc::MetaData& md = object.getMetaData();
  if (Alembic::AbcGeom::IXform::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::IXform(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  else if (Alembic::AbcGeom::IPolyMesh::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::IPolyMesh(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  else if (Alembic::AbcGeom::ICurves::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::ICurves(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  else if (Alembic::AbcGeom::INuPatch::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::INuPatch(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  else if (Alembic::AbcGeom::IPoints::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::IPoints(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  else if (Alembic::AbcGeom::ISubD::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::ISubD(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  else if (Alembic::AbcGeom::ICamera::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::ICamera(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  else if (Alembic::AbcGeom::IFaceSet::matches(md)) {
    return isObjectSchemaConstant(
        Alembic::AbcGeom::IFaceSet(object, Alembic::Abc::kWrapExisting)
            .getSchema());
  }
  return true;
}
MStatus AlembicWriteJob::Process(double frame)
{
  ESS_PROFILE_SCOPE("AlembicWriteJob::Process");
  // find the right time frame!
  int i = -1;
  for (size_t j = 0; j < mFrames.size(); ++j) {
    // compare the frames
    if (fabs(mFrames[j] - frame) <= 0.001) {
      i = (int)j;
      break;
    }
  }

  if (i < 0) {
    return MS::kSuccess;
  }

  // run the export for all objects
  MayaProgressBar pBar;
  pBar.init(0, (int)mapObjects.size(), 1);
  pBar.start();

  int interrupt = 20;
  MStatus status;
  const double currentFrame = mFrames[i];
  const bool isFirstFrame = (i == 0);

  for (multiMapStrAbcObj::iterator it = mapObjects.begin();
       it != mapObjects.end(); ++it, --interrupt) {
    if (interrupt == 0) {
      interrupt = 20;
      if (pBar.isCancelled()) {
        break;
      }
      pBar.incr(20);
    }
    status = it->second->Save(currentFrame, mTs, isFirstFrame);
    if (status != MStatus::kSuccess) {
      MPxCommand::setResult("Error caught in AlembicWriteJob::Process: " +
                            status.errorString());
      break;
    }
  }
  pBar.stop();
  return status;
}
int addRefArchive(std::string const& path)
{
  ESS_PROFILE_SCOPE("addRefArchive");
  if (path.empty()) return -1;
  std::string resolvedPath = resolvePath(path);

  // call get archive to ensure to create it!
  getArchiveFromID(path);
  std::map<std::string, AlembicArchiveInfo>::iterator it;
  it = gArchives.find(resolvedPath);
  if (it == gArchives.end()) return -1;
  it->second.refCount++;
#ifdef _DEBUG
  EC_LOG_INFO("ref count (a): " << it->second.refCount);
#endif
  return it->second.refCount;
}
Alembic::Abc::TimeSamplingPtr getTimeSamplingFromObject(
    Alembic::Abc::OObject* object)
{
  ESS_PROFILE_SCOPE("getTimeSamplingFromObject");
  const Alembic::Abc::MetaData& md = object->getMetaData();
  if (Alembic::AbcGeom::OXform::matches(md)) {
    return ((Alembic::AbcGeom::OXform*)(&object))
        ->getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::OPolyMesh::matches(md)) {
    return ((Alembic::AbcGeom::OPolyMesh*)(&object))
        ->getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::OCurves::matches(md)) {
    return ((Alembic::AbcGeom::OCurves*)(&object))
        ->getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::ONuPatch::matches(md)) {
    return ((Alembic::AbcGeom::ONuPatch*)(&object))
        ->getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::OPoints::matches(md)) {
    return ((Alembic::AbcGeom::OPoints*)(&object))
        ->getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::OSubD::matches(md)) {
    return ((Alembic::AbcGeom::OSubD*)(&object))->getSchema().getTimeSampling();
  }
  else if (Alembic::AbcGeom::OCamera::matches(md)) {
    return ((Alembic::AbcGeom::OCamera*)(&object))
        ->getSchema()
        .getTimeSampling();
  }
  else if (Alembic::AbcGeom::OFaceSet::matches(md)) {
    return ((Alembic::AbcGeom::OFaceSet*)(&object))
        ->getSchema()
        .getTimeSampling();
  }
  return Alembic::Abc::TimeSamplingPtr();
}
AbcObjectCache::AbcObjectCache( Alembic::Abc::IObject & objToCache )
		:	obj( objToCache ), isMeshTopoDynamic(false), isMeshPointCache(false), fullName(objToCache.getFullName())
{
	ESS_PROFILE_SCOPE("AbcObjectCache::AbcObjectCache");

	BasicSchemaData bsd;
	getBasicSchemaDataFromObject(objToCache, bsd);
	isConstant = bsd.isConstant;
	numSamples = bsd.nbSamples;
	bool isMesh = true;
	if ( bsd.type == bsd.__POLYMESH || !( isMesh = (bsd.type != bsd.__SUBDIV) ) )
	{
		bool isTopoDyn = false;
		extractMeshInfo(&objToCache, isMesh, isMeshPointCache, isTopoDyn);
		if (!isConstant)
			isMeshTopoDynamic = isTopoDyn;
	}
}
SampleInfo getSampleInfo
(
   double iFrame,
   Alembic::AbcCoreAbstract::TimeSamplingPtr iTime,
   size_t numSamps
)
{
   ESS_PROFILE_SCOPE("getSampleInfo");
   SampleInfo result;
   if (numSamps == 0)
      numSamps = 1;

   std::pair<Alembic::AbcCoreAbstract::index_t, double> floorIndex = iTime->getFloorIndex(iFrame, numSamps);

   result.floorIndex = floorIndex.first;
   result.ceilIndex = result.floorIndex;

   if (fabs(iFrame - floorIndex.second) < 0.0001) {
      result.alpha = 0.0f;
      return result;
   }

   std::pair<Alembic::AbcCoreAbstract::index_t, double> ceilIndex =
   iTime->getCeilIndex(iFrame, numSamps);

   if (fabs(iFrame - ceilIndex.second) < 0.0001) {
      result.floorIndex = ceilIndex.first;
      result.ceilIndex = result.floorIndex;
      result.alpha = 0.0f;
      return result;
   }

   if (result.floorIndex == ceilIndex.first) {
      result.alpha = 0.0f;
      return result;
   }

   result.ceilIndex = ceilIndex.first;

   result.alpha = (iFrame - floorIndex.second) / (ceilIndex.second - floorIndex.second);
   return result;
}
AbcArchiveCache* getArchiveCache(std::string const& path,
                                 CommonProgressBar* pBar)
{
  ESS_PROFILE_SCOPE("getArchiveCache");
  getArchiveFromID(path);
  std::string resolvedPath = resolvePath(path);
  std::map<std::string, AlembicArchiveInfo>::iterator it;
  it = gArchives.find(resolvedPath);
  if (it == gArchives.end()) return NULL;

  // compute cache if required.
  if (it->second.archiveCache.size() == 0) {
    if (!createAbcArchiveCache(it->second.archive, &(it->second.archiveCache),
                               pBar)) {
      it->second.archiveCache.clear();
      return 0;
    }
  }
  return &(it->second.archiveCache);
}
Exemple #23
0
ALEMBIC_TYPE getAlembicTypeFromObject(Abc::IObject object)
{
  ESS_PROFILE_SCOPE("getAlembicTypeFromObject"); 
  const Abc::MetaData &md = object.getMetaData();
  if(AbcG::IXform::matches(md))
    return AT_Xform;
  else if(AbcG::IPolyMesh::matches(md))
    return AT_PolyMesh;
  else if(AbcG::ICurves::matches(md))
    return AT_Curves;
  else if(AbcG::INuPatch::matches(md))
    return AT_NuPatch;
  else if(AbcG::IPoints::matches(md))
    return AT_Points;
  else if(AbcG::ISubD::matches(md))
    return AT_SubD;
  else if(AbcG::ICamera::matches(md))
    return AT_Camera;
  return AT_UNKNOWN;
}
AbcObjectCache* addObjectToCache( AbcArchiveCache* fullNameToObjectCache, Abc::IObject &obj, std::string parentIdentifier, CommonProgressBar *pBar ) {
	ESS_PROFILE_SCOPE("addObjectToCache");
	AbcObjectCache objectCache( obj );
	objectCache.parentIdentifier = parentIdentifier;
	for( int i = 0; i < obj.getNumChildren(); i ++ ) {
		if (pBar)
		{
			pBar->incr(1);
			if (!(i % 20) && pBar->isCancelled())	
				return 0;
		}

		Abc::IObject child = obj.getChild( i );
		AbcObjectCache* childObjectCache = addObjectToCache( fullNameToObjectCache, child, objectCache.fullName, pBar );
		if (childObjectCache == 0)
			return 0;

		objectCache.childIdentifiers.push_back( childObjectCache->fullName );
	}
	fullNameToObjectCache->insert( AbcArchiveCache::value_type( objectCache.fullName, objectCache ) );
	return &( fullNameToObjectCache->find( objectCache.fullName )->second );
}
MStatus AlembicAssignInitialSGCommand::doIt(const MArgList& args)
{
  MStatus status;
  MArgParser argData(syntax(), args, &status);

  if (argData.isFlagSet("help"))
  {
    MGlobal::displayInfo("[ExocortexAlembic]: ExocortexAlembic_assignFaceset command:");
    MGlobal::displayInfo("                    -m : mesh to assign the initialShadingGroup on");
    return MS::kSuccess;
  }

  if (!argData.isFlagSet("mesh"))
  {
    MGlobal::displayError("No mesh/subdiv specified!");
    return MS::kFailure;
  }

  MObject initShader;
  MDagPath dagPath;

  if (getObjectByName("initialShadingGroup", initShader) == MS::kSuccess && getDagPathByName(argData.flagArgumentString("mesh", 0), dagPath) == MS::kSuccess)
  {
    ESS_PROFILE_SCOPE("AlembicAssignInitialSGCommand::doIt::MFnSet");
    MFnSet set(initShader);
    set.addMember(dagPath);
  }
  else
  {
    MString theError("Error getting adding ");
    theError += argData.flagArgumentString("mesh", 0);
    theError += MString(" to initalShadingGroup.");
    MGlobal::displayError(theError);
    return MS::kFailure;
  }
  return MS::kSuccess;
}
Alembic::Abc::IArchive* getArchiveFromID(std::string const& path)
{
  ESS_PROFILE_SCOPE("getArchiveFromID-1");
  std::map<std::string, AlembicArchiveInfo>::iterator it;
  std::string resolvedPath = resolvePath(path);
  it = gArchives.find(resolvedPath);
  if (it == gArchives.end()) {
    // check if the file exists

    if (!boost::filesystem::exists(resolvedPath.c_str())) {
      ESS_LOG_ERROR("Can't find Alembic file.  Path: "
                    << path << "  Resolved path: " << resolvedPath);
      return NULL;
    }

    FILE* file = fopen(resolvedPath.c_str(), "rb");
    if (file == NULL) {
      return NULL;
    }
    else {
      fclose(file);

      AbcF::IFactory iFactory;
      AbcF::IFactory::CoreType oType;
      addArchive(new Abc::IArchive(iFactory.getArchive(resolvedPath, oType)));

      // addArchive(new Abc::IArchive( Alembic::AbcCoreHDF5::ReadArchive(),
      // resolvedPath));

      Abc::IArchive* pArchive = gArchives.find(resolvedPath)->second.archive;
      EC_LOG_INFO("Opening Abc Archive: " << pArchive->getName());
      return pArchive;
    }
  }
  return it->second.archive;
}
AlembicPoints::meshInfo AlembicPoints::CacheShapeMesh(Mesh* pShapeMesh, BOOL bNeedDelete, Matrix3 meshTM, int nMatId, int particleId, TimeValue ticks, ShapeType &type, Abc::uint16_t &instanceId, float &animationTime)
{
    ESS_PROFILE_FUNC();

	type = ShapeType_Instance;
	//animationTime = 0;
	
	//Mesh* pShapeMesh = pExt->GetParticleShapeByIndex(particleId);

	if(pShapeMesh->getNumFaces() == 0 || pShapeMesh->getNumVerts() == 0){
		meshInfo mi;
      return mi;
	}

    meshDigests digests;
    {
    ESS_PROFILE_SCOPE("AlembicPoints::CacheShapeMesh - compute hash");
	AbcU::MurmurHash3_x64_128( pShapeMesh->verts, pShapeMesh->numVerts * sizeof(Point3), sizeof(Point3), digests.Vertices.words );
	AbcU::MurmurHash3_x64_128( pShapeMesh->faces, pShapeMesh->numFaces * sizeof(Face), sizeof(Face), digests.Faces.words );
	if(mJob->GetOption("exportMaterialIds")){

		std::vector<MtlID> matIds;

		if(nMatId < 0){
			matIds.reserve(pShapeMesh->getNumFaces());
			for(int i=0; i<pShapeMesh->getNumFaces(); i++){
				matIds.push_back(pShapeMesh->getFaceMtlIndex(i));
			}
		}
		else{
			matIds.push_back(nMatId);
		}

		AbcU::MurmurHash3_x64_128( &matIds[0], matIds.size() * sizeof(MtlID), sizeof(MtlID), digests.MatIds.words );
	}
	if(mJob->GetOption("exportUVs")){
		//TODO...
	}
    }
	
	mTotalShapeMeshes++;

	meshInfo currShapeInfo;
	faceVertexHashToShapeMap::iterator it = mShapeMeshCache.find(digests);
	if( it != mShapeMeshCache.end() ){
		currShapeInfo = it->second;
	}
	else{
		meshInfo& mi = mShapeMeshCache[digests];
		mi.pMesh = pShapeMesh;

      mi.bbox = computeBoundingBox(pShapeMesh);

		mi.nMatId = nMatId;
		
		std::stringstream nameStream;
		nameStream<< EC_MCHAR_to_UTF8( mMaxNode->GetName() ) <<"_";
		nameStream<<"InstanceMesh"<<mNumShapeMeshes;
		mi.name=nameStream.str();

		mi.bNeedDelete = bNeedDelete;
		mi.meshTM = meshTM;
        mi.nMeshInstanceId = mNumShapeMeshes;

		currShapeInfo = mi;
		mNumShapeMeshes++;

		mMeshesToSaveForCurrentFrame.push_back(&mi);

		//ESS_LOG_WARNING("ticks: "<<ticks<<" particleId: "<<particleId);
		//ESS_LOG_WARNING("Adding shape with hash("<<vertexDigest.str()<<", "<<faceDigest.str()<<") \n auto assigned name "<<mi.name <<" efficiency:"<<(double)mNumShapeMeshes/mTotalShapeMeshes);
	}

    instanceId = currShapeInfo.nMeshInstanceId;

 //   {
 //      ESS_PROFILE_SCOPE("CacheShapeMesh push_back instance name");

	//std::string pathName("/");
	//pathName += currShapeInfo.name;

	//instanceId = FindInstanceName(pathName);
	//if (instanceId == USHRT_MAX){
	//	mInstanceNames.push_back(pathName);
	//	instanceId = (Abc::uint16_t)mInstanceNames.size()-1;
	//}

 //   }

    return currShapeInfo;
}
MStatus AlembicHair::Save(double time, unsigned int timeIndex,
    bool isFirstFrame)
{
  ESS_PROFILE_SCOPE("AlembicHair::Save");
  MStatus status;

  // access the geometry
  MFnPfxGeometry node(GetRef());

  // save the metadata
  SaveMetaData(this);

  // save the attributes
  if (isFirstFrame) {
    Abc::OCompoundProperty cp;
    Abc::OCompoundProperty up;
    if (AttributesWriter::hasAnyAttr(node, *GetJob())) {
      cp = mSchema.getArbGeomParams();
      up = mSchema.getUserProperties();
    }

    mAttrs = AttributesWriterPtr(
        new AttributesWriter(cp, up, GetMyParent(), node, timeIndex, *GetJob())
        );
  }
  else {
    mAttrs->write();
  }

  // prepare the bounding box
  Abc::Box3d bbox;

  // check if we have the global cache option
  const bool globalCache =
      GetJob()->GetOption(L"exportInGlobalSpace").asInt() > 0;

  MRenderLineArray mainLines, leafLines, flowerLines;
  node.getLineData(mainLines, leafLines, flowerLines, true, false,
                   mNumSamples == 0, false, false, mNumSamples == 0, false,
                   true, globalCache);

  // first we need to count the number of points
  unsigned int vertexCount = 0;
  for (unsigned int i = 0; i < (unsigned int)mainLines.length(); i++) {
    vertexCount += mainLines.renderLine(i, &status).getLine().length();
  }

  mPosVec.resize(vertexCount);
  unsigned int offset = 0;
  if (mNumSamples == 0) {
    mNbVertices.resize((size_t)mainLines.length());
    mRadiusVec.resize(vertexCount);
    mColorVec.resize(vertexCount);
    for (unsigned int i = 0; i < (unsigned int)mainLines.length(); i++) {
      const MRenderLine &line = mainLines.renderLine(i, &status);
      const MVectorArray &positions = line.getLine();
      const MDoubleArray &radii = line.getWidth();
      const MVectorArray &colors = line.getColor();
      const MVectorArray &transparencies = line.getTransparency();
      mNbVertices[i] = positions.length();

      for (unsigned int j = 0; j < positions.length(); j++) {
        const MVector &opos = positions[j];
        Imath::V3f &ipos = mPosVec[offset];
        ipos.x = (float)opos.x;
        ipos.y = (float)opos.y;
        ipos.z = (float)opos.z;
        bbox.extendBy(Imath::V3d(ipos));

        mRadiusVec[offset] = (float)radii[j];

        const MVector &ocol = colors[j];
        Imath::C4f &icol = mColorVec[offset];
        icol.r = (float)ocol.x;
        icol.g = (float)ocol.y;
        icol.b = (float)ocol.z;
        icol.a = 1.0f - (float)transparencies[j].x;
        offset++;
      }
    }
  }
  else {
    for (unsigned int i = 0; i < (unsigned int)mainLines.length(); i++) {
      const MRenderLine &line = mainLines.renderLine(i, &status);
      const MVectorArray &positions = line.getLine();
      for (unsigned int j = 0; j < positions.length(); j++) {
        const MVector &opos = positions[j];
        Imath::V3f &ipos = mPosVec[offset];
        ipos.x = (float)opos.x;
        ipos.y = (float)opos.y;
        ipos.z = (float)opos.z;
        bbox.extendBy(Imath::V3d(ipos));
        offset++;
      }
    }
  }

  // store the positions to the samples
  mSample.setPositions(Abc::P3fArraySample(&mPosVec.front(), mPosVec.size()));
  mSample.setSelfBounds(bbox);

  if (mNumSamples == 0) {
    mSample.setCurvesNumVertices(Abc::Int32ArraySample(mNbVertices));
    mSample.setWrap(AbcG::kNonPeriodic);
    mSample.setType(AbcG::kLinear);

    mRadiusProperty.set(
        Abc::FloatArraySample(&mRadiusVec.front(), mRadiusVec.size()));
    mColorProperty.set(
        Abc::C4fArraySample(&mColorVec.front(), mColorVec.size()));
  }

  // save the sample
  mSchema.set(mSample);
  mNumSamples++;

  return MStatus::kSuccess;
}
MStatus AlembicWriteJob::PreProcess()
{
  ESS_PROFILE_SCOPE("AlembicWriteJob::PreProcess");
  // check filenames
  if (mFileName.length() == 0) {
    MGlobal::displayError("[ExocortexAlembic] No filename specified.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no filename specified");
    return MStatus::kInvalidParameter;
  }

  // check objects
  if (mSelection.length() == 0) {
    MGlobal::displayError("[ExocortexAlembic] No objects specified.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no objects specified");
    return MStatus::kInvalidParameter;
  }

  // check frames
  if (mFrames.size() == 0) {
    MGlobal::displayError("[ExocortexAlembic] No frames specified.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no frame specified");
    return MStatus::kInvalidParameter;
  }

  // check if the file is currently in use
  if (getRefArchive(mFileName) > 0) {
    MGlobal::displayError("[ExocortexAlembic] Error writing to file '" +
                          mFileName + "'. File currently in use.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no filename already in "
        "use");
    return MStatus::kInvalidParameter;
  }

  // init archive (use a locally scoped archive)
  // TODO: determine how to access the current maya scene path
  // MString sceneFileName = "Exported from:
  // "+Application().GetActiveProject().GetActiveScene().GetParameterValue("FileName").GetAsText();
  try {
    createArchive("Exported from Maya.");

    mTop = mArchive.getTop();

    // get the frame rate
    mFrameRate = MTime(1.0, MTime::kSeconds).as(MTime::uiUnit());
    const double timePerSample = 1.0 / mFrameRate;
    std::vector<AbcA::chrono_t> frames;
    for (LONG i = 0; i < mFrames.size(); i++) {
      frames.push_back(mFrames[i] * timePerSample);
    }

    // create the sampling
    if (frames.size() > 1) {
      const double timePerCycle = frames[frames.size() - 1] - frames[0];
      AbcA::TimeSamplingType samplingType((Abc::uint32_t)frames.size(),
                                          timePerCycle);
      AbcA::TimeSampling sampling(samplingType, frames);
      mTs = mArchive.addTimeSampling(sampling);
    }
    else {
      AbcA::TimeSampling sampling(1.0, frames[0]);
      mTs = mArchive.addTimeSampling(sampling);
    }
    Abc::OBox3dProperty boxProp = AbcG::CreateOArchiveBounds(mArchive, mTs);

    MDagPath dagPath;
    {
      MItDag().getPath(dagPath);
    }
    SceneNodePtr exoSceneRoot = buildMayaSceneGraph(dagPath, this->replacer);
    const bool bFlattenHierarchy = GetOption("flattenHierarchy") == "1";
    const bool bTransformCache = GetOption("transformCache") == "1";
    const bool bSelectChildren = false;
    {
      std::map<std::string, bool> selectionMap;
      for (int i = 0; i < (int)mSelection.length(); ++i) {
        MFnDagNode dagNode(mSelection[i]);
        selectionMap[dagNode.fullPathName().asChar()] = true;
      }
      selectNodes(exoSceneRoot, selectionMap,
                  !bFlattenHierarchy || bTransformCache, bSelectChildren,
                  !bTransformCache, true);
    }

    // create object for each
    MProgressWindow::reserve();
    MProgressWindow::setTitle("Alembic Export: Listing objects");
    MProgressWindow::setInterruptable(true);
    MProgressWindow::setProgressRange(0, mSelection.length());
    MProgressWindow::setProgress(0);

    MProgressWindow::startProgress();
    int interrupt = 20;
    bool processStopped = false;
    std::deque<PreProcessStackElement> sceneStack;

    sceneStack.push_back(PreProcessStackElement(exoSceneRoot, mTop));

    while (!sceneStack.empty()) {
      if (--interrupt == 0) {
        interrupt = 20;
        if (MProgressWindow::isCancelled()) {
          processStopped = true;
          break;
        }
      }

      PreProcessStackElement &sElement = sceneStack.back();
      SceneNodePtr eNode = sElement.eNode;
      sceneStack.pop_back();

      Abc::OObject oParent = sElement.oParent;
      Abc::OObject oNewParent;

      AlembicObjectPtr pNewObject;
      if (eNode->selected) {
        switch (eNode->type) {
          case SceneNode::SCENE_ROOT:
            break;
          case SceneNode::ITRANSFORM:
          case SceneNode::ETRANSFORM:
            pNewObject.reset(new AlembicXform(eNode, this, oParent));
            break;
          case SceneNode::CAMERA:
            pNewObject.reset(new AlembicCamera(eNode, this, oParent));
            break;
          case SceneNode::POLYMESH:
            pNewObject.reset(new AlembicPolyMesh(eNode, this, oParent));
            break;
          case SceneNode::SUBD:
            pNewObject.reset(new AlembicSubD(eNode, this, oParent));
            break;
          case SceneNode::CURVES:
            pNewObject.reset(new AlembicCurves(eNode, this, oParent));
            break;
          case SceneNode::PARTICLES:
            pNewObject.reset(new AlembicPoints(eNode, this, oParent));
            break;
          case SceneNode::HAIR:
            pNewObject.reset(new AlembicHair(eNode, this, oParent));
            break;
          default:
            ESS_LOG_WARNING("Unknown type: not exporting " << eNode->name);
        }
      }

      if (pNewObject) {
        AddObject(pNewObject);
        oNewParent = oParent.getChild(eNode->name);
      }
      else {
        oNewParent = oParent;
      }

      if (oNewParent.valid()) {
        for (std::list<SceneNodePtr>::iterator it = eNode->children.begin();
             it != eNode->children.end(); ++it) {
          if (!bFlattenHierarchy ||
              (bFlattenHierarchy && eNode->type == SceneNode::ETRANSFORM &&
               isShapeNode((*it)->type))) {
            // If flattening the hierarchy, we want to attach each external
            // transform to its corresponding geometry node.
            // All internal transforms should be skipped. Geometry nodes will
            // never have children (If and XSI geonode is parented
            // to another geonode, each will be parented to its extracted
            // transform node, and one node will be parented to the
            // transform of the other.
            sceneStack.push_back(PreProcessStackElement(*it, oNewParent));
          }
          else {
            // if we skip node A, we parent node A's children to the parent of A
            sceneStack.push_back(PreProcessStackElement(*it, oParent));
          }
        }
      }
      //*
      else {
        ESS_LOG_ERROR("Do not have reference to parent.");
        MPxCommand::setResult(
            "Error caught in AlembicWriteJob::PreProcess: do not have "
            "reference to parent");
        return MS::kFailure;
      }
      //*/
    }

    MProgressWindow::endProgress();
    return processStopped ? MStatus::kEndOfFile : MStatus::kSuccess;
  }
  catch (AbcU::Exception &e) {
    this->forceCloseArchive();
    MString exc(e.what());
    MGlobal::displayError("[ExocortexAlembic] Error writing to file '" +
                          mFileName + "' (" + exc +
                          "). Do you still have it opened?");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: error writing file");
  }

  return MS::kFailure;
}
MStatus AlembicExportCommand::doIt(const MArgList &args)
{
  ESS_PROFILE_SCOPE("AlembicExportCommand::doIt");

  MStatus status = MS::kFailure;

  MTime currentAnimStartTime = MAnimControl::animationStartTime(),
        currentAnimEndTime = MAnimControl::animationEndTime(),
        oldCurTime = MAnimControl::currentTime(),
        curMinTime = MAnimControl::minTime(),
        curMaxTime = MAnimControl::maxTime();
  MArgParser argData(syntax(), args, &status);

  if (argData.isFlagSet("help")) {
    // TODO: implement help for this command
    // MGlobal::displayInfo(util::getHelpText());
    return MS::kSuccess;
  }

  unsigned int jobCount = argData.numberOfFlagUses("jobArg");
  MStringArray jobStrings;
  if (jobCount == 0) {
    // TODO: display dialog
    MGlobal::displayError("[ExocortexAlembic] No jobs specified.");
    MPxCommand::setResult(
        "Error caught in AlembicExportCommand::doIt: no job specified");
    return status;
  }
  else {
    // get all of the jobstrings
    for (unsigned int i = 0; i < jobCount; i++) {
      MArgList jobArgList;
      argData.getFlagArgumentList("jobArg", i, jobArgList);
      jobStrings.append(jobArgList.asString(0));
    }
  }

  // create a vector to store the jobs
  std::vector<AlembicWriteJob *> jobPtrs;
  double minFrame = 1000000.0;
  double maxFrame = -1000000.0;
  double maxSteps = 1;
  double maxSubsteps = 1;

  // init the curve accumulators
  AlembicCurveAccumulator::Initialize();

  try {
    // for each job, check the arguments
    bool failure = false;
    for (unsigned int i = 0; i < jobStrings.length(); ++i) {
      double frameIn = 1.0;
      double frameOut = 1.0;
      double frameSteps = 1.0;
      double frameSubSteps = 1.0;
      MString filename;
      bool purepointcache = false;
      bool normals = true;
      bool uvs = true;
      bool facesets = true;
      bool bindpose = true;
      bool dynamictopology = false;
      bool globalspace = false;
      bool withouthierarchy = false;
      bool transformcache = false;
      bool useInitShadGrp = false;
      bool useOgawa = false;  // Later, will need to be changed!

      MStringArray objectStrings;
      std::vector<std::string> prefixFilters;
      std::set<std::string> attributes;
      std::vector<std::string> userPrefixFilters;
      std::set<std::string> userAttributes;
      MObjectArray objects;
      std::string search_str, replace_str;

      // process all tokens of the job
      MStringArray tokens;
      jobStrings[i].split(';', tokens);
      for (unsigned int j = 0; j < tokens.length(); j++) {
        MStringArray valuePair;
        tokens[j].split('=', valuePair);
        if (valuePair.length() != 2) {
          MGlobal::displayWarning(
              "[ExocortexAlembic] Skipping invalid token: " + tokens[j]);
          continue;
        }

        const MString &lowerValue = valuePair[0].toLowerCase();
        if (lowerValue == "in") {
          frameIn = valuePair[1].asDouble();
        }
        else if (lowerValue == "out") {
          frameOut = valuePair[1].asDouble();
        }
        else if (lowerValue == "step") {
          frameSteps = valuePair[1].asDouble();
        }
        else if (lowerValue == "substep") {
          frameSubSteps = valuePair[1].asDouble();
        }
        else if (lowerValue == "normals") {
          normals = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "uvs") {
          uvs = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "facesets") {
          facesets = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "bindpose") {
          bindpose = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "purepointcache") {
          purepointcache = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "dynamictopology") {
          dynamictopology = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "globalspace") {
          globalspace = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "withouthierarchy") {
          withouthierarchy = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "transformcache") {
          transformcache = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "filename") {
          filename = valuePair[1];
        }
        else if (lowerValue == "objects") {
          // try to find each object
          valuePair[1].split(',', objectStrings);
        }
        else if (lowerValue == "useinitshadgrp") {
          useInitShadGrp = valuePair[1].asInt() != 0;
        }

        // search/replace
        else if (lowerValue == "search") {
          search_str = valuePair[1].asChar();
        }
        else if (lowerValue == "replace") {
          replace_str = valuePair[1].asChar();
        }
        else if (lowerValue == "ogawa") {
          useOgawa = valuePair[1].asInt() != 0;
        }
        else if (lowerValue == "attrprefixes") {
          splitListArg(valuePair[1], prefixFilters);
        }
        else if (lowerValue == "attrs") {
          splitListArg(valuePair[1], attributes);
        }
        else if (lowerValue == "userattrprefixes") {
          splitListArg(valuePair[1], userPrefixFilters);
        }
        else if (lowerValue == "userattrs") {
          splitListArg(valuePair[1], userAttributes);
        }
        else {
          MGlobal::displayWarning(
              "[ExocortexAlembic] Skipping invalid token: " + tokens[j]);
          continue;
        }
      }

      // now check the object strings
      for (unsigned int k = 0; k < objectStrings.length(); k++) {
        MSelectionList sl;
        MString objectString = objectStrings[k];
        sl.add(objectString);
        MDagPath dag;
        for (unsigned int l = 0; l < sl.length(); l++) {
          sl.getDagPath(l, dag);
          MObject objRef = dag.node();
          if (objRef.isNull()) {
            MGlobal::displayWarning("[ExocortexAlembic] Skipping object '" +
                                    objectStrings[k] + "', not found.");
            break;
          }

          // get all parents
          MObjectArray parents;

          // check if this is a camera
          bool isCamera = false;
          for (unsigned int m = 0; m < dag.childCount(); ++m) {
            MFnDagNode child(dag.child(m));
            MFn::Type ctype = child.object().apiType();
            if (ctype == MFn::kCamera) {
              isCamera = true;
              break;
            }
          }

          if (dag.node().apiType() == MFn::kTransform && !isCamera &&
              !globalspace && !withouthierarchy) {
            MDagPath ppath = dag;
            while (!ppath.node().isNull() && ppath.length() > 0 &&
                   ppath.isValid()) {
              parents.append(ppath.node());
              if (ppath.pop() != MStatus::kSuccess) {
                break;
              }
            }
          }
          else {
            parents.append(dag.node());
          }

          // push all parents in
          while (parents.length() > 0) {
            bool found = false;
            for (unsigned int m = 0; m < objects.length(); m++) {
              if (objects[m] == parents[parents.length() - 1]) {
                found = true;
                break;
              }
            }
            if (!found) {
              objects.append(parents[parents.length() - 1]);
            }
            parents.remove(parents.length() - 1);
          }

          // check all of the shapes below
          if (!transformcache) {
            sl.getDagPath(l, dag);
            for (unsigned int m = 0; m < dag.childCount(); m++) {
              MFnDagNode child(dag.child(m));
              if (child.isIntermediateObject()) {
                continue;
              }
              objects.append(child.object());
            }
          }
        }
      }

      // check if we have incompatible subframes
      if (maxSubsteps > 1.0 && frameSubSteps > 1.0) {
        const double part = (frameSubSteps > maxSubsteps)
                                ? (frameSubSteps / maxSubsteps)
                                : (maxSubsteps / frameSubSteps);
        if (abs(part - floor(part)) > 0.001) {
          MString frameSubStepsStr, maxSubstepsStr;
          frameSubStepsStr.set(frameSubSteps);
          maxSubstepsStr.set(maxSubsteps);
          MGlobal::displayError(
              "[ExocortexAlembic] You cannot combine substeps " +
              frameSubStepsStr + " and " + maxSubstepsStr +
              " in one export. Aborting.");
          return MStatus::kInvalidParameter;
        }
      }

      // remember the min and max values for the frames
      if (frameIn < minFrame) {
        minFrame = frameIn;
      }
      if (frameOut > maxFrame) {
        maxFrame = frameOut;
      }
      if (frameSteps > maxSteps) {
        maxSteps = frameSteps;
      }
      if (frameSteps > 1.0) {
        frameSubSteps = 1.0;
      }
      if (frameSubSteps > maxSubsteps) {
        maxSubsteps = frameSubSteps;
      }

      // check if we have a filename
      if (filename.length() == 0) {
        MGlobal::displayError("[ExocortexAlembic] No filename specified.");
        for (size_t k = 0; k < jobPtrs.size(); k++) {
          delete (jobPtrs[k]);
        }
        MPxCommand::setResult(
            "Error caught in AlembicExportCommand::doIt: no filename "
            "specified");
        return MStatus::kFailure;
      }

      // construct the frames
      MDoubleArray frames;
      {
        const double frameIncr = frameSteps / frameSubSteps;
        for (double frame = frameIn; frame <= frameOut; frame += frameIncr) {
          frames.append(frame);
        }
      }

      AlembicWriteJob *job =
          new AlembicWriteJob(filename, objects, frames, useOgawa,
              prefixFilters, attributes, userPrefixFilters, userAttributes);
      job->SetOption("exportNormals", normals ? "1" : "0");
      job->SetOption("exportUVs", uvs ? "1" : "0");
      job->SetOption("exportFaceSets", facesets ? "1" : "0");
      job->SetOption("exportInitShadGrp", useInitShadGrp ? "1" : "0");
      job->SetOption("exportBindPose", bindpose ? "1" : "0");
      job->SetOption("exportPurePointCache", purepointcache ? "1" : "0");
      job->SetOption("exportDynamicTopology", dynamictopology ? "1" : "0");
      job->SetOption("indexedNormals", "1");
      job->SetOption("indexedUVs", "1");
      job->SetOption("exportInGlobalSpace", globalspace ? "1" : "0");
      job->SetOption("flattenHierarchy", withouthierarchy ? "1" : "0");
      job->SetOption("transformCache", transformcache ? "1" : "0");

      // check if the search/replace strings are valid!
      if (search_str.length() ? !replace_str.length()
                              : replace_str.length())  // either search or
                                                       // replace string is
                                                       // missing or empty!
      {
        ESS_LOG_WARNING(
            "Missing search or replace parameter. No strings will be "
            "replaced.");
        job->replacer = SearchReplace::createReplacer();
      }
      else {
        job->replacer = SearchReplace::createReplacer(search_str, replace_str);
      }

      // check if the job is satifsied
      if (job->PreProcess() != MStatus::kSuccess) {
        MGlobal::displayError("[ExocortexAlembic] Job skipped. Not satisfied.");
        delete (job);
        failure = true;
        break;
      }

      // push the job to our registry
      MGlobal::displayInfo("[ExocortexAlembic] Using WriteJob:" +
                           jobStrings[i]);
      jobPtrs.push_back(job);
    }

    if (failure) {
      for (size_t k = 0; k < jobPtrs.size(); k++) {
        delete (jobPtrs[k]);
      }
      return MS::kFailure;
    }

    // compute the job count
    unsigned int jobFrameCount = 0;
    for (size_t i = 0; i < jobPtrs.size(); i++)
      jobFrameCount += (unsigned int)jobPtrs[i]->GetNbObjects() *
                       (unsigned int)jobPtrs[i]->GetFrames().size();

    // now, let's run through all frames, and process the jobs
    const double frameRate = MTime(1.0, MTime::kSeconds).as(MTime::uiUnit());
    const double incrSteps = maxSteps / maxSubsteps;
    double nextFrame = minFrame + incrSteps;

    for (double frame = minFrame; frame <= maxFrame;
         frame += incrSteps, nextFrame += incrSteps) {
      MAnimControl::setCurrentTime(MTime(frame / frameRate, MTime::kSeconds));
      MAnimControl::setAnimationEndTime(
          MTime(nextFrame / frameRate, MTime::kSeconds));
      MAnimControl::playForward();  // this way, it forces Maya to play exactly
      // one frame! and particles are updated!

      AlembicCurveAccumulator::StartRecordingFrame();
      for (size_t i = 0; i < jobPtrs.size(); i++) {
        MStatus status = jobPtrs[i]->Process(frame);
        if (status != MStatus::kSuccess) {
          MGlobal::displayError("[ExocortexAlembic] Job aborted :" +
                                jobPtrs[i]->GetFileName());
          for (size_t k = 0; k < jobPtrs.size(); k++) {
            delete (jobPtrs[k]);
          }
          restoreOldTime(currentAnimStartTime, currentAnimEndTime, oldCurTime,
                         curMinTime, curMaxTime);
          return status;
        }
      }
      AlembicCurveAccumulator::StopRecordingFrame();
    }
  }
  catch (...) {
    MGlobal::displayError(
        "[ExocortexAlembic] Jobs aborted, force closing all archives!");
    for (std::vector<AlembicWriteJob *>::iterator beg = jobPtrs.begin();
         beg != jobPtrs.end(); ++beg) {
      (*beg)->forceCloseArchive();
    }
    restoreOldTime(currentAnimStartTime, currentAnimEndTime, oldCurTime,
                   curMinTime, curMaxTime);
    MPxCommand::setResult("Error caught in AlembicExportCommand::doIt");
    status = MS::kFailure;
  }
  MAnimControl::stop();
  AlembicCurveAccumulator::Destroy();

  // restore the animation start/end time and the current time!
  restoreOldTime(currentAnimStartTime, currentAnimEndTime, oldCurTime,
                 curMinTime, curMaxTime);

  // delete all jobs
  for (size_t k = 0; k < jobPtrs.size(); k++) {
    delete (jobPtrs[k]);
  }

  // remove all known archives
  deleteAllArchives();
  return status;
}