Tuesday, February 18, 2014

Alembic Maya - Edge Crease Export

Recently I encountered a request from our Rigging TD to see how we may be able to export meshes in Alembic format with edge crease information from Maya. Unfortunately, Alembic is still quite poorly documented in one single place, so it takes a bit of time to put all the information together to figure out how certain things are done. One of the things I ended up having to do was to actually traverse the AlembicExporter for Maya to learn how certain export processes work. The main Object that we need to look at to see how Alembic finds crease data is MayaMeshWrite.cpp. In this Object, if we look at lines 288-324, the constructor of the MayaMeshWriter object, we can see this chunk of code:
// check to see if this poly has been tagged as a SubD
MPlug plug = lMesh.findPlug("SubDivisionMesh");
if ( !plug.isNull() && plug.asBool() )
{
    Alembic::AbcGeom::OSubD obj(iParent, name.asChar(), iTimeIndex);
    mSubDSchema = obj.getSchema();

    Alembic::AbcGeom::OV2fGeomParam::Sample uvSamp;
    if ( mWriteUVs )
    {
        getUVs(uvs, indices);

        if (!uvs.empty())
        {
            uvSamp.setScope( Alembic::AbcGeom::kFacevaryingScope );
            uvSamp.setVals(Alembic::AbcGeom::V2fArraySample(
                (const Imath::V2f *) &uvs.front(), uvs.size() / 2));
            if (!indices.empty())
            {
                uvSamp.setIndices(Alembic::Abc::UInt32ArraySample(
                    &indices.front(), indices.size()));
            }
        }
    }

    Alembic::Abc::OCompoundProperty cp;
    Alembic::Abc::OCompoundProperty up;
    if (AttributesWriter::hasAnyAttr(lMesh, iArgs))
    {
        cp = mSubDSchema.getArbGeomParams();
        up = mSubDSchema.getUserProperties();
    }
    mAttrs = AttributesWriterPtr(new AttributesWriter(cp, up, obj,
        lMesh, iTimeIndex, iArgs));

    writeSubD(uvSamp);
}
In the code, you may notice that the exporter is looking for the plug "SubDivisionMesh" in the mesh that is being parsed. Now, when I looked at this the first time, I thought that the tag might be added when you use an actual SubD meshes created in Maya. But, alas, in Maya 2014, the support for the SubD meshes has been changed, and you no longer can even create Subdivision primitives...

After a few attempts I realized that the SubD meshes that I got converted from PolyMeshes were not containing the "SubDivisonMesh" plug that the exporter was looking for.

Over the weekend the Rigging TD that asked me to look into this, had a look as well, and saw what I was missing this whole time - the flag "SubDivisionMesh" was a bool tag that the user was supposed to add him/herself! And sure enough, as soon as you add the tag to the mesh (NOTE: Add the tag to the Shape node of the mesh, NOT the transform node. But export the Transform node. Makes sense, right??? If so, lucky you, cause I don't get the point...:( ) and export it, the mesh is exported using Alembic's OSubD and OSubDSchema, which is capable of storing the edge creases and corners, as per Alembic docs. The creases are exported in MayaMeshWriter::writeSubD function, in lines 834-862:
std::vector <Alembic::Util::int32_t> creaseIndices;
std::vector <Alembic::Util::int32_t> creaseLengths;
std::vector <float> creaseSharpness;

std::vector <Alembic::Util::int32_t> cornerIndices;
std::vector <float> cornerSharpness;

MUintArray edgeIds;
MDoubleArray creaseData;
if (lMesh.getCreaseEdges(edgeIds, creaseData) == MS::kSuccess)
{
    unsigned int numCreases = creaseData.length();
    creaseIndices.resize(numCreases * 2);
    creaseLengths.resize(numCreases, 2);
    creaseSharpness.resize(numCreases);
    for (unsigned int i = 0; i < numCreases; ++i)
    {
        int verts[2];
        lMesh.getEdgeVertices(edgeIds[i], verts);
        creaseIndices[2 * i] = verts[0];
        creaseIndices[2 * i + 1] = verts[1];
        creaseSharpness[i] = static_cast<float>(creaseData[i]);
    }

    samp.setCreaseIndices(Alembic::Abc::Int32ArraySample(creaseIndices));
    samp.setCreaseLengths(Alembic::Abc::Int32ArraySample(creaseLengths));
    samp.setCreaseSharpnesses(
        Alembic::Abc::FloatArraySample(creaseSharpness));
}
Another thing to note: the "SubDivisionMesh" plug does not need to be added to the export Attributes list in the Alembic Exporter or the script function AbcExport. The plug is searched for in the MayaMeshWriter constructor, and so any mesh object being exported will be checked for the presence of this flag.

Hopefully this saves someone out there the time it took me and my colleague to look all this up!

I'll keep writing more as I dig deeper into Alembic, and Alembics exporter for maya, so perhaps this blog will live again! Yay!

5 comments:

  1. Very informative! How did you attach the flag to the shape node? Was it a script?

    ReplyDelete
  2. ##This works!
    import maya.cmds as cmds
    selMesh=cmds.ls(sl=True)
    listShape=cmds.listRelatives(selMesh,s=True)
    selShape=listShape[0]
    cmds.select(selShape)
    cmds.addAttr(ln="SubDivisionMesh",at='bool')
    cmds.setAttr(selShape+".SubDivisionMesh",k=True,e=True)
    cmds.setAttr(selShape+".SubDivisionMesh",1)

    ReplyDelete
    Replies
    1. Yup, you got it! Just add it like you would nay other attribute. Just make sure to spell it right and keep the camel casing.

      Glad I could help!

      Cheers,

      DK

      Delete
  3. I have noticed that this method isn't working with skinned meshes. Any theories?

    ReplyDelete
    Replies
    1. Could you specify what isn't working? The creasing, or the attribute addition?

      Delete