Using OpenMaya for maya object transformations

Work In Progress / 24 June 2020

Been doing some research on methods to apply spatial transformations to objects.
The resulting tool is still work-in-progress but I wanted to get the first version out there and see if people may have suggestions for improvements.

I chose to approach this using OpenMaya for speed reasons, using the cmds library works as well but doesn't allow you to query values using a time context, rather having to set the currentTime which is much, much slower.

time_context = OpenMaya.MDGContext(OpenMaya.MTime(frame))
matrix_object = plug.asMObject(time_context)
fn_matrix_data = OpenMaya.MFnMatrixData(matrix_object)
matrix = fn_matrix_data.matrix()

Another benefit is being able to directly manipulate animation curves, for animated objects.

m_plug = m_dag_node.findPlug(attribute, False)
m_source = m_plug.source()
if m_source.isNull:
    return
m_source_node = m_source.node()
if not m_source_node.apiTypeStr.startswith('kAnimCurve'):
    return
fn_curve = OpenMayaAnim.MFnAnimCurve(m_source_node)
fn_curve.addKey(OpenMaya.MTime(input), value)

The main outstanding issue is the way it handles rotations over the 180 degree threshold, it's internally using matrices so this information gets lost during calculations. I've implemented an experimental flipping filter that works quite well but I'm continuing to do research on how to improve it when I have some time.

Full code available on my github: https://github.com/mvanneutigem/maya_tools

Writing a basic deformer for Maya in python

Tutorial / 26 May 2020

This tutorial functions as a starting point and to give some basic pointers on how to approach writing a deformer for Maya, I've based this on the mnCollisionDeformer I recently wrote, the video will cover the basic idea but will leave room for you to implement your own algorithm.

You can find the deformer template to get started from here: https://github.com/mvn882/tutorials/blob/master/plugins/deformerTemplate.py 

You can start from the template this already has the basic functions and class for the deformer set up.
It starts with the imports, we want to import OpenMaya and OpenMayaMPX from the old python API, the newer API does not support MPxDeformerNode's yet. We also want to import Mel eval, this will be used for evaluating custom attribute editor templates.

import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
from maya.mel import eval as mel_eval

Then we'll set a couple of global variables, these are pre-defined in Maya to access geometry data and will work from 2016 upwards.
Sidenote: the MPxDeformerNode inherits from the MPxGeopmetryFilter base class, hence that's what the variables are called.

kInput = OpenMayaMPx.cvar.MPxGeometryFilter_input
kInputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom
kOutputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom
kEnvelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope
kGroupId = OpenMayaMPx.cvar.MPxGeometryFilter_groupId

Now we'll define the actual class, this will inherit from the MPxDeformerNode as this is a simple deformer.

class deformer(OpenMayaMPx.MPxDeformerNode):
    def __init__(self):
        OpenMayaMPx.MPxDeformerNode.__init__(self)

You will have to set a type id and a type name to be used in the initializePlugin method.

type_id = OpenMaya.MTypeId(0x00001)  
type_name = "deformer"

The initalizePlugin method also needs a creator and an initalize method so we'll define these as well.
The creator should be a clas method as it does not use any instance data and will need to be callable without a class instance, this method will simply return a new instance of the class.

@classmethod
def creator(cls):
    return cls()

The initialize method is where we create all the attributes that we want the deformer to have as inputs and outputs, for a collision deformer you'll want to at least have a collider mesh as an input.

@classmethod
def initialize(cls):
    generic_attr_fn = OpenMaya.MFnGenericAttribute()
    cls.collider_attr = generic_attr_fn.create('collider', 'cl')
    generic_attr_fn.addDataAccept( OpenMaya.MFnData.kMesh )
    cls.addAttribute( cls.collider_attr )

This is also where you would define dependencies between in and outputs, so that when an input is changed the corresponding output will be recomputed.

cls.attributeAffects( cls.collider_attr, kOutputGeom )

Now you can add the initializePlugin and uninitializePlugin methods, outside of the class.
The initializePlugin is simply used to register the plugin in Maya, this is also where you set the author and version of the plugin and load any custom Attribute Editor templates.

def initializePlugin(plugin):
    plugin_fn = OpenMayaMPx.MFnPlugin(plugin, "Marieke van Neutigem", "0.0.1")
    plugin_fn.registerNode(
            deformer.type_name,
            deformer.type_id,
            deformer.creator,
            deformer.initialize,
            OpenMayaMPx.MPxNode.kDeformerNode
        )
    mel_eval( gui_template )

The uninitializePlugin method simply deregisters the plugin using the plugin id when the plugin is unloaded from maya.

def uninitializePlugin(plugin):
    plugin_fn = OpenMayaMPx.MFnPlugin(plugin)
    plugin_fn.deregisterNode(deformer.type_id)

Now you can start implementing the deform method, you could implement the compute method instead as you would on a basic deformer but really I recommend only doing that if the deformation is not a simple per vertex operation and requires you to use compute instead.

The deform method gives you access to the data_block, this can be used to retrieve the values of any of the attributes you added to your plugin. You also have a geometry_iterator, this can be used to iterate over all the vertices of the input geometry and modify them on the fly.
Then there's the local to world space matrix that you can use to transform the vertices from the input mesh to world space for any calculations that require that. And lastly, there is the geometry index which is needed to access to the input geometry from the global geometry filter variables.

def deform(self, data_block, geometry_iterator, local_to_world_matrix, geometry_index):

After you've implemented the deform method there's one last thing; you can add a custom attribute editor template. This is useful if you have added some attributes to your plugin but they don't show the way you would want them to. This is a simple Mel script that needs to be evaluated on loading the plugin, the proc has to be named AE[Name of your deformer]Template.

gui_template = """global proc AEdeformerTemplate( string $nodeName )
    {
        editorTemplate -beginScrollLayout;
            editorTemplate -beginLayout "Deformer Attributes" -collapse 0;
                // Add your own attributes here in the way you want them to be displayed.
            editorTemplate -endLayout;
            AEdependNodeTemplate $nodeName;
            editorTemplate -addExtraControls;
        editorTemplate -endScrollLayout;
    }"""

I hope that get's you a bit of a starting point to start writing your own deformers!

You can find the full source code for the mnCollisionDeformer here for reference: https://github.com/mvanneutigem/tutorials/blob/master/plugins/mnCollisionDeformer.py 

mnCollisionDeformer; a collision deformer for Maya.

Work In Progress / 23 May 2020

A collision deformer I'm writing for learning purposes in my spare time.
Not very performant at the minute as it's written in Python and I came up with my own logic rather than using an established algorithm, this also means it's got some issues with edge-cases, but a very good learning exercise.


I will be releasing the code and a tutorial on how to approach something like this when I find some time for code clean up.

Writing a basic maya plugin in python

Tutorial / 17 May 2020

I've been working from home for the last few weeks and with the extra time on my hands, I figured I'd try my hand at creating a tutorial for one of the things that may seem a bit more daunting to get started on; writing Maya plugins.

This is a very basic tutorial going over how to create a simple plugin and should allow you to take what you need from it to start creating your own plugins.

You can find the code from this tutorial on my Github: https://github.com/mvanneutigem/tutorials/blob/master/plugins/demoNode.py