ZBrush modeling

3D sculpt inspired by mixed martial arts.

Space Switching Video Series

A video series explaining the concept and scripting a tool to setup and interact with space switching setup for rigs.

Rigid Bodies Simulation

Small project with simulated rigid bodies and nParticle effects.

Maya Muscle Tutorial

A tutorial on using the spline deformer on custom geometry to create muscle flexing and stretching behavior.

Friday, November 15, 2013

Friend in need

Today's post isn't about anything technical, today I want to reach out to ask for help for a fellow artist in need.

Ana Kessel (http://anakessel.weebly.com/3D modeler, was struck by a hit-and-run driver while riding her moped. Her leg has been amputated from the injuries. She is doing OK, but she is going to need as much support as she can in order to get through this. A fundraiser has been started for her to try and raise money to help pay for the medical bills. Any donations will help and they can be submitted at 

http://www.gofundme.com/58oak4

Please share the link and help more people get involved.

Thank you

Thursday, October 31, 2013

Spidey rig update

The Spidey rig has evolved! New look, more intuitive controls, and more advance features. More updates soon.


 

  



Little guy!




Wednesday, October 16, 2013

Droid Game Rig: Collision to Animation test

An early test for a droid game rig. The setup includes a 'breakable' mode where the the rig's 'free ctrls' can move individual body parts freely in world space. On top of that a system was needed that would allow us to simulate collisions for quick animations down onto the controls without weighing down the scene. Once done the simulation can be baked to the controls so that we could continue to animate the controls for cleanup and finessing.

Model: Roberto Saavedra

Monday, October 14, 2013

Clicker Game Rig

Early iteration of animation rig for a game project. Character based on the zombie creature from 'Last of Us'.

Contribution: Retopology and rigging.



Animation: Fabio Ribak
Model: Roberto Saavedra

Monday, September 30, 2013

lpPoser Tool

Updates coming soon!



Proxy Pose


  



Motion Blur

Rotation Select Axis

Handy little tool to select individual rotation axis handle quicker. Good also for when manipulator gets in the way of select correct axis, and when in gimbal lock.


Its a short one, so just quicker to copy and paste :)
Here's the code
import maya.cmds as cmds
from functools import partial

def rotateContext(axis, *args):
 cmds.manipRotateContext( 'Rotate', e=True, ah=axis)
 
if cmds.window("rotateContextUI", exists=True):
 cmds.deleteUI("rotateContextUI")
 
win = cmds.window("rotateContextUI", title="Rotate Axis UI", w=150, sizeable=False)
cmds.rowColumnLayout(nc=3, columnAttach=[(1,"both", 2), (3, "both", 2)])

cmds.button("X", w=30, bgc=[1,0,0], c=partial(rotateContext, 0))
cmds.button("Y", w=30, bgc=[0,1,0], c=partial(rotateContext, 1))
cmds.button("Z", w=30, bgc=[0,0,1], c=partial(rotateContext, 2))

cmds.showWindow(win)

Saturday, September 7, 2013

lpDynamicChains

This a reference page for the lpDynamicChains tool.

DESCRIPTION

This tool allows for the creation of dynamic joint chains. User can specify joints to become dynamic. Can also turn any geometry into a collision object. Simulations can be cached to a file or baked as animation onto joints and dynamic nodes can be removed when done.

Suggested use: for tails, appendages, for secondary delayed movement - ex: jiggle


File:             lpDynamicChains.py

Versions:     1.0  -  06/28/2013  -  first version created
                   1.1  -  07/02/2013  -  changed curve degree to work with 2-3 joints chain. fixed
                                                        complete removal of dynamic nodes for delete dynamic
                   2.0  -  07/13/2013  -  added create collision object option
                   2.1  -  08/15/2013  -  updated collision feature and entire tool to use nDynamics
                                                        and the nucleus solver unifying all simulations under one solver
                   2.2  -  08/19/2013  -  added option to bake sim onto joint

USAGE NOTES
The UI
        1.     Place script in
                    Windows:     C:/Program Files/Autodesk/Maya2013/Python/lib/site-packages/ 
                    Mac:            Users/userName/Library/Preferences/Autodesk/maya/2013
                                        -x64/scripts/
        2.   To open the UI:
import lpDynamicChains
reload(lpDynamicChains)
lpDynamicChains.UI()
The UI allows for the creation and interaction of dynamic chains in the scene. It also allows access to lpDynamicChains commands

Create Dynamic joints

Select the start joint, shift select the end joint and click the 'Make Joints Dynamic' button under the Main section.

Commands:
from lpDynamicChains import *

dynChain = lpDynamiChain(startJoint, endJoint)
dynChain.create()

def lpDyanmicChain(startJoint="", endJoint="", node=""):
    '''
    Create the custom node

    Parameters:
        startJoint        - Name of the first joint in the chain
        endJoint          - Name of the last joint in the chain
        node              - Name of the node to manage by the class

    Returns:
        Nothing
    '''

def create(self):
    '''
    Create the actual dynamic joint chain node network

    Parameters:
        Nothing

    Returns:
        List of all new created nodes
    '''
A custom locator node will be created at the start joint's position holding the parameters to be tweaked to customize the dynamic behavior of the joint chain.

All nodes created will be stored as meta data on the nodes themselves to allow for the tool to keep track of the nodes associated with every newly created dynamic chain.

You will get a window prompting which nucleus to connect the dynamicChain to.

A log window will display at the end of creation, listing all nodes created with the new dynamicChain.

Remove Dynamic Nodes

Select any node created for the dynamic chain to be deleted or the dynamic chain locator at the start joint, and click the 'Remove Dynamic' button under the Main section.

Commands:
from lpDynamicChains import *

dynChain = lpDynamicChain(node=node)
dynChain.delete()
del(dynChain)
Note: Removing dynamic nodes will also check for any unused nucleus nodes and remove them as well.

All nodes created for the dynamic chain to be deleted will be removed from the scene returning it to its original state before the creation of the dynamic chain.

Iterate Through All Dynamic Chain Nodes

Commands:
# create instance
dynChain = lpDynamicChain()
# create iterable for all dynamic nodes in the scene
dynChains = dynChain.Iter()
for d in dynChains:
    print d
Adding Collision Objects

Select the locator for the dynamic chain to be edited, shift select the geometry to be turned into a collision object and click the 'Create Collision Object' button under the Options section.

Geometry object will be turned into a passive nRigid object to interact with the dynamic joints. A nRigid node will be created and stored in an array attribute keeping track of all collider nodes interacting with it.

Creating nCache

Select the locator or multiple locators for the dynamic chain(s) to be cached and click the 'Cache Simulation' button under the Advanced -> nCache section.

The nCache option window will display so you can choose how to save out the cache file (or multiple files) for the dynamic simulations. For more information please refer to the Autodesk Maya Help Docs

Removing nCache

Select the locator for the dynamic chain to be cached and click the 'Delete Cache' button under Advanced -> nCache section.

Note: Removing dynamic nodes for a given dynamic chain will not delete the cache file on disk.

The Delete nCache option window will display. If there are multiple cache nodes for the dynamic chain, you will be able to select which ones to delete.

The option to delete or keep the cache files on disk will also be available.

Baking Simulation to Joints

Select the locator for the dynamic chain to be bake the simulation for and click the 'Bake Simulation' button under Advanced -> Animation

Utility Commands

In addition to the UI functions, you can also access utility commands available with the script. Below are some examples.

Get and Set the Active Nucleus
from lpDynamicChains import *

nucleus = getActiveNucleus()
setActiveNucleus(nucleus)

def getActiveNucleus():
    '''
    Query the active nucleus node
    
    Parameters:
        Nothing
    
    Returns:
        Name of active nucleus node
    '''

def setActiveNucleus(nucleus):
    '''
    Set the active nucleus node
    
    Parameters:
        nucleus        - Name of nucleus node to set as current active nucleus
    
    Returns:
        Nothing
    '''
Get Connected

Get commands to find the connected DynamicChain node or the nucleus node connected to the dynamicNode
from lpDynamicChains import *

# get selected object
obj = cmds.ls(sl=True)[0]
dNode = getConnectedDynamicChain(obj)
nucleus = getConnectedNucleusNode(dNode)

def getConnectedDynamicChain(node):
    '''
    Look for a valid DynamicChain node connected to the given node
    
    Parameters:
        node             - Name of the node to find connection to DynamicChain
    
    Returns:
        Name of DynamicChain node connected to given node
    '''

def getConnectedNucleusNode(node):
    ''' 
    Look for the nucleus node connected to the given node
    
    Parameters:
        node             - Name of the node to find connection to nucleus
    
    Returns:
        Name of the nucleus node connected to the given node
    '''
Checks

Check commands to verify if node is a DynamicChain node or node is a nucleus compatible nDynamics node.
from lpDynamicChains import *

# get selected object
obj = cmds.ls(sl=True)[0]
if isDynamicChain(obj):
    dynChain = lpDynamicChain(obj)

    # access attribute
    hairShape = dynChain.hairSystemShape[0]
    return isNType(hairShape, "hairSystem")

def isDynamicChain(node):
    '''
    Look for the identifier to check if node is 'dynamic' 
    
    Parameters:
        node         -  Name of node to check for 'nodeType'
    
    Returns:
        Boolean value if node is dynamic or not, True or False
    '''

def isNType(node, nodeType):
    '''
    Check if the given node is a nucleus compatible nDynamics node
    
    Parameters:
        node        - Name of node to check for 'NType' compatibility
        nodeType    - Node type to check
    
    Returns:
        Boolean value if node is compatible with nDynamics solver
    '''
Number of Colliders

Check how many collision objects are attached to the node
from lpDynamicChains import *

# get selected object
obj = cmds.ls(sl=True)[0]
if isDynamicChain(obj):
    numColliders = findNumberOfCollisionObjects(obj)

def findNumberOfCollisionObjects(node):
    '''
    Find the number of collision objects attached to the node
    
    Parameters:
        node         - Name of the node to check for collision objects
    
    Returns:
        Integer number of collision objects attached to the node
    '''
Delete Unused Nucleus Nodes

Check nucleus nodes in the scene for their connections and remove any that are not being used.
from lpDynamicChains import *

deletedNodes = deleteUnusedNucleusSolvers()

def deleteUnusedNucleusSolvers():
    '''
    Delete all nucleus nodes not being used
    
    Parameters:
        Nothing
    
    Returns:
        List of nucleus node deleted
    '''
Create and Connect

Creation and connection commands to create new nucleus nodes, nRigid nodes, connect DynamicChain node to nucleus or to connect a nRigid node to a nucleus.
from lpDynamicChains import *

# get selected objects
selected = cmds.ls(sl=True)[0]

dNode = selected[0]
mesh = selected[1]

nucleus = createNucleus()
if isDynamicChain(dNode):
    nucleus = connectToNucleus(dNode, nucleus)
nRigid = createNRigid(mesh, nucleus)
index = connectNRigidToNucleus(nRigid, nucleus)

def createNucleus(name="", setActive=True):
    '''
    Create nucleus node
    
    Parameters:
        name             - Name for the new nucleus node
        setActive        - Boolean to set the new nucleus as the current active nucleus
        
    Returns:
        Name of new nucleus node
    '''

def createNRigid(obj, nucleus=""):
    '''
    Create a nRigig node from the given obj
    
    Parameters:
        obj              - Name of the geo to create nRigid from
        nucleus          - Name of the nucleus to connect nRigid to
        
    Returns:
        Name of new nRigid node
    '''

def connectToNucleus(node, nucleus):
    '''
    Connect the given node to the nucleus node
    
    Parameters:
        node             - Name of the node to connect to the nucleus solver
        nucleus          - Name of nucleus solver to connect to
    
    Returns:
        Name of nucleus node (debug)
    '''

def connectNRigidToNucleus(nRigid, nucleus, newNucleus=True):
    '''
    Connect the given nRigid node to the nucleus node, maintaining prior connections to
    other nucleus nodes
    
    Parameters:
        nRigid         - Name of nRigid node to connect to nucleus
        nucleus        - Name of nucleus node to connect
        newNucleus     - Boolean to create a new nucleus node if the specified nucleus doesn't exist
    
    Returns:
        Integer index of next available passive nRigid for the given nucleus
    '''
Access connections

Command to get all nodes connected to custom node, like hairSystem, or the ikHandle. Access custom attributes through class _getattr_ method

from lpDynamicChains import *

# get selected object
obj = cmds.ls(sl=True)
if isDynamicChain(obj):
    dNode = lpDynamicChain(node=obj)
    nodes = dNode.listConnections()
# get hairSystemShape node
hairShape = dNode.hairSystemShape[0]

def listConnections(self):
    '''
    Wrapper to get all connections to the node to message attributes
        
    Parameters:
        Nothing
        
    Returns:
        List of connected nodes to all custom message attributes
    '''

Wednesday, September 4, 2013

Node Based Pivot Switching

Hello guys. I was going to write a post to show how you can setup pivot switching to be built in your own rigs using native maya nodes.

However, this time, I thought just a video would cover what you need to know.

As you will see, this is a fairly straightforward approach once you've done it once. It just requires a little creativity. The idea here obviously is to look for an alternative to setting pivot switching for your controls, so you can interactively switch the pivot of your controls, without having to rely on an external tool or script.

If you need to refer back at some of the steps, here are some notes.


Here is the video.


Dynamic Chains Comeback

I did a video awhile back showing how to setup dynamic joint chains using Maya Hair. The idea was to show a quick and easy process to get those working. Since then I put together a tool to automate the setup process and to allow for easy management of multiple dynamic joint chains in the scene.

This is the original video posted.


The tool was planned out, and I've been updating it as I used it and as needed. Here is the original plan for the tool.


In the process, the setup also got update to utilize nDynamics and the nucleus solver, unifying all dynamic simulations under one solver.

In summary, the tool will allow the creation of multiple dynamic joint chain driven by nHair. The user will also be able to turn any geometry into a collision object, in order to have the joint interact with it. The simulations created with the setup can also be cached to a file or baked as animation onto joints, through the tool, and the dynamic nodes can be removed at any point in time.

I'll be making the tool script available soon in the Downloads section, and I will be posting a reference page with instructions on using the tool's UI.

Hope you guys enjoy it. Any feedback or comments will be very appreciated.

Friday, August 30, 2013

Lion Rig Update

Update on a quadruped rig for a Lion model.
Model credit: Attakarn Vachiravuthichai





Sunday, August 11, 2013

Joint Relocation

In this post I want to show you a quick and easy way of reposing your joint layout to make it easier for you to reuse a skeleton if you already have one laid out that you want to reuse. The idea here is to be able to just worry about where the joint is positioned, and not have to worry about maintaining joint orientation.

To accomplish this we will rely mainly on one thing, and that is an aim constraint, to allow us to aim the rotation axis we want down the joint chain.

Ok, so to break it down, lets just start with a 3 joint chain. To visualize this better lets go ahead and turn Local Rotation Axis on for all joints.

By default we have the x axis going down the joints. To keep our joint intact we will use locators to actually move around the joints to reposition them. The aim constraints will be applied to these locators.

So we need to start by breaking up the joint hierarchy. Parent each to world, you can do that by hitting Shift + P with the joint selected.

We will then create a locator at each joints position. In fact we want to maintaing the joints orientation as well. So for each joint, select the joint, select the locator, and goto Constraint -> Parent Constraint. We want to uncheck maintain offset. That will position and orient the locators to match the joints. We can then delete the constraint, and parent the joint under the locator.


Now we go ahead and set the aim constraints. This will mimic our original hierarchy and allow us to keep the joint orientation.

Select the second locator and shift select the first locator. And goto Constraint -> Aim Constraint.
We can repeat the steps for the third and second locators.
We can now relocate the joints. When we are ready we can unparent the joints (Shift + P) and parent the joints back into the hierarchy.


We can then delete the locators, and everything we be gone with them.


And there we go! Now, obviously it wouldn't be too efficient to have to redo this every single time. So if you want, check out lpJointRelocation.py

And here is a quick overview of how to use it.

Break Hierarchy: This will break down the hierarchy, create locators and constraints, and hook up message attributes to manage the connections with the tool. Select the first joint in the hierarchy you want to adjust.

Rebuild Hierarchy: To put the joints back into the hierarchy in the new position. This will also get rid of the created nodes that are no longer required. Select the first locator.

Select Hierarchy: To make it easier to select all the joint in the hierarchy starting from the selected joint.

Toggle Local Axis: To toggle on and off the local rotation axis to help with some visual feedback.

Hope you guys find it useful!

Tuesday, August 6, 2013

Turning Off Local Rotation Axis

If you ever have been frustrated with the local axis toggle in maya like I once was you might find this little script useful. Sometimes you forget or loose track of what you've toggled on. Today I saw a friend spend so much time trying to figure out what node has local axis on, that while it was kinda of funny, I felt it couldn't be a bad thing to share something that can fix it in just a few lines. As I'm sure there are more people who have struggled with this at some point. So, using good old fashion MEL, you can just create a shelf button and copy these lines

// Turn off LRA for all the selected objects
$sl = `ls -sl`; for ($s in $sl) { $on = `toggle -q -localAxis $s`; if ($on == 1) { toggle -localAxis $s; } };
You can now select the objects you want and click the shelf button to run the code, and it will toggle only the ones that are on. Essentially making sure everything is off.

Saturday, August 3, 2013

Extracting Twist Controls for IK Spline

Being able to have twisting propagate down a joint chain is a common feature for character rigs. An automated approach over a manual approach might be a quicker and easier solution, however as we'll discuss below might end up presenting problems. A manual method of animating twisting in the other hand while giving exactly what the animator want, is also more work for the animator.  There are many ways to setup twisting, each with their own advantages and issues. However, this post won't be about the best solution for twisting, so much as it will be a practical solution for providing the animator with more control over the twist action of the joints by having both an automated and a manual solution.

I've tried many different setups and upon arriving at this solution I found it to be the most stable and practical for when dealing with twisting.

Watch the video demonstrating the setup here.

So let's get started. Before we start lets create a quick setup to try this out. The first thing we will need is a Spline IK driven joint chain. 


Next, we will need to make our IK controls. To keep everything simple for now, lets just use empty transform nodes for our controls. As an observation, this step isn't really necessary, we just want to have a way of driving the curve shape to drive the joint's position.

Let's start by making a single joint, group it twice and name the nodes,
GRP_Start_IK, CTRL_Start_IK, jnt_StartCurveDriver

Duplicate the top group and rename the new nodes,
GRP_End_IK, CTRL_End_IK, jnt_EndCurveDriver


Now we can move the corresponding groups to the start and end of the joint chain.


Finally we can select the driver joints and select the curve last, and goto Skin -> Bind -> Smooth Bind
we can then choose the following options


Now, we can drive the Spline IK with our controls. So this will be our starting point.

Notice that we can move and rotate our controls to drive the curve that is driving the joint chain. However, we can't seem to rotate the controls to get twisting. The reason for this is that components, such as the CVs on the curve only represent points in space, and because we don't get normals with curves, we can't get rotation information from the curve points. Now, some of you may be familiar with the Advance Twist Controls setup for spline IK. If not, you can check out the post here that goes over the setup.

The advance twist setup is nice to quickly get twisting with your Spline IK. However, we mentioned that we want to have not only the option to toggle auto twisting on and off, but to also offset the twisting. In addition we will also look at how this new setup will give us a nice solution for a common problem that comes with using the advance twist controls. Lets take a look now at how we can extract the twist from the spline IK solver and give it to the animator to control and give them the option to automate or not the twisting.

Lets start by creating some attributes on our end control to drive the twisting. Lets create an autoTwist and a twistOffset attribute.




We can now start creating and hooking up the math nodes to help us with this setup. The first thing we want to create is a multiplyDivide node to multiply the rotationX (that is our twist axis for our control in this case) of our end control by our autoTwist attribute value. Lets rename this md_autoTwist and hook up our attributes.

Connect CTRL_End_IK.rotateX to md_autoTwist.input1X
Connect CTRL_End_IK.autoTwist to md_autoTwist.input2X


Next we want to create a plusMinusAverage node to sum our twistOffset value to the output of the multiplyDivide node. So lets do that and rename this pma_twistOffset.

Connect md_autoTwist.outputX to pma_twistOffset.input1D[0]
Connect CTRL_End_IK.twistOffset to pma_twistOffset.input1D[1]


Finally, we are ready to connect it to our ikHandle.

Connect pma_twistOffset.output1D to ikHandle1.twist



There we go! So with not a lot of extra work, just 2 extra nodes we get a clean setup for twisting. And that is actually not the best part. I mentioned earlier that this setup would also help solve an issue of the advance twist control. Or with most automated solutions. And that is the flip that occurs at 180 degrees. This problems occurs because of how rotations are being calculated (Euler rotations). This ends up leading us into problems such as Gimbal locking and making the setup fragile if we are relying on any one rotation axis to get our twisting. So as it turns out its not so advance after all! :)

As this is another whole discussion in itself, I'll just leave it at that. For now, be assured that this setup will handle with no problems rotations beyond 180 degrees or -180 degrees for that matter with no problems because of how the spline IK is solving for twisting.

The setup in its current state is an example in which the twist propagates up the joint chain. However, what if we need the CTRL_Start_IK to also be able to control the twisting. To have the twisting propagate down the joint chain. Let's take a look at that.

If we go ahead and use the roll attribute on the ikHandle notice that the entire joint chain rotates. The obvious thing to do is to counter rotate using the twist and we then get the effect the rotation came from the start of the joint chain. 

So let's start with that first.

Connect CTRL_Start_IK.rotateX to ikHandle1.roll

Now, for the start control to work with the end control at the same time and for our autoTwist attribute to work on CTRL_Start_IK as well, we will create another multiplyDivide node, we can call this md_autoTwist2.

Connect CTRL_End_IK.autoTwist to md_autoTwist2.input1X
Connect CTRL_Start_IK.rotateX to md_autoTwist2.input2X

To counter rotate we will use a plusMinusAverage node. We will call this pma_twistOffset2. However, we also need to account for whatever twist offset already exists from the CTRL_End_IK.

Connect pma_twistOffset.output1D to pma_twistOffset2.input1D[0]
Connect md_autoTwist2.outputX to pma_twistOffset2.input1D[1]
Connect pma_twistOffset2.output1D to ikHandle1.twist

And we can set the operation to Subtract.


And there it is! Now go and have fun making your joints twist!

I realize although its not too complicated you probably don't want to have to setup it every single time. Hopefully you still were able to understand it so that you could :)

But to speed up the process here's a script to set it up for you. It takes in the start control, the end control, the control object, the ikHandle, and the twist axis for the control rotation (this can be negative as well).

import maya.cmds as cmds

def twist(startCtrl, endCtrl, ctrlObj, ikHandle, axis):
 '''
 Create twist control for the spline IK setup
  
  startCtrl : node controlling the start of the joint chain
  endCtrl  : node controlling the end of the joint chain
  ctrlObj  : control object to host the twist attributes
  ikHandle : the spline IK handle
  axis  : the rotation axis for twisting on the controls : "x", "y", "z", "-x", "-y", "-z"
 '''
 # check nodes
 for node in [startCtrl, endCtrl, ctrlObj, ikHandle]:
  if not cmds.objExists(node):
   cmds.error("%s does not exist." % node)
 # check axis
 if axis not in ["x", "y", "z", "-x", "-y", "-z"]:
  cmds.error("Invalid axis.")
  
 # check if our twist axis is on the negative side
 neg = False
 splitString = axis.split("-")
 if splitString[0] == "":
  axis = splitString[1]
  neg = True
 axis = ".r" + axis
 
 # create new attributes
 cmds.addAttr(ctrlObj, ln="autoTwist", at="double", dv=1, min=0, max=1, k=True)
 cmds.addAttr(ctrlObj, ln="twistOffset", at="double", dv=0, k=True)
  
 # create nodes for autoTwist and twistOffset
 autoTwist1 = cmds.createNode("multiplyDivide", n="md_" + ctrlObj + "_autoTwist1")
 autoTwist2 = cmds.createNode("multiplyDivide", n="md_" + ctrlObj + "_autoTwist2")
 twistOffset1 = cmds.createNode("plusMinusAverage", n="pma_" + ctrlObj + "_twistOffset1")
 twistOffset2 = cmds.createNode("plusMinusAverage", n="pma_" + ctrlObj + "_twistOffset2")

 # connect the end control
 cmds.connectAttr(endCtrl + axis, autoTwist1 + ".input1X")
 cmds.connectAttr(ctrlObj + ".autoTwist", autoTwist1 + ".input2X")
 
 cmds.connectAttr(autoTwist1 + ".outputX", twistOffset1 + ".input1D[0]")
 cmds.connectAttr(ctrlObj + ".twistOffset", twistOffset1 + ".input1D[1]")
 
 # connect the start control
 cmds.connectAttr(startCtrl + axis, autoTwist2 + ".input1X")
 cmds.connectAttr(ctrlObj + ".autoTwist", autoTwist2 + ".input2X") 

 cmds.connectAttr(twistOffset1 + ".output1D", twistOffset2 + ".input1D[0]")
 cmds.connectAttr(autoTwist2 + ".outputX", twistOffset2 + ".input1D[1]")
 
 cmds.setAttr(twistOffset2 + ".operation", 2) # subtract
 
 # connect to the ikHandle
 cmds.connectAttr(startCtrl + axis, ikHandle + ".roll")
 cmds.connectAttr(twistOffset2 + ".output1D", ikHandle + ".twist")
 
 # make changes if the twist axis is on the negative side
 if neg:
  invTwist1 = cmds.createNode("multiplyDivide", n="md_" + ctrlObj + "_invTwist1")
  invTwist2 = cmds.createNode("multiplyDivide", n="md_" + ctrlObj + "_invTwist2")
  
  cmds.setAttr(invTwist1 + ".input2X", -1)
  cmds.setAttr(invTwist2 + ".input2X", -1)
  
  cmds.connectAttr(endCtrl + axis, invTwist1 + ".input1X")
  cmds.connectAttr(startCtrl + axis, invTwist2 + ".input1X")
  
  cmds.connectAttr(invTwist1 + ".outputX", autoTwist1 + ".input1X", f=True)
  cmds.connectAttr(invTwist2 + ".outputX", autoTwist2 + ".input1X", f=True)
  cmds.connectAttr(invTwist2 + ".outputX", ikHandle + ".roll", f=True)

Hope you guys liked it! Feel free to share!
And to leave any comments or questions!

Enjoy!

Friday, August 2, 2013

Rigging the Eye Brows

Check out the video tutorial on rigging eye brow controls for your characters. For my friend Rafael Salvador who asked me to put this together.
Im also going to start embedding the videos here in the website and see how that goes.


This is the concept shown in the video.


Enjoy!

Tuesday, July 30, 2013

Practical Facial Rigging

In this post I'll be going over a face setup Im working on for a personal project. The idea behind this setup is to allow for quick and efficient facial rigging, by allowing a large level of control, from a large library of preset face shapes and individual face controls. This setup will also allow for easy transferability of the the rig across characters and with that, animation data across multiple face rigs.

Some of the concepts I'll be touching on were introduced by Jeremy Ernst for the Gears of War 3 game.

I mentioned a large library of preset face shapes. Yes, this means blendshapes, or morph targets, or whatever the software you use calls them. And lots of them. The idea is to make the facial animation easier and cleaner. By having many face poses broken down into separate groups, we can narrow down the animation of these poses to just one attribute control, instead of having to animate translations or rotations (and sometimes even scale depending on how little joints we are using on the face) to get the desired pose. Again, this keeps things a lot cleaner and simpler to animate (single animation curves in the graph editor).

This takes us next to how the poses are made. The pose library for the face will be built following the Facial Action Coding System. Individual poses will be sculpted to represent each action unit, and then used as blendshapes. Below are some links with more information if you want to learn more about it.

http://en.wikipedia.org/wiki/Facial_Action_Coding_System
http://www.cs.cmu.edu/~face/facs.htm
http://face-and-emotion.com/dataface/facs/description.jsp

To summarize, F.A.C.S outlines a series of action units that describe the face's range of motion through the contraction or relaxation of muscles in the face.

Although the current setup offers the animator a lot of intuitive control to drive the character's facial expressions, its still desirable to have individual controls to offset the preset poses. To allow for that, on top of the current setup there will also be a control rig, with individual controls for joints that are weighted on the face skin cluster. The interesting part however is to figure out how to have the controls follow the surface of the face while we are changing between different face poses. And this is where the concept of rivets comes in.

In essence, rivets allow you to pin something to something else, much like a point constraint, but at a component level. Meaning, the object will move with the point it is pinned to. Therefore, you can deform the object and the rivet will move to follow the deformation. Places for using rivets include surfaces (NURBS and Poly) and curves. Below I included a script for creating a locator pinned to a point on a surface or a point on a curve.
# import python modules
try:
    import maya.cmds as cmds
    import maya.OpenMaya as om
except Exception, e:
    print "Error while trying to import python modules."
    print "Exception: ", e

def rivetAtPointOnSurface():
    ''' Pin the object to selected points on the NURBS surface '''
    # get the selected components
    sel = cmds.ls(sl=True, fl=True)
    
    if not sel:
        cmds.error("Nothing selected")
        
    # get the surface name
    surface = sel[0].split(".")[0]
    # get the shape
    shape = cmds.listRelatives(surface, shapes=True)[0]
    
    # create rivet for each selected component
    for each in sel:
        # get the position to pin the object to on the surface
        pos = cmds.xform(each, q=True, ws=True, t=True)
        # define the point
        mPoint = om.MPoint(pos[0], pos[1], pos[2])
        # get the surface's dagPath
        selectionList = om.MSelectionList()
        selectionList.add(shape)
        mNode = om.MDagPath()
        selectionList.getDagPath(0,mNode)
    
        if cmds.objectType(shape) == "nurbsSurface":        
    
            surfaceFn = om.MFnNurbsSurface(mNode)
            util = om.MScriptUtil()
        
            uParamPtr = util.asDoublePtr()
            vParamPtr = util.asDoublePtr()
        
            if not surfaceFn.isPointOnSurface(mPoint):
                surfaceFn.getParamAtPoint(mPoint, uParamPtr, vParamPtr, False, om.MSpace.kObject, 0.001)
            else:
                mPoint = surfaceFn.closestPoint(mPoint, False, uParamPtr, vParamPtr, False, 0.001, om.MSpace.kObject)
                surfaceFn.getParamAtPoint(mPoint, uParamPtr, vParamPtr, False, om.MSpace.kObject, 0.001)
            
            # get the U and V paramters
            u = util.getDouble(uParamPtr)
            v = util.getDouble(vParamPtr)
    
            # create rivet locator
            rivet = cmds.spaceLocator(n="rivet")[0]
    
            # create point on surface node
            psi = cmds.createNode("pointOnSurfaceInfo", n="psi_" + surface)
            cmds.setAttr(psi + ".parameterU", u)
            cmds.setAttr(psi + ".parameterU", v)
            
            cmds.connectAttr(shape + ".ws", psi + ".is", f=True)
            cmds.connectAttr(psi + ".position", rivet + ".t", f=True)
            
            # constraint the rivet locator
            cmds.orientConstraint(surface, rivet, mo=True)
        else:
            cmds.error("Not a NURBS surface component")

def rivetAtPointOnCurve(curve):
    ''' Pin the object to the selected CVs '''
    # get the selected components
    sel = cmds.ls(sl=True, fl=True)
     
    if not sel:
        cmds.error("Nothing selected")
         
    # get the curve name
    curve = sel[0].split(".")[0]
    # get the shape
    shape = cmds.listRelatives(curve, shapes=True)[0]
     
    # create rivet for each selected component
    for each in sel:
        # get the position to pin the object to on the surface
        pos = cmds.xform(each, q=True, ws=True, t=True)
        # define the point
        mPoint = om.MPoint(pos[0], pos[1], pos[2])
        # get the surface's dagPath
        selectionList = om.MSelectionList()
        selectionList.add(shape)
        mNode = om.MDagPath()
        selectionList.getDagPath(0,mNode)
     
        if cmds.objectType(shape) == "nurbsCurve":       
     
            curveFn = om.MFnNurbsCurve(mNode)
            util = om.MScriptUtil()
         
            uParamPtr = util.asDoublePtr()
         
            if curveFn.isPointOnCurve(mPoint):
           curveFn.getParamAtPoint(mPoint, uParamPtr, 0.001, om.MSpace.kObject)
            else:
           mPoint = curveFn.closestPoint(mPoint, uParamPtr, 0.001, om.MSpace.kObject)
           curveFn.getParamAtPoint(mPoint, uParamPtr, 0.001, om.MSpace.kObject)

            # get the U paramter
            u = util.getDouble(uParamPtr)
     
            # create rivet locator
            rivet = cmds.spaceLocator(n="rivet")[0]
     
            # create pointOnCurveInfo node
            pci = cmds.createNode("pointOnCurveInfo", n="pci_" + curve)
            cmds.setAttr(pci + ".parameter", u)
             
            cmds.connectAttr(shape + ".ws", pci + ".ic", f=True)
            cmds.connectAttr(pci + ".position", rivet + ".t", f=True)
            # constraint the rivet locator
            cmds.orientConstraint(curve, rivet, mo=True)
        else:
            cmds.error("Not a NURBS Curve component")
Unfortunately with Maya's default nodes we can't attach an object to a point on a polygon. An idea by Michael Bazhutkin was to use the node curveFromMeshEdge to extract two curves from edges on a surface and use them to create a loft surface (a NURBS patch) and pin the rivet to the middle point on this surface.
def rivetAtPointOnMesh():
    ''' Pin the object to selected face on the NURBS surface '''
    # get the selected component
    sel = cmds.ls(sl=True, fl=True)

    if not sel:
        cmds.error("Nothing selected")

    # get the surface name
    surface = sel[0].split(".")[0]
    # get the shape
    shape = cmds.listRelatives(surface, shapes=True)[0]    
    # create rivet for each selected component
    for each in sel:
        # convert selection to edges
        cmds.polyListComponentConversion(each, ff=True, te=True)
    
        # create curves from edges
        edges = cmds.ls(sl=True, fl=True)

        if cmds.objectType(shape) == "mesh": 
    
            c1 = cmds.createNode("curveFromMeshEdge")
            cmds.setAttr(me1 + ".ihi", 1)
            cmds.setAttr(me1 + ".ei[0]", edges[0])
        
            c2 = cmds.createNode("curveFromMeshEdge")
            cmds.setAttr(me2 + ".ihi", 1)
            cmds.setAttr(me2 + ".ei[0]", edges[1])
            
            # create a lofted surface from curves
            loft = cmds.createNode("loft")
            cmds.setAttr(loft + ".ic", s=2)
            cmds.setAttr(loft + ".u", True)
            cmds.setAttr(loft + ".rsn", True)
            
            # create a pointOnSurfaceInfo node for center point on lofted surface
            psi = cmds.createNode("pointOnSurfaceInfo")
            cmds.setAttr(psi + ".turnOnPercentage", 1)
            cmds.setAttr(psi + ".parameterU", .5)
            cmds.setAttr(psi + ".parameterV", .5)
            
            cmds.connectAttr(loft + ".os", psi + ".is", f=True)
            cmds.connectAttr(c1 + ".oc", loft + ".ic[0]")
            cmds.connectAttr(c2 + ".oc", loft + ".ic[[1]")
            cmds.connectAttr(shape + ".w", c1 + ".im")
            cmds.connectAttr(shape + ".w", c2 + ".im")
            
            # create rivet locator
            rivet = cmds.spaceLocator(n="rivet")[0]
            cmds.connectAttr(psi + ".position", rivet + ".t", f=True)
            
            # constraint the rivet locator
            cmds.orientConstraint(surface, rivet, mo=True)
    
        else:
            cmds.error("Not a polygon mesh")
This works great, but doesn't let us create a rivet on a specific point on a polygonal mesh. For that we would need a plugin that works like the pointOnSurfaceInfo. But that's a subject for later.

Now that we have a way to track different positions on the face, we can use that information to move our controls. And yes, I did say track (with italics). Because in essence that is what we are doing, much like motion capture, we are tracking different positions on that face to be able to drive or controls, which are driving the face joints.

With that in mind, we just need to understand how all these things will be hooked up together. Let break this down.


To keep our face rig as clean as possible, we will have one blendshape target (Master Blendshape) piped into the final face mesh. All of the different blendshapes (from the F.A.C.S pose library) will be driving the Master Blendshape, and hence the final mesh. The rivet locators will be pinned to the Master Blendshape face. Because the rivets are only pinned to a position, we also need to constraint its rotation to that of the head so it follows it correctly. We can then drive the control's parent space with the rivet. We can do that with a parent constraint on a group above the control. And finally the joints are parent constrainted to the controls.

Cool. Now that we have covered the setup lets look at what we can further achieve with this setup.

Transferring Rigs

The idea is that you can use the same topology for various face meshes, and by applying the initial rig as a blendshape, we now have the rig working with a new face model.

Transferring Animation

Transferring animation data also becomes really easy. With the the pose library the animator will be animating attributes, and with the controls, offset transforms, which are relative and not based on world or parent space. Therefore we can transfer animation really easily between multiple face rigs with the same setup.

So that was an overview of the design. Hopefully that can inspire you to do something cool!

Thursday, July 25, 2013

Connect Attribute: Maintain Offset

Connecting attributes is one way to drive a node's attribute using another in Maya. When connecting numeric values, such as translate or rotate, sometimes an offset exists prior to the connection which you might not want to lose. It would be nice if we could maintain that offset when doing the connection between attributes, like we can with our standard constraints.

A simple way to do that is using a math node plusMinusAverage as a buffer. We can first calculate the difference between the two attributes we are connecting. For example, if we have A = < 0, 0, 0 >  and B = < 3, 1, 3 >  if we connect A to B directly, now B = < 0, 0, 0 >. What we can do to get around this is to first find the difference between A and B as A-B and then use our plusMinusAverage node to subtract this difference from A. We can first store the current value for the source attribute, A, in the first input, and set the other input to the difference we have just computed, in this case < -3, -1, -2 >. By default the node's operation is set to Sum, we want Subtract. We then connect the output to the destination attribute, and now the attributes are connecting but no 'snap' happened.

In this example I want to connect pCube2.translateY to pCube1.translateZ, but notice the pCube2.translateY is 0 while pCube1.translateZ is 3.

If I do a simple connection, pCube1 will 'snap' to the origin.


We might not want that. So we do this setup to keep that offset.








Here is a wrapper for Maya's connectAttr command allowing to maintain the current offset by using a plusMinusAverage node in between.

UPDATED
def connectAttr(srcPlug, destPlugs, maintainOffset=False, **kwargs):
    ''' Wrapper for Maya's connectAttr command with the extra functionality of allowing to maintain offset
        if attribute is a numeric attribute and to connect to multiple objects '''
    if type(destPlugs) != list:
        destPlugs = [destPlugs]
    
    srcValue = cmds.getAttr(srcPlug)

    for destPlug in destPlugs:
        if maintainOffset:
            # get the attribute type
            attrType = cmds.getAttr(srcPlug, type=True)
            
            destValue = cmds.getAttr(destPlug)
            
            if attrType in ["doubleLinear", "doubleAngle", "double", "long", "float"]:
                # find the difference 'delta' between the two attributes    
                diff = srcValue - destValue
                # create a node to keep the offset
                offsetNode = cmds.createNode("plusMinusAverage")
                cmds.setAttr(offsetNode + ".operation", 2)      # subtract
                
                # connect and disconnect plug to get the value in
                cmds.connectAttr(srcPlug, offsetNode + ".input1D[0]")
                cmds.connectAttr(srcPlug, offsetNode + ".input1D[1]")
                cmds.disconnectAttr(srcPlug, offsetNode + ".input1D[1]")
                cmds.setAttr(offsetNode + ".input1D[1]", diff)
                
                cmds.connectAttr(offsetNode + ".output1D", destPlug, **kwargs)
            
            elif attrType in ["double3", "float3"]:
                # find the difference 'delta' between the two attributes    
                diff = []
                for i, d in enumerate(srcValue[0]):
                    diff.append(d - destValue[0][i])
                
                # create a node to keep the offset
                offsetNode = cmds.createNode("plusMinusAverage")
                cmds.setAttr(offsetNode + ".operation", 2)      # subtract
                
                # connect and disconnect plug to get the value in
                cmds.connectAttr(srcPlug, offsetNode + ".input3D[0]")
                cmds.connectAttr(srcPlug, offsetNode + ".input3D[1]")
                cmds.disconnectAttr(srcPlug, offsetNode + ".input3D[1]")
                cmds.setAttr(offsetNode + ".input3D[1]", diff[0], diff[1], diff[2])
                
                cmds.connectAttr(offsetNode + ".output3D", destPlug, **kwargs)
                
                return offsetNode
        else:
            cmds.connectAttr(srcPlug, destPlug, **kwargs)
Hope you guys find this useful.

Wednesday, July 24, 2013

Lattice Fun: Correctives

I wanted to do this quick post to remind some of you the power of lattices when it comes to quickly shaping geometry. The workflow I'll be going over in the example below covers an quick and easy technique for creating some pose space deformations. Do keep in mind however, that while this technique is great for deformations on single axis rotations, in areas that allow for multi axes rotations we would need a more advance approach.

You can also watch here the video example for setting this up.
So let's get started.

I'll be using a simple polygonal cylinder to demonstrate this. So go ahead and create a cylinder.
Give it enough resolution so we can deform this properly.


Next lets create some joints for skinning the geometry to.


Now, lets go ahead and create a lattice to deform the cylinder. Select the cylinder, and goto
Create Deformers -> Lattice 


Give the lattice enough resolution to deform the geometry properly. Think about the shape you want to achieve and how many control point spans you might need. I changed the divisions along the height to 9, that way I'll have 3 control spans in the midsection of each bone, and that will give me enough control. Play around with these values and see what suits your needs for what you have in mind.


This is where this gets fun. We now want to bind the lattice to the joints, so Select the first joint, Shift Select the lattice and goto
Skin -> Bind Skin -> Rigid Bind (at default settings -> it will use the joint's hierarchy)

Note: We could have used the Smooth Bind instead. However, we are looking for easy control of shaping, and when we begin tweaking the lattice to shape our corrective, you'll notice that the smoothing from the Smooth Bind can get in the way. We Rigid Bind the tweaking are more precise.

Great. Now test out the setup by rotating the mid joint.

Right now, the deformation is a little rough however, so to get a better starting point.
Change the Local Influence in the ffd1 node (located under the lattice's OUTPUTS).


The skinning should be smoother now (see, and we are not even using Smooth Bind!) :)

The last step is just modifying our lattice to give us the desired shape on the geometry that we want.
Before you do this, just figure out how you plan on using this new shape. You can use it for a BlendShape target for example by duplicating the new geometry and getting rid of the lattice. Or you could just leave the lattice and use Set Driven Keys using the joint's rotation to drive the lattice's control point's position.

If your going with the second option, make sure you you set a key on the Set Driven Key window before you start tweaking the lattice points. Select all latices points and click Load Driven, select the mid joint and click Load Driver.


And thats it!

Now go have fun using lattices!