Maya API How-To #18

Back · Previous · Next Maya

How do I write a polygon mesh exporter?

After a deluge of queries on this (well, OK, three) in the span of a week, I'm thinking there may be some interest in having this posted hereā€¦

The API class that you use to register an "exporter" plug-in is ‘MPxFileTranslator’. Unfortunately, Maya's documentation on the specifics for using this class are sparse (let me know if you find any!) so you pretty much have to rely on the example source code provided in the devkit folder.

Ultimately, a polygon exporter boils down to the dissection of each mesh object in the scene, and knowing how to acquire the vertex positions, normals, colours, and UVs. A basic polygon exporter can be written using only MFnMesh and MItMeshPolygon.

Below is an amalgamate of code from various exporters I've written in the past. For the sake of simplicity (this is intended as an "introduction" to building an exporter), it does not cover processing multiple UV Sets (although this is as simple as iterating the sets), nor does it deal with Materials and Textures assigned to the mesh. Also, I don't touch on exporting animations.

Note: I have not attempted to compile this code as it is written here, but it should be fundamentally correct as it was ripped out of actual production tools. If you find any errors please let me know and I'll correct them.

First, we start with a utility function. For retrieving the normals for vertex-face components, Maya's API requires face-relative vertex indices. However, the methods for querying the vertices associated with the triangulation of an n-gon return object-relative indices. This method converts from object-relative indices to face-relative indices.

// ********************************************************************
// MItMeshPolygon::getTriangle() returns object-relative vertex
// indices; BUT MItMeshPolygon::normalIndex() and ::getNormal() need
// face-relative vertex indices! This converts vertex indices from
// object-relative to face-relative.
// param  getVertices: Array of object-relative vertex indices for
//                     entire face.
// param  getTriangle: Array of object-relative vertex indices for
//                     local triangle in face.
// return Array of face-relative indicies for the specified vertices.
//        Number of elements in returned array == number in getTriangle
//        (should be 3).
// note   If getTriangle array does not include a corresponding vertex
//        in getVertices array then a value of (-1) will be inserted
//        in that position within the returned array.
// ********************************************************************
MIntArray GetLocalIndex( MIntArray & getVertices, MIntArray & getTriangle )
  MIntArray   localIndex;
  unsigned    gv, gt;

  assert ( getTriangle.length() == 3 );    // Should always deal with a triangle

  for ( gt = 0; gt < getTriangle.length(); gt++ )
    for ( gv = 0; gv < getVertices.length(); gv++ )
      if ( getTriangle[gt] == getVertices[gv] )
        localIndex.append( gv );

    // if nothing was added, add default "no match"
    if ( localIndex.length() == gt )
      localIndex.append( -1 );

  return localIndex;

And here's the function that does all the work. Given the DAG path for a mesh, it extracts the triangles that may be used to construct the shape. The way this code is written the data doesn't actually go anywhere - it simply drops out of scope when the method returns. You'll want to define class members to hold the information, or add an argument that provides appropriate storage.

MStatus ExportMesh( const MDagPath& dagForMesh )
   MStatus  status;

  MFnMesh  fnMesh( dagForMesh );

  //  Cache positions for each vertex
  MPointArray meshPoints;
  fnMesh.getPoints( meshPoints, MSpace::kObject );

  //  Cache normals for each vertex
  MFloatVectorArray  meshNormals;
  // Normals are per-vertex per-face..
  // use MItMeshPolygon::normalIndex() for index
  fnMesh.getNormals( meshNormals );

  // Get UVSets for this mesh
  MStringArray  UVSets;
  status = fnMesh.getUVSetNames( UVSets );

  // Get all UVs for the first UV set.
  MFloatArray   u, v;
  fnMesh.getUVs( u, v, &UVSets[0] );

  // Get Colors for this mesh
  // (Not used in this example - included for reference)
  MColorArray  vertexColors;
  MColorArray  faceVertexColors;
  fnMesh.getVertexColors( vertexColors );
  fnMesh.getFaceVertexColors( faceVertexColors );

  int  vtxInPolygon;

  MItMeshPolygon  itPolygon( dagForMesh, MObject::kNullObj );
  for ( /* nothing */; !itPolygon.isDone(); )
    // Get object-relative indices for the vertices in this face.
    MIntArray                           polygonVertices;
    itPolygon.getVertices( polygonVertices );

    // Get triangulation of this poly.
    int numTriangles = itPolygon.numTriangles();
    while ( numTriangles-- )
        MPointArray                     nonTweaked;
        // object-relative vertex indices for each triangle
        MIntArray                       triangleVertices;
        // face-relative vertex indices for each triangle
        MIntArray                       localIndex;

        status = itPolygon.getTriangle( numTriangles,
                                        MSpace::kObject );

        if ( status == MS::kSuccess )

      // --------  Get Positions  --------

          // While it may be tempting to use the points array returned
          // by MItMeshPolygon::getTriangle(), don't. It does not represent
          // poly tweaks in its coordinates!

          // Positions are:
          //  { meshPoints[triangleVertices[0]],
          //    meshPoints[triangleVertices[1]],
          //    meshPoints[triangleVertices[2]] }

      // --------  Get Normals  --------

          // (triangleVertices) is the object-relative vertex indices
          // BUT MItMeshPolygon::normalIndex() and ::getNormal() needs
          // face-relative vertex indices!

          // Get face-relative vertex indices for this triangle
          localIndex = GetLocalIndex( polygonVertices,
                                      triangleVertices );

          // Normals are:
          //  { meshNormals[ itPolygon.normalIndex( localIndex[0] ) ],
          //    meshNormals[ itPolygon.normalIndex( localIndex[1] ) ],
          //    meshNormals[ itPolygon.normalIndex( localIndex[2] ) ] }

      // --------  Get UVs  --------

          // Note: In this example I'm only considering the first UV Set.
          // If you want more simply loop through the sets in the UVSets array.

          int uvID[3];

          // Get UV values for each vertex within this polygon
          for ( vtxInPolygon = 0; vtxInPolygon < 3; vtxInPolygon++ )
            itPolygon.getUVIndex( localIndex[vtxInPolygon],
                                  &UVSets[0] );

          // UVs are:
          //  { { u[uvID[0]], v[uvID[0]] },
          //    { u[uvID[1]], v[uvID[1]] },
          //    { u[uvID[2]], v[uvID[2]] } }

      // --------  Get Colours  --------

          MColor colours[3];

          // Get Colour values for each vertex within this polygon
          for ( vtxInPolygon = 0; vtxInPolygon < 3; vtxInPolygon++ )
            itPolygon.getColor( colours[vtxInPolygon],
                                localIndex[vtxInPolygon] );

          // Colours are:
          //  { colours[0],
          //    colours[1],
          //    colours[2] }

      }    // getTriangle()

    }    // while (triangles)

  }    // itPolygon()

  return status;

}    // ExportMesh()

Related How-To's

04 April, 2003