top of page
  • Mason Smigel

This post will outline the process I used to build a post-rig deformation system for better shaping of characters.

The goal of this system is to provide higher control over the shape and silhouette of characters, after the main pose. I saw a similar system from a paper written at Dreamworks called the "Contour Cage." I wanted to try to create something similar within Maya.



GOALS

Deformations like this could pretty easily be achieved by creating a local rig and piping the deformation into the render mesh via a blendshape, however, a solution like this would be rather expensive, and unable to take advantage of Maya's parallel evaluation.

With that in mind, I had a couple of main goals when developing this system:

1. The setup itself should be fast and fully optimized for parallel evaluation

2. To speed up the creation and maintain editability the only input would be a poly mesh. We can build the controls and skinning from this!

3. The system should be flexible enough to add onto most rigs



Overview

To give a rough overview of the system works:

  1. First model an input mesh, we can skin this mesh and use it to drive a series of controls and bind joints.

  2. The bind joints will be skinned to a duplicate of the mesh, and using the bindPreMatrix attribute of the joints deform the skin based on the offset between the bpm(bindPreMatrix) joint and the bind joint.

  3. This skin cluster can finally be stacked on top of the existing skinCluster to produce the final deformation.


Data flow overview


The Input mesh

The control points for the rig are built from an input mesh called the cage mesh. This is a manually modeled mesh that fits the shape of the character, with resolution around the areas you want to control. Of course, there is a trade-off with the number of vertices and the speed of the rig later on so it's helpful to keep this mesh really low. I found that using N-gons can be a great trick to adding a bit more resolution where you need it (similar to the profile curves used at Pixar).


Deform Cage geometry for Diagoro


Later on, we will use the cage to create and drive the control points for the rig, but to make the system more intuitive it would be helpful to have them follow along with the rig. For this, we can skin the deformCage to the bind joints of the main rig. An important note here is that each vertex should maintain a maximum influence of 2. This will important later when we drive the controls from the cage.




The control Cage

The control cage will be used to drive the final deformation. Each control point has a small hierarchy consisting of an offset, control as well as two joints, a bind and a bpm (bindPreMatrix), these will come into play later!

Control Hierarchy for each control point


We can then pretty easily create a series of control points based on the vertex position and orient them to the vertex normals.


Now comes the fun part! Since we only skinned each vertex to a maximum of two joints we can now get the influences of the vertex and create a blended matrix constraint between the two influences with weights based on the weights from our skin cluster.


Each control point is weighted based on the skin cluster weights


Notice the skinCluster influence joints are connected as inputs to the blend matrix node, and the skin weight is used as a blend between the influences. Now when the rig is deformed the controls "stick" to the vertices, but they have a completely independent DG graph from the geometry!


While it may look like these controls are connected to the verts, they're only driven by their offset parent matrix.



One final thing I added to the controls was a way to display the connectivity of vertices, without this the controls tend to get lost and can be confusing as to what part of the body they control.

Even Daigoro doesn't know what these controls do!


For this, we can create a new NURBS curve with CVs connected to each control point of the rig. The result is a bunch of NURBS control points curves with connections to the controlPoint.position of the curve. Each Control point of the curve follows the appropriate rig Control, resulting in something that looks a bit like a lattice.


Complete connectivity display for Diagoro



Skinning and connecting

Finally, we need to connect our control rig points to the actual mesh. There are really two major parts to this step, skinning the deformation cage and connecting that deformation to the existing skinning.


Luckily we modeled a super nice low poly proxy earlier that we can also use for skinning the deformation cage. By smoothing it a couple of times and skinning it to the control cage joints we can get some really nice and smooth skinning.

Output high res-skinning proxy

We can now copy the skinning from the high res proxy geometry we created to the final mesh. Here the bpm joints we created earlier become super important. If we look back at the hierarchy they are parented under the offset but not the control. That means they follow along with the matrix connection to the bind joints, but not when we move the control.


If we connect the worldInverseMatrix of the bpm joints to the BindPreMatrix attribute of the skin cluster (following the order in which the influences were connected) we can essentially offset the 'bind pose' of the joints.


Example bpm matrix connections. For a real skin cluster you'd have tons of inputs so its easier to script this.


Connecting the bpm joints makes the deformation relative to the distance between the start(bpm) and end (bind). I like to think about this as converting the deformation from being based on a point, to a vector.


By repeating this setup on the skinned mesh we get a result that looks like this:

Deform cage skinCluster is calculated only from relative offsets. so it looks 'disconnected' from the main rig.


While this may look a bit awkward on its own, once this skin cluster is stacked on top of the existing skinning it works really nicely with the existing deformations. (for more information about stacking skin clusters check out Charles Waldraws article and course on rigging Dojo)



If you want to use this in your own projects or just dig into the code this is all written into my larger rigging system Rigamajig2 which you can check out here!

  • Mason Smigel

Updated: Mar 22, 2022

Setting up an organized project structure is essential for maintaining and collaborating on large-scale projects. RIGAMJIG2 is designed to be simple, flexible, and logical so it makes sense to not only me but other TDs.


Maya environment

I used Maya's Module system to keep track of data within Maya's environment variables. This allows me to use a single .mod file to add all the icons, scripts, and plugins into Maya's environment variables on startup.


Using modules also makes it simple to install rigamajig2. Using the techniques from Christian Akesson's tutorial I wrote a simple drag and drop installer to create the .mod file and load in the scripts and plugins.


""" Drag and Drop installer for rigamajig2"""
import os
import sys

import pymel.util
import maya.cmds as cmds

def onMayaDroppedPythonFile(*args):
    # Get the location of the installer
    installer_path = __file__.replace('\\', '/')

    # from the installer path build other important paths.
    module_root = os.path.dirname(installer_path)
    python_path = os.path.join(module_root, 'scripts')
    plugin_path = os.path.join(module_root, 'plug-ins')
    lib_path = os.path.join(module_root, 'scripts', 'lib')

    # Check if the modules directory exists in the user preference directory (if it doesn't, create it)
    maya_moddir_path = '{}/modules'.format(pymel.util.getEnv('MAYA_APP_DIR'))
    if not os.path.exists(maya_moddir_path):
        os.makedirs(maya_moddir_path)

    # Define the module file path
    maya_mod_file = '{}/rigamajig2.mod'.format(maya_moddir_path)

    # Write our module file
    with open(maya_mod_file, 'w') as moduleFile:

        output = '+ rigamajig2 1.0 {}'.format(module_root)
        output += '\r\nPYTHONPATH += {}'.format(lib_path)
        # Add the path to plugin path on first use
        if plugin_path not in pymel.util.getEnv("MAYA_PLUG_IN_PATH"):
            pymel.util.putEnv("MAYA_PLUG_IN_PATH", [pymel.util.getEnv("MAYA_PLUG_IN_PATH"), plugin_path])

        moduleFile.write(output)

    # add the python path on first use
    if python_path not in sys.path:
        sys.path.append(python_path)
    if lib_path not in sys.path:
        sys.path.append(lib_path)

Project structure

rigamajig2/
|-- archetypes/
|   |-- base/
|   |-- biped/
|   |-- quadruped/
|-- bin/
|-- icons/
|-- misc/
|-- plug-ins/
|-- scripts/
|   |-- lib/
|   |-- rigamajig2/
|   |   |-- maya/
|   |   |   |-- anim/
|   |   |   |-- cmpts/
|   |   |   |-- data/
|   |   |   |-- rig/
|   |   |   |-- test/
|   |   |-- shared/
|   |   |-- ui/
|-- tests/
|-- .gitignore
|-- drag_into_maya.py
|-- README.md

archetypes: rig archetypes or presets. Each archetype contains data to act as a starting point when constructing a rig.


bin: executable files. This should be added to a system path to run from the terminal.


icons: icons used within UI or shelves. This is added to the XBMLANGPATH environment variable.


misc: random assets or files. (ex. poly modeled axis marker file)


plug-ins: plugins. This is added to the MAYA_PLUG_IN_PATH environment variable.


scripts: python and Mel scripts. This is added to the PYTHONPATH and MAYA_SCRIPT_PATH environment variables.


lib: external python libraries. For this project we only need NumPy.


rigamajig2: python module for rigamajig2


maya: scripts for Maya. At the root level, the scripts should be largely independent of other modules and are combined together later.

(ex. matrix constrain, match transform, create nodes, cleanup mesh).


anim: scripts for animation. (ex. mirror pose, ikfk matching)


cmpts: components used in rig build. Components are python classes with specific methods and parameters used in the rig builder.

(ex. arm, leg, cog, lookAt)


data: scripts to manage saving and loading data from Maya to JSON.

(ex. hierarchy data, skin data, joint data, blendshape data, curve data)


rig: scripts for building rigs, but not whole components. (ex. controls, rig builder, ikfk setups, ik spline setup, space switching)


test: contains Maya unit test runner


shared: scripts not related to Maya or another DCC.


ui: python scripts for UIs and custom PySide widgets


tests: python files for unit testing. All files here are run when using the 'runmayaunittest' executable.


.gitignore: git file to specify files not under VCS


drag_into_maya.py: drag and drop installer


README.md: markdown document containing basic documentation about rigamajig2

  • Mason Smigel

Updated: Mar 23, 2022

When working on rigamajig2 setting up an efficient development environment was crucial for maintaining and organizing code. In the past I have primarily relied on the Maya script editor or Chris Zurbriggs Charcoal editor 2, however the scale of this project would not allow me to work out of only the script editor.


For development of rigamajig2, I opted to use PyCharm as my primary IDE. For easier use and access within Maya, I also used the plugin MayaCharm which simply forms a connection between the PyCharm UI and Maya to avoid constantly switching between the two applications. For quick development the charcoal editor allowed me to quickly test and experiment with code. Finally, I used Git as my version control system (VCS) within PyCharm.




Python interperter

The first step to preparing PyCharm as a Maya IDE was to add the Maya Python interpreter as the main interpreter for the project.

To add a new Python interpreter in PyCharm. 1. Open the preferences window

2. Navigate to the Project> Project Interpreter panel

3. Click the gear icon and press add.

4. Locate and select the mayapy Python interpreter

MAC: /Applications/Autodesk/<version>/Maya.app/Contents/bin/mayapy
WIN: C:\Program Files\Autodesk\<version>\bin\mayapy.exe

5. Press 'OK' to add the new interpreter


In my case I chose to lock into Maya2020 so I could utilize the new matrix options without fully committing to Python 3 (rigamajig2 is forward compatible with Python 3).


Auto complete

I'm a pretty terrible speller so auto-completion is also a huge productivity booster for me, and it's super easy to add Maya auto-complete in PyCharm.


1. Download and install the Maya devkit

2. Navigate to the Project> Project Interpreter panel

3. Select "Show All..." in the project interpreter dropdown

4. Click the folder icon to edit interpreter paths

5. Add the Python Stubs path from the devkit

MAC: /Applications/Autodesk/maya2020/devkit/other/pymel/extras/completion/py
WIN: C:\Program Files\Autodesk\Maya2019\devkit\other\pymel\extras\completion\py

6. Remove the site-package paths from the interpreter paths



Auto complete works with both Maya commands and open Maya.


MayaCharm

MayaCharm is pretty simple to install, navigate to the plugins option of the preferences, search MayaCharm and install it. To begin using MayaCharm:


1. Create a new configuration and select MayaCharm

2. Select a file to execute (Most of the time for this I like to use a scratch file to store the temp code to run within maya)

3. Open Maya if its not already running

4. Prepare Maya to receive code from PyCharm by opening a command port:

# connect maya to PyCharm
if not cmds.commandPort(':4434', query=True):
    cmds.commandPort(name=':4434')

This can also be added to your userSetup.py

5. In PyCharm navigate to Run> Connect to Maya's Log to see log messages from Maya inside PyCharm

6. Click the execute button. The Code should run within Maya


You can also use alt + S and alt + A to run the selected code or document within Maya.



GIT

For rigamajig2 I used Git for version control. Jetbrains has great documentation about using Git within PyCharm.


bottom of page