Monthly Archives: March 2011

Create Transform and Select By Name

This is a bit of Max Script I have encountered. First how to create a transform node in Max? In Maya, an empty group and a transform node are the same thing. They define the transformation matrix about where to put stuff, and they are not renderable. In Max, I find Dummy, which acts quite like Maya transform. It can be create by Max Script:

dummy name:"some_name"

or with some initial position:

dummy name:"some_name" pos:[3,4,5]

But Dummy is not just a transformation matrix. It also has a box visualization can be directly picked up in the viewport. In fact, most objects in Max has transformation matrix, even a mesh object can be transformed on its own. Objects can be attached to a Dummy. When you move the Dummy, you move everything under it. So it acts like a Group, but a Dummy is not a Group. They show up differently in scene explorer. I still don’t know how to create an empty Group in Max, with or without Max Script:(

So how to select something by name?

bob = getNodeByName "some_name" 
if bob != undefined then 
( 
select bob
)

A strange, and maybe amazing, thing about Max is multiple objects in the same place can have the same name. Means there can be two sphere01 under group01, which is not possible in Maya. I am not sure what is the benefit or trouble about this feature. Better to avoid it anyway.

Advertisements

Leave a comment

Filed under Max Script

Extracting Mesh

This is a quick note about how to extract polygonal mesh from an object in Maya API and Max SDK.

In Maya, given the MObject of something, first we check if it is a mesh by MObject::hasFn(), if it returns true, create an MFnMesh based on this MObject. i.e.

if(some_obj.hasFn(MFn::kMesh))
{
MFnMesh fmesh(some_obj);
// do something to the fmesh
}

MFnMesh is the mesh function set can do almost anything to the mesh. For example:

int n_poly = fmesh.numPolygons();
 int n_vert = fmesh.numVertices();

tells us how many faces or vertices the mesh has.

In Max, there are two kinds of mesh object: TriObject is strictly triangular mesh, and PolyObject is polygonal mesh. Once you have the INode, you must check if it can be converted to a specific kind of mesh object. If it is convertible, the actual mesh function and data can be accessed as a specific member belongs to the object. i.e.

char retrievePolyFrom(INode * node, MNMesh& out_mesh)
{
 Object *cvtobj = node->EvalWorldState(0).obj;

 if (cvtobj->CanConvertToType(Class_ID(POLYOBJ_CLASS_ID, 0)))
 {
 PolyObject *poly = (PolyObject *)cvtobj->ConvertToType(0, Class_ID(POLYOBJ_CLASS_ID, 0));

 out_mesh = poly->mm;
 return 1;
 }
 return 0;
}

This only works with (polygonal) MNMesh, which is PolyObject::mm. The (triangular) Mesh should be TriObject::mesh.

Now you can use the function set to retrieve or modify the mesh data. i.e.

int face_count = poly_mesh.F(0)->deg;

This returns how many vertices the fist face of the polygonal mesh has.

Leave a comment

Filed under Max SDK, Maya API

Regex Search Basic

Here is the problem: we are looking for Date within some log text. Cannot use string::find() because we don’t know exactly what the data is. We only know the pattern of the string of Data: MM-DD-YYYY, month and day can have one or two digits, year has four, and they are separated by two hyphens (-), i.e. 1-31-2010, 12-1-2011.

Boost.Regex is useful in this case. regex_search() can search the text base on specific expression. “\\d{1,2}-\\d{1,2}-\\d{4}” means any string that consists of “either 1 or 2 numbers a hyphen either 1 or 2 numbers a hyphen four numbers”. Here is the search:

std::string::const_iterator start, end;
 start = log_text.begin();
 end = log_text.end();
 static const boost::regex expression("\\d{1,2}-\\d{1,2}-\\d{4}");
boost::match_results<std::string::const_iterator> what;
 while(regex_search(start, end, what, expression, boost::match_extra))
 {
cout<<what[0]<<endl;
 start = what[0].second;
 }

Leave a comment

Filed under C++

Finding Materials

This note is about how to find materials in Maya API and Max SDK. In Maya, we use MItDag to walk through all meshes to look for attached materials. All types of geometry, mesh, NURBS surface, SubDiv, could have material, but for simplicity, here only meshes are listed.

MItDag itdag(MItDag::kDepthFirst, MFn::kMesh);
for
(; !itdag.isDone(); itdag.next())
{
MDagPath path;
itdag.getPath(path);

MObject omesh = itdag.currentItem();

// find the material and do something to it

}

There is no direct connection between mesh and material, there are linked up via a Shading Engine (or SG). Find the SG by MItDependencyGraph go downstream:

MObject findShadingEngine(MObject &mesh)
{
MStatus result;
MItDependencyGraph itdep(mesh, MFn::kShadingEngine, MItDependencyGraph::kDownstream, MItDependencyGraph::kDepthFirst, MItDependencyGraph::kNodeLevel, &result);
for(; !itdep.isDone(); itdep.next())
{
return itdep.currentItem();
}
return MObject::kNullObj;
}

Then go upstream from the “surfaceShader” plug of SG to find the material. Only lambert, blinn, and phong is listed here:

MObject findMaterial(MObject &mesh)
{
MObject osg = findShadingEngine(mesh);
if(osg == MObject::kNullObj)
return osg;
MStatus result;
MPlug plgsurface = MFnDependencyNode(osg).findPlug("surfaceShader");
MItDependencyGraph itdep(plgsurface, MFn::kInvalid, MItDependencyGraph::kUpstream, MItDependencyGraph::kDepthFirst, MItDependencyGraph::kNodeLevel, &result);
for(; !itdep.isDone(); itdep.next())
{
MObject ocur = itdep.currentItem();
if(ocur.hasFn(MFn::kLambert) || ocur.hasFn(MFn::kBlinn) || ocur.hasFn(MFn::kPhong))
return ocur;
}
return MObject::kNullObj;
}

Now we have the MObject pointed to the material ready for further operation.

In Max, there is nothing like MItDag, I do it myself:

void lsChildNode( INode *parent, std::vector<INode *>& nodeList)
{
int objectCount = parent->NumberOfChildren();
for(int i = 0; i < objectCount; i++)
{
INode* cur = parent->GetChildNode(i);
nodeList.push_back(cur);
lsChildNode( cur, nodeList);
}
}
void lsAllNodeInScene(std::vector<INode *>& nodeList)
{
Interface* ip = GetCOREInterface();
INode* parent = ip->GetRootNode();
int objectCount = parent->NumberOfChildren();
for(int i = 0; i < objectCount; i++)
{
INode* cur = parent->GetChildNode(i);
nodeList.push_back(cur);
lsChildNode( cur, nodeList);
}
}

It starts with scene root, lists everything under it, generating a long list of INode. Material is accessed directly by INode::GetMtl()

for(int i=0; i < allNode.size(); i++)
{
Mtl* m =  allNode[i]->GetMtl();
if(m) do_something_to(m);
}

Leave a comment

Filed under Max SDK, Maya API

API Animation Curve

This document is about how to create keyframe animation by Maya API and Max SDK.

First, some basic idea. An animation curve can be regarded as Q = f(t), means at any given time t, you can calculate the value of Q by function f. Both Maya and Max uses modified cubic 2 dimensional bezier curve as f. The curve is a number of segments connected by knots call keyframes, or keys. Below is the diagram showing one segment.

Green dotted line is the curve, which is defined by 4 points, red ones are end points, blue ones are control points. You may call those yellow lines handles. Those points live in 2-dimensional space. Actually a key should have one end point and two control points, one for in segment, the other for out. Two keys defines one segment, and a curve should have at least one segment, so at least two keys.

Creating animation curve in Maya is fairly straightforward. You have the MPlug of any keyable attribute, create the curve by MFnAnimCurve::create(MPlug), then add keys by MFnAnimCurve::addKeyframe(MTime, double). So at the end point, Q is the double value, and t is MTime. For each control points, we need tangent type, angle, weight.
Tangent is the handle mentioned before. In the following image:


Tangent angle equals atan(dQ/dt), and tangent weight is length of the handle, which equals sqrt(dQ*dQ + dt*dt).
Set the tangent angle and weight by MFnAnimCurve::setTangent().
Tangent type is changed by MFnAnimCurve::setInTangentType() and MFnAnimCurve::setOutTangentType(). I think MFnAnimCurve::kTangentFixed is the most flexible and useful.
There are two kinds of animation curve, weighted and unweighted. We are talking about weighted version here, so MFnAnimCurve::setIsWeighted(true).
Not yet, tangent handles should be unlocked by calling MFnAnimCurve::setTangentsLocked() and MFnAnimCurve::setWeightsLocked() before those angles and weights can be correctly set.

The Max side of the story is a bit more confusing. In Max, keyframe animation is done via controllers. Controllers are attached to a parameter belongs to a IParamBlock2 of the INode, or just built into the INode. There are a few ways to access the controller, i.e.
node->GetTMController()->GetPositionController()->GetXController();
Once you have the controller, get the keyframe interface by
IKeyControl *ikeys = GetKeyControlInterface(c);

Keyframe is added by IKeyControl::SetKey(). Specific controller requires specific kind of keys. We only talk about IBezFloatKey here.

It has time (t) and val (Q).

Tangent type is set by SetInTanType() and SetOutTanType(). BEZKEY_USER is similar to Maya’s Fixed type.

A Max key has intan, outtan, inLength, and outLength. The value of intan and outtan equals dQ/dt. The value of inLength and outLength equals dt/time_span. The time_span for out tangent of first key is shown below

Remember multiply any time value by TicksPerFrame.

Leave a comment

Filed under Max SDK, Maya API

Finding Stuff by MItDag

This note is about how to find stuff in Maya API.

Sometimes we need to find the specific object in the scene by its name. Here is a very crude way to do that:

MItDag itdag(MItDag::kDepthFirst); 
for(; !itdag.isDone(); itdag.next()) 
{ 
  MFnDagNode pf(itdag.currentItem()); 
  if(pf.fullPathName() == MString(name)) 
  { 
    return itdag.currentItem(); 
  } 
} 
return MObject::kNullObj;

The DAG iterator starts as the world root, the traverses through every available DAG node, comparing the pall path name of the node with given name. It returns the MObject if hits a match. For example: to find sphere1 under group1, which is under group2, we should give “|group2|group1|sphere1” to match.

It is quite a wasteful way to find a node in case there are lots of them out there, traversing to right end of them can take some time. I think the searching process can be benefit from the hierarchical nature of the notes. In other words: if |group2 does not exist, |group2|group1 and |group2|group1|sphere1, or everything possibly under |group2 won’t exist either. So we search |group2 first. If found, reset the iterator to |group2, looking for |group2|group1. If found, reset to where we found and search for the next one under it. Anytime the search returns nothing, time to abort. The improved method is:

char findChildByName(MObject &parent, MObject &result, std::string &name)
{
 MItDag itdag(MItDag::kBreadthFirst);
 
 if(parent != MObject::kNullObj)
   itdag.reset(parent);
 
 for(; !itdag.isDone(); itdag.next())
 {
   MFnDagNode pf(itdag.currentItem());
   if(pf.fullPathName() == MString(name.c_str())) 
   {
     result = itdag.currentItem();
     return 1;
   }
 }
 result = MObject::kNullObj;
 return 0;
}

char AHelper::getObjByFullName(const char* name, MObject& res)
{
 std::string r;
 std::string str = name;
 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
 boost::char_separator<char> sep("|");
 tokenizer tokens(str, sep);
 MObject parent = MObject::kNullObj;
 for (tokenizer::iterator tok_iter = tokens.begin();
 tok_iter != tokens.end(); ++tok_iter)
 {
   r = r + "|" + (*tok_iter);
   if(!findChildByName( parent, res, r))
   return 0;
 
   parent = res;
 }
 return 1;
}

The traversal type of the iterator is set to breadth-first, so it will jump to next sibling instead of wasting time on the children of an unmatched node.

Leave a comment

Filed under Maya API