DCC Agnostic Tool Development

I’ve put together some boilerplate code for writing DCC agnostic tools. This not only allows for artists to work in their software of choice, but it also reduces the amount of tool support needed since it allows for code to be shared. The key is to utilize as much of the Python standard library as possible and reduce calls to DCC specific libraries like maya.cmds or bpy. Hoping to wrap this in a PySide widget in the future.

Github Repository

What's Cookin'?

It’s been a while since my last update, so I wanted to share what I’ve been doing these days.

Game Jam:

About a week before the COVID-19 Stay at Home order, I participated in a Game Jam with some friends. The theme was ‘Cycles‘, so we made a game about a hamster breaking the cycle of running on its wheel day in and day out. I was responsible for modeling, rigging and animating the characters as well as voicing the narrator. I also helped setup the ragdoll system with another programmer so that the bone colliders interacted with the inside of the hamster ball. It was more difficult than we first thought it would be :)

Royalty Free Music from Bensound

Rigging Dojo:

I’ve also been learning Blender in my free time to see how the new 2.8 update is. To help bolster my Blender knowledge, I signed up for Rigging Dojo’s On Demand class that covers motion editing in Blender and compares it to Maya/MotionBuilder pipelines. It’s been great seeing the workflows and best practices demonstrated in the class. Class details can be found here: https://ondemand.riggingdojo.com/BlenderForMotionEditing


IK FK Utils v1.0

I recently finished up the first iteration of IK FK Utils. This tool allows you to add IK FK matching to almost any standard rig in Maya. Here is a video showing how the tool works on a sample of mocap data.

Next step if to document my code and release it for public use. I will also follow up with a video on the setup portion of this tool. It will show you how to create a template file for the rigs you commonly use.

Special Thanks:

Ramtin Ahmadi for the Moom rig.

Ugur Ulvi Yetiskin for the Bony rig.

Animation Studios for the Advanced Skeleton rig. I’d like to point out that Advanced Skeleton has this functionality already. I was just using the rig as another test case.

Zeth Willie for his series on IK FK matching. His use of message attributes works well for this process so I implemented a similar system into this tool.

Scale Animation from Maya to Unity

A while ago a friend of mine was working on a game in Unity that featured a creature with slinky like arms that would squash and stretch by animating scale on the arm joints. He was having an issue exporting scale animation on the arms into Unity, so I wrote a script to convert the scale animation to translation animation which Unity can read in.

Conditions:

  • The script assumes the joints scale along their X axis only. ( This can be changed if your joints use Z or Y as their twist axis )

How To Use It:

  1. Copy this python script into Maya’s script directory ( C:\Users\[ Your Name ]\Documents\maya\scripts )

  2. Increment and Save your animation scene as this will bake your animation to dense keys.

  3. Select the joints with scale animation.

  4. Run the following code in Maya’s script editor:

    1. import scale_to_translate
      joints = maya.cmds.ls( sl = True )
      scale_to_translate.main( joints )

The Code:

"""

Transforms scaleX values to translateX values on a list of keyed joints.

Author: ckurtz35@gmail.com

"""

# Imports
import maya.cmds
import maya.api.OpenMaya


def get_distance( obj_one, obj_two ):
    """

    Gets the distance between two objects. Equivalent to using
    the distance tool in Maya.

    """

    # Get the objects' translation
    trans_one = maya.cmds.xform( obj_one, t = 1, q = 1 )
    trans_two = maya.cmds.xform( obj_two, t = 1, q = 1 )

    # Convert the translation into MVector objects.
    vector_one = maya.api.OpenMaya.MVector( trans_one )
    vector_two = maya.api.OpenMaya.MVector( trans_two )

    # Subtracting the two vectors gives us a new one that
    # describes the distance between the two.
    new_vec = vector_two - vector_one

    # Taking the length of the vector gives us the
    # actual distance
    distance = new_vec.length( )

    return distance


def main( joints ):
    """

    Transforms scaleX values into translateX values on a set of joints.

    """

    # Preventing the UI from refreshing
    # speeds things up.
    maya.cmds.refresh( suspend = True )

    # Locators are used to get the distance
    # between joints later on.
    loc_one = maya.cmds.spaceLocator( )
    loc_two = maya.cmds.spaceLocator( )

    for jnt in joints:

        # The child joint position determines the length of the current joint.
        child_jnt = maya.cmds.listRelatives( jnt, children = 1, type = "joint" )
        if not child_jnt:
            continue

        child_jnt = child_jnt[ 0 ]

        # Constrain the locators to the two joints.
        constraint_one = maya.cmds.pointConstraint( jnt, loc_one, mo = False )
        constraint_two = maya.cmds.pointConstraint( child_jnt, loc_two, mo = False )

        # We need to convert the scale value for each keyframe.
        # CHANGE AXIS HERE IF NEEDED.
        key_frames = maya.cmds.keyframe( "{0}.scaleX".format( jnt ), query = 1 )
        if not key_frames:
            continue

        for k in key_frames:

            maya.cmds.currentTime( k, edit = True )

            # Getting the distance before and after we set the
            # scale to 1 will give us the distance delta ( difference )
            distance = get_distance( loc_one, loc_two )
            maya.cmds.xform( jnt, s = [ 1.0, 1.0, 1.0 ] )
            
            # CHANGE AXIS HERE IF NEEDED.
            maya.cmds.setKeyframe( jnt, at = "scaleX", time = k )
            new_distance = get_distance( loc_one, loc_two )

            delta = distance - new_distance
            
            # CHANGE AXIS HERE IF NEEDED.
            child_len = maya.cmds.getAttr( "{0}.translateX".format( child_jnt ) )

            # The distance delta is added to the child's translateX to get
            # the correct length for our current joint.
            if child_len < 0:
                new_len  = child_len - delta
            else:
                new_len = child_len + delta
            
            # CHANGE AXIS HERE IF NEEDED.
            maya.cmds.setAttr( "{0}.translateX".format( child_jnt ), new_len )
            # CHANGE AXIS HERE IF NEEDED.
            maya.cmds.setKeyframe( child_jnt, at = "translateX", time = k )

        maya.cmds.delete( constraint_one )
        maya.cmds.delete( constraint_two )

    maya.cmds.delete( loc_one )
    maya.cmds.delete( loc_two )

    maya.cmds.refresh( suspend = False )

get_dagpath_from_name( node_name )

This is the first post of it’s kind where I will post a useful function that can be used to build up your library.

I find myself bouncing between maya.cmds and maya.api.OpenMaya quite a bit, and have run into the issue of converting string names objects that can be used with OpenMaya operations. Here is a simple function that will get a maya.api.OpenMaya.MDagPath object from the given node name.

import maya.api.OpenMaya
import maya.cmds

def get_dagpath_from_name( node_name ):
    """
    Gets a node's MDagPath object from it's name.
    
    args:
    
    node_name: string | The name of the node.
    
    kwargs:
    
    return:
    
    MDagPath if successful, None otherwise.
    
    """
    
    selection_list = OpenMaya.MSelectionList( )
    
    if not maya.cmds.objExists( node_name ):
        return None
    
    selection_list.add( node_name )
    
    dag_path = selection_list.getDagPath( 0 )
    
    return dag_path

Motionbuilder 2018 Crash On Close

Recently I found time to do some Mobu quality of life work, and addressed a problem where one of our PySide tools was causing Mobu 2018 to crash on close. Whenever an animator or I closed Mobu with this tool open, the close dialog would hang at ‘Closing User Interface‘, and then crash without providing a log. I initially thought there was a stale callback in the scene that the tool didn’t clean up, but commenting out all of the callbacks that the tool created didn’t work. My debugger wasn’t catching the error either, so I really had to go through each step one-by-one to find the cause. I started by opening the tool and closing Mobu. Mobu closed just fine, so I then would open the tool, perform an operation in the tool, and close Mobu. Doing this a few times with different operations, I found that the problem was being caused by a QMenu containing embedded sub-menus was not cleaning itself up after it was created.

Before the fix:

# pseudocode
my_menu = QtGui.QMenu( 'my_menu', self.view )
my_menu.exec_( self.view.mapToGlobal( QPoint ) ) # Open menu

After the fix:

# pseudocode
my_menu = QtGui.QMenu( 'my_menu', self.view )
my_menu.exec_( self.view.mapToGlobal( QPoint ) ) # Open menu
my_menu.destroy( ) # Clean up the menu.

The only difference in these two blocks is the line ‘my_menu.destroy( )‘. The destroy( ) method cleaned up the QMenu and it’s sub-menus allowing Mobu to close properly. I believe you can also use QObject.deleteLater( ) to clean up menus after execution, but I’ll have to put that in practice to see the results.

Hope this helps!

IK FK Utils 01

ik_fk_utils_03.13.2019.jpg

Lately I have been writing a tool which allows an animator to add IK/FK matching and baking to almost any control rig. This was prompted by working with some control rigs that did not have matching built-in or didn’t have the matching functionality I wanted by default.

My intentions are to have this tool work with any limb on a control rig, but you could technically use this on two plain old joint chains where one of the chains has an IK control. Template files can also be saved out and applied for each type of rig the user uses.

I’m at a point right now where I am just downloading a bunch of rigs to try it on. Of course the second rig I downloaded arose an issue with matching rotations of controls that have different orientations, but a fix for that has been going pretty well.