RBF Solver for Quaternions Interpolation in Maya

Recently I came across this paper:
RBF Solver for Quaternions Interpolation by: Rinaldi FabioDolci Daniele

Using their pseudocode:

SamplesPositions = LIST ()
SampleValues = LIST ()
MatrixT = Creatematrixfromlist(SamplesPositions)
MatrixS = CreatematrixfromList(SampleValues)
Epsilon = INT ()
MatrixD = ComputeDistanceMatrix(Matrix T)
Kernel = CreateKernel(SQUARED(MatrixD), Epsilon)
Invertedkernel = Kernel.Interpolate(INTERPOLATION TYPE).Invert()
WeightsMatrix = Invertedkernel∗MatrixS
CurrentPosition = MATRIX()
CurrentDistanceMatrix = ComputeDistanceMatrix(CurrentPosition)
Result = CurrentDistanceMatrix∗WeightsMatrix

We can build a simple MPxNode to interpolate Quaternions using Eigen. In the .gif below we have a simple scene to illustrate what we can do with this solver. We have 9 poses as inputs, and 9 rotations as inputValues, solve for the weights, and use a vector of the locator’s position as the driver as the current position:

In the next .gif, we took a jaw joint driven by an expression, and trained the RBF with Jaw controller as the driver, and the jaw joint as the driven. With 8 poses we have a good interpolating jaw joint.

This technique combined with more control on the input of the data type, we are able to create a pretty good RBF solver to handle almost anything we would like to drive.


Pro-Tip:

Eigen doesn’t seem to have built-in exp/log methods for quaternions, here is some code that can help with that:
Lionel Heng on github:

#ifndef QUATERNIONMAPPING_H
#define QUATERNIONMAPPING_H

namespace px
{
	double sinc(const double x)
	{
		if (x == 0)
			return 1;
		return sin(x) / x;
	}

	template<typename T>
	Eigen::Quaternion<T> expq(const Eigen::Quaternion<T>& q)
	{
		T a = q.vec().norm();
		T exp_w = exp(q.w());

		if (a == T(0))
		{
			return Eigen::Quaternion<T>(exp_w, 0, 0, 0);
		}

		Eigen::Quaternion<T> res;
		res.w() = exp_w * T(cos(a));
		res.vec() = exp_w * T(sinc(a)) * q.vec();

		return res;
	}

	template<typename T>
	Eigen::Quaternion<T> logq(const Eigen::Quaternion<T>& q)
	{
		T exp_w = q.norm();
		T w = log(exp_w);
		T a = acos(q.w() / exp_w);

		if (a == T(0))
		{
			return Eigen::Quaternion<T>(w, T(0), T(0), T(0));
		}

		Eigen::Quaternion<T> res;
		res.w() = w;
		res.vec() = q.vec() / exp_w / (sin(a) / a);

		return res;
	}

}

#endif

~Cheers!

Working with Maya’s python api – The compound attribute

Continuing my explanation of maya’s python api, we come to the compound attribute.

This is a handy way to organize our arrays.

First we create our MFnCompoundAttribute class:

cAttr = OpenMaya.MFnCompoundAttribute()

Then we create an input array(we are using matrix as input) so let’s create that class.

mAttr = OpenMaya.MFnMatrixAttribute()

Then create our matrix array attribute that will be a child of our compound attribute:

testNode.inputs = mAttr.create('inputs', 'inputs')
mAttr.setKeyable(1)
mAttr.setArray(1)
testNode.addAttribute(testNode.inputs)

Next, we can create our ‘parent’ compound attribute:

testNode.poseList = cAttr.create("inputList", "inputList")
cAttr.addChild(rbfAttrNode.inputs) #Here is where we state the child 
cAttr.setArray(1)
cAttr.setHidden(1)
testNode.addAttribute(testNode.inputList)

As you can see, we created a compound attribute with our inputs array set to be the child.

This creates a hierarchy that would make sense:

inputList - Our compound attribute
    -inputList[0] - The first entry in the compound attribute array
        -inputs -The first entry in out matrix array
            -inputs[0] - matrix input 1
            -inputs[1]- matrix input 2
    -inputList[1] - The second entry in the compound attribute array
         -inputs -The second entry in out matrix array
             -inputs[0] - matrix input 1
             -inputs[1] - matrix input 2

Next we will look at how to get the data from each plug when we need it:

-Note, I found this very confusing when I first tried to do this correctly. Explanations are in the code as comments:

#First we get our compound array dataBlock
inputListArrayDataHandle = dataBlock.inputArrayValue(testNode.inputList)
#Get the amount of children arrays - this returns an int
index = inputListArrayDataHandle.elementCount()
#This is how I got the data I wanted, this can change based on what you need

#Create empty list
inputData = []
#Loop through the children arrays of the compound attribute
for i in range(index):
    #Go to the first child in the array
    inputListArrayDataHandle.jumpToElement(i)

    #(This is the first entry array(inputList[0] in the example above))
    inputsDataHandle = inputListArrayDataHandle.inputValue()

    # Get the child array attribute(The first entry in the children of the child array)
    # (This is the inputs in the example above)
    inputDataHandle = OpenMaya.MArrayDataHandle(inputsDataHandle .child(testNode.inputs))

    #Get the number of children in the child array
    index2 = attributeDataHandle.elementCount()
    # Create an empty to list 
    data = []
    #Loop through the number of matrix in the inputs array
    #These are the inputs[0], inputs[1]....inputs[n] in the example above
    for j in range(index2):
        #I created another emtpy list to keep track of things
        #I made this list set at 3 entries as I only wanted the transform data
        out = [None, None, None]
        #Go to the first entry in the inputs array
        inputDataHandle .jumpToElement(j)
        #Get the data as an MMatrix class
        mMatrix = om.MMatrix(inputDataHandle .inputValue().asMatrix())
        #Create a MTransformationMatrix class with the MMatrix variable
        mTrans = om.MTransformationMatrix(mMatrix)
        #Grab the wold position data
        trs = mTrans.translation(1)
        #Optional data that can be pulled from the matrix is(Not used in this example)
        #quat = mTrans.rotation(1)
        #axis, angle = quat.asAxisAngle()

        # Set out array values (xyz, xyz, angle)
        out[0] = trs[0]
        out[1] = trs[1]
        out[2] = trs[2]
        if out: #Check that there is data to fill out list
            for o in out:
                data.append(o)
    #Fill the top level list with the list
    inputData.append(data)

With the above example we end up with a list of arrays:
inputData = [[0,0,0], [0,0,0], [0,0,0], [0,0,0], [0,0,0]]

I hope you find this helpful.

Working with Maya’s python api – The enum attribute

Here is some information on creating a enum attribute in maya python api.

 

Let’s start on creating an enum attribute that will work like an optionMenu.

First we create our enum class:

eAttr = OpenMaya.MFnEnumAttribute()

Then we can add our attribute to the class:

testNode.optionMenu= eAttr.create('options', 'options', 0)

Next we can add some ‘fields’ to the attribute. This is where we add the options to our optionMenu:

eAttr.addField('option1', 0)
eAttr.addField('option2', 1)
eAttr.addField('option3', 2)
eAttr.addField('option4', 3)
eAttr.addField('option5', 4)

There,  now we have 5 options available to choose from. When queried, we will get an int in return.

Next let’s set up some options in the MFnEnumAttribute class:

eAttr.setKeyable(1)
eAttr.setStorable(1)

We want to be able to key the option we want, and we want to be able to have current option save to the file.

Last, we need to add our newly created attribute to the node we are working on:

testNode.addAttribute(testNode.optionMenu)

The whole code for this enum attribute:

eAttr = OpenMaya.MFnEnumAttribute()

testNode.optionMenu= eAttr.create('options', 'options', 0)
eAttr.addField('option1', 0)
eAttr.addField('option2', 1)
eAttr.addField('option3', 2)
eAttr.addField('option4', 3)
eAttr.addField('option5', 4)
eAttr.setKeyable(1)
eAttr.setStorable(1)
testNode.addAttribute(testNode.optionMenu)

 

Next, lets look at how to get at our optionMenu when we need to:

When we want to get at the data, we need to create a dataBlock:

optionMenu_value = dataBlock.inputValue(testNode.optionMenu)

The variable ‘optionMenu_value’ now contains the data we want. We can use it like this:

if optionMenu_value.asInt() == 0:
    runThisCode()
if optionMenu_value.asInt() == 1:
    runThisCode()
if optionMenu_value.asInt() == 2:
    runThisCode()

etc.

This is an easy way to add a little option box to our plugin.