• Blog
  • Work
  • Resources
    • Rigs
  • Resume
  • Contact
I do technical things...

Calculate a character's visisble radius.

_ I want to write some documents around the maya api. As someone who has no formal training in scripting/programming, learning as I go, the api was (still is really) this misty confusing beast, seemingly something that i could never get my head around. However, recently I've been making progress, starting to get it a little more. By no means am I an expert yet, I'm definitely still learning, but I am getting to grips with it now. So thought I’d sort of document my progress. Sometimes, when im learning, I want someone to really break things down... in other words; lay it out in lay-man's terms, for me - a layman. So that's what i will try and do here. Of course this isn't a basic coding topic, so some python experience is needed, but it should be welcoming to a mel scripter that is unsure on key concepts.

So here's a really basic script that I recently wrote, that I thought could be a good one to break down and go into detail on. It should outline some of the really key fundamental concepts of using python and the api.

So, the issue. Essentially I had to calculate the largest sphere radius of a character (well all our characters) including all their animations. Basically define the largest draw sphere radius the character could possibly need. So, instead of using python commands to query vert positions, I used some Api calls to iterate over the mesh data. The time savings were pretty big, around 10x...

The code:



_import maya.OpenMaya as OpenMaya
import maya.mel as mel
import pymel.core as pm
import os

def getMeshPositions( ):

    # get selection
    selection = OpenMaya.MSelectionList()
    OpenMaya.MGlobal.getActiveSelectionList( selection );
   
    # create an iterator for selection... we use the constant tyer id MFn.kGeometric to filter anything but poly mesh objects
    selIter = OpenMaya.MItSelectionList ( selection, OpenMaya.MFn.kGeometric );
   
    # intiialize a list in this scope... to return
    distanceArr = []
   
    # iterate/loop over the selection
    while not selIter.isDone():

        # intialize a MObject class type
        mObj = OpenMaya.MObject()
        # convert the emtpy MObject into the actual scene node
        selIter.getDependNode( mObj )
       
        # create another iterator for the mesh vertecies
        meshVertIT = OpenMaya.MItMeshVertex ( mObj )
       
        # iterate/loop over the vertecies
        while not meshVertIT.isDone():
            # get the current vert index
            indx = meshVertIT.index()
           
            # get the position of the current vert
            pos = meshVertIT.position (OpenMaya.MSpace.kObject)       
           
            # Store the position in the list to return later
            distanceArr.append( [pos.x, pos.y, pos.z]  )         
           
            # move iterator to next vert on mesh
            meshVertIT.next()
           
        # move iterator to next item in MSelectionList
        selIter.next()
       
    return distanceArr

_ The code above isn’t the actual code i used, i just wrapped up the necessary bits for this document.

So let's go over this in detail and clear up, as best we can, what's going on.

Firs we get the current selection list.... but it's not really a list... well it is.
    selection = OpenMaya.MSelectionList()
    OpenMaya.MGlobal.getActiveSelectionList( selection );
As you can see this takes two lines in the api, the reason for this is one of the key concepts of object oriented programming. Essentially we create a special class type MSelectionList which is a blank/empty version of that class. Then we pass that instantiation to a class method/function that will populate the 'list' in it's place. This is refereed to, in languages that allow this, as 'passing by reference', which means you can pass an argument to a function that is the memory address of the data. So when we pass selection to the MGlobal.getActiveSelectionList, anything done to selection in the getActiveSelectionList is reflected in the selection variable outside. You don't need to return the value and re-cast it.
That's a pretty basic interpretation of it, but it should serve our perposes in this example, but I would definitely recommend googling it and learning up.
   
So now what? we have this strange class type, selection, that we don't really know that much about. We'll, actually we do know one key bit of information. We know it's a 'list' type of class, or its supposed to act like a list. If you know your python iterators, if not again I’d recommend some googling, then we can assume the good people at autodesk have implemented this class as an iterative type.
What is an iterator? it just means it is an 'object' that we can loop over, that some key data inside the class instantiation is organized/treated like a list. In python you can make any class definition an iterative type by some function overloading. There are two ways, but the way this one is made is it has an MSelectionList.__iter__() method and a MSelectionList.next() method defined in the class definition for MSelectionList. This is the base implementation for making a custom class an 'iterable' object. Then we can call another python built-in function, iter() on that class, which returns an 'iterator' object. This object can then be used like a list in a for loop. Of course the api has their own classes to initialize an iterator, so we don't use python's iter() here, we do:
    selIter = OpenMaya.MItSelectionList ( selection, OpenMaya.MFn.kGeometric );
We know we are calling an api iterator type by the MIt prefix on the method call. So the selIter variableis now an object that we can loop over. We also pass in a filter definition OpenMaya.MFn.kGeometric which is a pre-defined constant that MItSelectionList understands. It just means only add to the iterator you are returning geometry type objects.

We now have the selection pretty much done, all we need to do now is iterate over that data. We simply do:

    while not selIter.isDone():
        ... some code
        selIter.next()

Anyone familiar with loops should feel pretty comfortable here. The main difference is the selIter.next(). As the 'thing' we are iterating over is an object, we need to advance the 'index' or item. To Do this we a method of that class, CLASS.next() needs to be implemented, this tells the class 'when i"m being itereated on get the "next" item'. In this case its the .next() gets the next object in the selected list.

Now we are in the loop, iterating over each selected mesh, we get into more of the 'guts' of the code. Here we actually getting into dealing with nodes and mesh functionality.

The next two lines get the currnet item, as a MObject() object type. Again we're creating an empty intialization of the MObject() class and then passing it to a function to populate it, as we did above. This time, however, we pass it to a method of the current item in the iterator

        mObj = OpenMaya.MObject()
        selIter.getDependNode( mObj )

Ok, so what is a MObject and why do we need it? The MObject is the api type for any node/attribute/plugin. However when we 'do' anything to an object in the api, we dont use an MObject, we pass it to other classes or functions - and tell the those to use this MObject. Why do we use an MObject?. well it's what the next iterator type needs, the MItMeshVertex(). A quick look at the api docs for mesh iterators (anything with MIt and Mesh in its name ;)) we find this 
    
    http://download.autodesk.com/us/maya/2009help/API/class_m_it_mesh_vertex.html

and we look at one of the C++ constructors  
    MItMeshVertex (MObject &polyObject, MStatus *ReturnStatus=NULL) 

You dont need to be a C++ guru (which i am not) to kind of get what's going on here. When we intialize this class it takes an MObject, then it retreives all the mesh vertex data of that object. I could be my pretty poor description of events, but it's a little confusing right?

Why use this weird MObject type? Why not be able to pass the current selIter to it? Because an MObject is a 'handle' to the internal memory adress of the actual node/attribute/whatever in the core of maya. It is like shapeshifting variable type. We say to ourselves 'i need to use a maya obejct or attribute now', so we create an MObject, we need to then 'transform' it to the thing we want. This is usually done my a function set or other api class. So here we are getting the current iterator item as an MObject Type, then very shortly we will