CStatus AlembicWriteJob::Process()
{
    // check filenames
    if(mFileName.IsEmpty())
    {
        Application().LogMessage(L"[alembic] No filename specified.",siErrorMsg);
        return CStatus::InvalidArgument;
    }

    // check objects
    if(mSelection.GetCount() == 0)
    {
        Application().LogMessage(L"[alembic] No objects specified.",siErrorMsg);
        return CStatus::InvalidArgument;
    }

    // check frames
    if(mFrames.size() == 0)
    {
        Application().LogMessage(L"[alembic] No frames specified.",siErrorMsg);
        return CStatus::InvalidArgument;
    }

    // init archive (use a locally scoped archive)
    CString sceneFileName = L"Exported from: "+Application().GetActiveProject().GetActiveScene().GetParameterValue(L"FileName").GetAsText();
    mArchive = CreateArchiveWithInfo(
                   Alembic::AbcCoreHDF5::WriteArchive(),
                   mFileName.GetAsciiString(),
                   "Softimage Alembic Plugin",
                   sceneFileName.GetAsciiString(),
                   Alembic::Abc::ErrorHandler::kThrowPolicy);

    // get the frame rate
    double frameRate = 25.0;
    CValue returnVal;
    CValueArray args(1);
    args[0] = L"PlayControl.Rate";
    Application().ExecuteCommand(L"GetValue",args,returnVal);
    frameRate = returnVal;
    if(frameRate == 0.0)
        frameRate = 25.0;
    double timePerSample = 1.0 / frameRate;

    // create the sampling
    AbcA::TimeSampling sampling(timePerSample,0.0);
    mTs = mArchive.addTimeSampling(sampling);

    Alembic::Abc::OBox3dProperty boxProp = Alembic::AbcGeom::CreateOArchiveBounds(mArchive,mTs);

    // create object for each
    std::vector<AlembicObjectPtr> objects;
    for(LONG i=0; i<mSelection.GetCount(); i++)
    {
        X3DObject xObj(mSelection[i]);
        if(xObj.GetType().IsEqualNoCase(L"camera"))
        {
            AlembicObjectPtr ptr;
            ptr.reset(new AlembicCamera(xObj.GetActivePrimitive().GetRef(),this));
            objects.push_back(ptr);
        }
        else if(xObj.GetType().IsEqualNoCase(L"polymsh"))
        {
            AlembicObjectPtr ptr;
            ptr.reset(new AlembicPolyMesh(xObj.GetActivePrimitive().GetRef(),this));
            objects.push_back(ptr);
        }
    }

    ProgressBar prog;
    prog = Application().GetUIToolkit().GetProgressBar();
    prog.PutMinimum(0);
    prog.PutMaximum((LONG)(mFrames.size() * objects.size()));
    prog.PutValue(0);
    prog.PutCancelEnabled(true);
    prog.PutVisible(true);

    for(unsigned int frame=0; frame < (unsigned int)mFrames.size(); frame++)
    {
        for(size_t i=0; i<objects.size(); i++)
        {
            prog.PutCaption(L"Frame "+CString(mFrames[frame])+L" of "+objects[i]->GetRef().GetAsText());
            CStatus status = objects[i]->Save(mFrames[frame]);
            if(status != CStatus::OK)
                return status;
            if(prog.IsCancelPressed())
                break;
            prog.Increment();
        }
        if(prog.IsCancelPressed())
            break;
    }

    prog.PutVisible(false);

    return CStatus::OK;
}
void OutputClusterPropertyValues( std::ofstream& in_mfw,	CGeometryAccessor& in_ga, CRefArray& in_array)
{	
	LONG nVals = in_array.GetCount();
	double s;

	ClusterProperty cp(in_array[0]);

	CFloatArray valueArray;
	CBitArray flags;		
	cp.GetValues( valueArray, flags );

	LONG nValueSize = cp.GetValueSize();
	
	bar.PutValue(0);
	bar.PutStatusText(L"Optimize UV...");
	// polygon node indices
	CLongArray polyNodeIdxArray;
	CStatus st = in_ga.GetNodeIndices(polyNodeIdxArray);
	st.AssertSucceeded( L"GetNodeIndices");

	//app.LogMessage(L"polyNodeIdxArray: " + polyNodeIdxArray.GetAsText());
	g_aNodeIsland = polyNodeIdxArray;
	std::vector<float> newValueArray;
	newValueArray.clear();

	for ( LONG j=0; j < polyNodeIdxArray.GetCount(); j++)
	{	
		float u = valueArray[polyNodeIdxArray[j]*3];
		float v = valueArray[polyNodeIdxArray[j]*3+1];
		//app.LogMessage(L"u = " + CString(u)+ "; v = "+ CString(v));
		LONG nmb = 0;
		bool bIs = false;
		for ( ULONG k = 0; k < newValueArray.size(); k += 3 )
		{
			if(fabs(newValueArray.at(k) - u) < 0.000002 && fabs(newValueArray.at(k+1) - v) < 0.000002)
			{
				nmb = k/3;
				bIs = true;
				break;
				//app.LogMessage(L"Yarr!: g_aNodeIsland["+ CString(j)+"] = "+ CString(k/3));
			}
		}
		
		if(bIs)
		{
			g_aNodeIsland[j] = nmb;
			//app.LogMessage(L"Yarr!: g_aNodeIsland["+ CString(j)+"] = "+ CString(nmb));
		}
		else
		{
			newValueArray.push_back(u);
			newValueArray.push_back(v);
			newValueArray.push_back(0.0f);
			g_aNodeIsland[j] = (LONG)(newValueArray.size()/3-1);
			//app.LogMessage(L"g_aNodeIsland["+ CString(j)+"] = "+ CString(newValueArray.size()/3-1));
		}
	}

	in_mfw << "#begin ";
	in_mfw << CString((ULONG)newValueArray.size()/nValueSize).GetAsciiString();
	in_mfw << "\n";
	
	bar.PutStatusText(L"Clusters...");
	bar.PutMinimum(0);
	bar.PutMaximum((LONG)newValueArray.size()/nValueSize);
	for ( ULONG j=0; j < newValueArray.size(); j += nValueSize)
	{
		//bar.PutValue(j);
		s = newValueArray.at(j);
		CString strValues = FormatNumber(s);
		
		for ( LONG k=1; k<nValueSize; k++ )
		{
			s = newValueArray.at(j+k);
			strValues += L" " + FormatNumber(s);
		}
		
		in_mfw << "vt ";
		in_mfw << strValues.GetAsciiString();
		in_mfw << "\n";

		gVt++;
		bar.Increment();
	}
	CString string = L"#end " +  CString((ULONG)newValueArray.size()/nValueSize) + L"\n";
	in_mfw << string.GetAsciiString();
	in_mfw << "\n";
}