Building an Export List


 

 

When writing an exporter, one crucial aspect is how you determine a list of items to export. Although you could simply output every node you find in the scene, you will find that this method introduces lots of redundant data within your exported file.

Instead it is much better to be selective with what you decide to export. One solution would be to flag all nodes to be exported with a custom attribute that indicates whether to ignore the node or not. Again, this is an option however you are bound to have human errors creeping in, files going out of sync etc.

Ideally we want to be able to generate a list of objects automatically that consist of as small amount of data as possible. The method I usually adopt first parses the scene to build up lists of items that we will end up exporting. This allows us to check to see which nodes are actually used and ignore any other ones we come across.

The psuedo code looks a little bit like this :

 

MObjectArray Textures;
MObjectArray Materials;
MDagPathArray Shapes;
MDagPathArray Transforms;

// First iterate through all shapes (surfaces,lights,curves,camera's etc)
for each node in maya {
  if object is a skin cluster
  add influence joints to Transform array
else {
  if object is a mesh or nurb surface {
  add materials used into the material array
add textures used by materials into the Textures array
}
if object is of a type we want to export {
  recurse UP the bone hierarchy to the world transform
  add each bone into the Transform array
}
}

}

 
 

 

 

Source Code

source code presented on this page can be found here

 

 

 

The Global Data

In order to build up an export list, we need to be able to store a global set of references to the various nodes that we consider to be important enough to export.

 

The code on the right provides a very basic way of storing the references we generate.

 

maps between the node name and the MObject for the node are generated for textures, materials, shapes and transforms.

 

By using the maps we are then able to quickly check to see if the node is already contained in the export list.

 

These maps can simply be iterated through later to export each important node.

 

The emphasis at this stage must be to ensure that you do not generate large quantities of temporary data. Ideally, do not make copies of the surface and transform data, instead output that directly to the file. Maya is a memory heavy application on it's own, allocating large amounts of temporary data is going to kill the speed of the exporter (if it doesn't crash).

 

Note: although the code here presents the maps as global variables, if you are deriving a plugin from MPxFileTranslator, then you may want to add the maps as member data of your file translator plugin.

 

 

 

 

 

 

 

 

 

 

 

 

 

 


#include<maya/MFnDependencyNode.h>
#include<maya/MFnDagNode.h>
#include<maya/MDagPath.h>
#include<map>
#include<string>
#include<algorithm>

// global map of texture references
std::map<std::string,MObject> Textures;
// global map of material references

std::map<std::string,MObject>
Materials;
// global map of material references

std::map<std::string,MObject>
Shapes;
// global map of material references

std::map<std::string,MObject>
Transforms;

void CleanReferences() {
  Textures.clear();
Materials.clear();
Shapes.clear();
Transforms.clear();
}

void AddTexture(MObject& obj) {
 

MFnDependencyNode fn(obj);

// get name of texture node
std::string
name = fn.name().asChar();

// if we can't find it in the map...
if
( Textures.find( name ) == Textures.end() ) {

  // insert the object into the map
Textures.insert( std::make_pair(name,obj) );

}

}

void AddMaterial(MObject& obj) {
 

MFnDependencyNode fn(obj);

// get name of texture node
std::string
name = fn.name().asChar();

// if we can't find it in the map...
if
( Materials.find( name ) == Materials.end() ) {

  // insert the object into the map
Materials.insert( std::make_pair(name,obj) );

}

}

void AddObject(MObject& obj) {
 

MFnDagNode fn(obj);

// get name of texture node
std::string
name = fn.name().asChar();

// if we can't find it in the map...
if
( Shapes.find( name ) == Shapes.end() ) {

  // insert the object into the map
Shapes.insert( std::make_pair(name,obj) );

}

}

void AddTransform(MObject& obj) {
 

MFnDagNode fn(obj);

// get name of texture node
std::string
name = fn.name().asChar();

// if we can't find it in the map...
if
( Transforms.find( name ) == Transforms.end() ) {

  // insert the object into the map
Transforms.insert( std::make_pair(name,obj) );

}

}

 

 

Recursing UP the hierarchy

When we come across some node we want to export, for example a polygonal mesh, we need to export all of the transforms that have gone into positioning our mesh in the 3D world. This basically includes all transforms above the mesh.

By recursing up the hierarchy we will only get the nodes that we actually need. If we descended down we would end up with redundant data.

 


#include<maya/MFnDagNode.h>

void RecurseUp(MObject& bone)
{
 

MFnDagNode fn(bone);

// add the transform into the export list
AddTransform
( fn.object() );

// loop through each parent of this transform. (We may have
// more than one parent if we have an instance)

for(int i=0;i<fn.parentCount();++i) {

 

// add parent node into transform list
RecurseUp
( fn.parent(i) );

}

}

 

 

Building References from SkinCluster nodes

 

Assuming that we are writing an exporter that supports skin weights, then we will need to make sure that all influence joints are included in the export lists.

 

This is basically a case of listing all skin clusters and adding the influence objects from each cluster into the Transform list.

 

 

 

 


#include<maya/MFnSkinCluster.h>
#include<maya/MDagPathArray.h>

void ReferenceSkinClusters()
{
 

MItDependencyNodes it(MFn::kSkinClusterFilter);

// loop through each skin cluster

for( ; !it.isDone(); it.next() ) {

 

// attach a skin cluster function set to
// access the data

MFnSkinCluster fn(it.item());
MDagPathArray infs;
MStatus stat;

// get an array of influence objects
fn.influenceObjects(infs,&stat);

// add each bone in skin cluster to transform list
for
(int i=0;i<infs.length();++i) {

  RecurseUp( infs[i].node() );

}

}

}

 

 

Building References from JointCluster nodes

 

Joint clusters only reference a single transform so it's fairly easy to add the joints into the export list.

 

 

 

 

 

 

 

 


#include<maya/MFnSkinCluster.h>
#include<maya/MDagPathArray.h>

void ReferenceJointClusters()
{
 

MItDependencyNodes it(MFn::kJointCluster);

// loop through each skin cluster

for( ; !it.isDone(); it.next() ) {

 

// attach a skin cluster function set to
// access the data

MFnWeightGeometryFilter fn(it.item());

MObject joint;

// get a plug to the matrix attribute
MPlug pJointPlug = fn.findPlug("matrix");
MPlugArray conns;

// add each bone in skin cluster to transform list
if
(pJointPlug.connectedTo(conns,true,false)) {

  RecurseUp( conns[0].node() );

}

}

}

 

 

Adding Materials, Textures and Objects to the Export List

 

Basically this sections of the code is concerned with building references to the objects and materials you wish to export. The ReferenceObject function adds the specified object, it's transforms, and any connected materials into the export lists.

 

The ReferenceMaterial function simply adds any textures it finds connected to the material to the export list.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


#include<maya/MFnDependencyNode.h>
#include<maya/MPlugArray.h>
#include<maya/MFnDagNode.h>
#include<maya/MDagPath.h>

void ReferenceMaterial(MObject& material)
{

 

MFnDependencyNode fn(material);

// add the material to our export list
AddMaterial(material);

// get an array of all connections to this node
MPlugArray
plugs;
fn.getConnections(plugs);

// loop through all connected attributes on this node
for
(int i=0;i<plugs.length();++i) {

 

MPlugArray conns;

// get the connections to this attribute
plugs[i].connectedTo(conns,true,false);

// loop through each connection looking for a
// file texture node.

for
(int j=0;j<conns.length();++j) {

 

MObject obj = conns[j].node();

if(obj.apiType()==MFn::kFileTexture) {

  // add texture to export list
AddTexture
(obj);

}

}

}

}

void ReferenceObject(MObject& shape)
{
 

MFnDagNode fn(shape);

if( fn.isIntermediateObject() ) return ;

AddObject( fn.object() );

// make sure we reference all of the objects
// parent transforms

for(int i=0;i<fn.parentCount();++i) {

 

RecurseUp( fn.parent(i) );

}

// get an array of all connections to this node
MPlugArray
plugs;
fn.getConnections(plugs);

// loop through all connected attributes on this node
for
(int i=0;i<plugs.length();++i) {

 

MPlugArray conns;

// get the connections to this attribute
plugs[i].connectedTo(conns,true,false);

// loop through each connection looking for a
// material node.

for
(int j=0;j<conns.length();++j) {

 

MObject obj = conns[j].node();

if( obj.hasFn(MFn::kShadingEngine) ) {

  // attach a function set to the shading engine
MFnDependencyNode fnS(obj);

// find the surface shader plug
MPlug
mat_plg = fnS.findPlug("surfaceShader");

MPlugArray material_conns;

// get the connections to this attribute
mat_plg.connectedTo(material_conns,true,false);

// loop through all connections
for
(int k=0;k<material_conns.length();++k) {

 

MObject mat = material_conns[k].node();

if( mat.hasFn(MFn::kLambert) ) {

  // add material to export list
ReferenceMaterial(mat);

}

}

}

if( shape.hasFn(MFn::kParticle) ) {

 

if( obj.hasFn(MFn::kField) ) {

  // add dynamics to export list
RecurseUp(obj);

}

if( obj.hasFn(MFn::kEmitter) ) {

  // add emitter to export list
RecurseUp(obj);

}

}

}

}

}

 

 

Parsing The Scene

 

The purpose of this function is to simply build references to the various shape nodes we are interested in, and in turn any textures/materials or transforms they reference.

 

At this stage you don't have to worry about the deformer nodes such as FFD's, blend shapes etc. They can be exported seperately later on in the process.

 

 

 

 

 

 

 

 

 

 

 


#include<maya/MFnSkinCluster.h>
#include<maya/MDagPathArray.h>

void ParseScene()
{
 

CleanReferences();

MItDependencyNodes it(MFn::kInvalid);

// loop through each object in the scene

for( ; !it.isDone(); it.next() ) {

 

switch(it.item().apiType()) {

  // output any 'interesting' shapes we find
case MFn::kMesh:
case MFn::kNurbsCurve:
case MFn::kNurbsSurface:
case MFn::kRenderSphere:
case MFn::kRenderBox:
case MFn::kRenderCone:
case MFn::kLocator:
case MFn::kPointLight:
case MFn::kAmbientLight:
case MFn::kDirectionalLight:
case MFn::kSpotLight:
case MFn::kParticle:
case MFn::kLattice:
case MFn::kBaseLattice:
ReferenceObject( it.item() );
break;

default : break;

}

}

// make sure we include bones used in skinning
ReferenceSkinClusters();
ReferenceJointClusters();

}

 

 

Do The Export

 

When the export process commences, the scene references are first built up. The data is then output in the following order

 

  • Textures
  • Materials
  • Shapes
  • Transforms
  • Soft & Rigid Skinning Info
  • Blend Shapes
  • FFD's
  • Animation Data

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


#include<maya/MFnSkinCluster.h>
#include<maya/MDagPathArray.h>

void Export()
{
 

ParseScene();

{ // output all textures

 

std::map<std::string,MObject>::iterator it;

it = Textures.begin();

// loop through each object in the scene

for( ; it != Textures.end(); ++it ) {

  OutputTexture(it->second);

}

}

{ // output all materials

 

std::map<std::string,MObject>::iterator it;

it = Materials.begin();

// loop through each object in the scene

for( ; it != Materials.end(); ++it ) {

  OutputMaterial(it->second);

}

}

{ // output all shapes

 

std::map<std::string,MObject>::iterator it;

it = Shapes.begin();

// loop through each object in the scene

for( ; it != Shapes.end(); ++it ) {

 

MObject obj = it->second;

switch(obj.apiType()) {

  // output correct node type.
// (you need to write the export funcs!)
case MFn
::kMesh:
case MFn::kNurbsCurve:
case MFn::kNurbsSurface:
case MFn::kRenderSphere:
case MFn::kRenderBox:
case MFn::kRenderCone:
case MFn::kLocator:
case MFn::kPointLight:
case MFn::kAmbientLight:
case MFn::kDirectionalLight:
case MFn::kSpotLight:
case MFn::kParticle:
case MFn::kLattice:
case MFn::kBaseLattice:
break;

default : break;

}

}

}

{ // output all transforms

 

std::map<std::string,MObject>::iterator it;

it = Transforms.begin();

// loop through each object in the scene

for( ; it != Transforms.end(); ++it ) {

 

MObject obj = it->second);

switch(obj.apiType()) {

 

// output correct node type.
// (you need to write the export funcs!)
case MFn
::kEmitter:
case MFn::kAir:
case MFn::kDrag:
case MFn::kGravity:
case MFn::kNewton:
case MFn::kRadial:
case MFn::kTurbulence:
case MFn::kUniform:
case MFn::kVolumeAxis:
case MFn::kVortex:
break;

default: // just a normal transform...

break;

}

}

}

// All that remains is to output the following:
//
// Skin & Joint Clusters
// Blend Shapes
// Free Form Deformations
// Animation Data
//

// clean all temp data
CleanReferences();

}

 

 

 

 

What Next?

Material Data

Transformation Data

Blend Shape Deformers

Soft Skinned Surfaces

Rigid Skinned Surfaces

Lattice Deformers

index

Rob Bateman [2004]

[HOME] [MEL] [API]