Tutorial: Create a Procedural Ribbon in Maya

Tutorial: Create a Procedural Ribbon in Maya

Since I put up my rigging reel I’ve received a lot of questions and requests for a tutorial on the procedural ribbons, so I’ve finally put together a tutorial on them. When I finished the tutorial I realized that it was really slow-paced, so I’ve written a script for it also (attached at the bottom of this post). So for those of you that find it easier to figure out what’s going on by skimming through a script, you know what to do :)

ProceduralRibbonDemo

I’m sure this technique is old news to many people, but it’s too awesome not to be shared. These ribbons actually evaluate slightly faster than the traditional ribbon-setup, which is crazy considering they’re so much more flexible. What makes this setup powerful though, is that they utilize the nonLinear deformers in Maya, which means you inherit the same flexibility as you have with deformers, so you won’t take a performance-hit as you’re just adding functionality to the already existing deformer(s). This technique could of course easily be altered to ride ontop of FK-rigs, which could be used to rig tails/tentacles/fishes etc.

The tutorial:

The script:
The script is almost identical to the one I create in the tutorial, I’ve just added an option to offset where on the surface the twist occurs, and cleaned up the setup a bit.

jh_proceduralRibbon (Procedural Ribbon)

# ************************************************************************************************************
# Title: jh_proceduralRibbon.py
# Author: Jorn-Harald Paulsen
# Created: October 12, 2014
# Last Update: October 12, 2014
# Description: Utility to set up a ribbon with twist/sine/volume
# ************************************************************************************************************

# IMPORT MODULES
import maya.cmds as cmds
import maya.OpenMaya as OpenMaya1
import math

# UI: FUNCTION FOR BUILDING THE ELEMENTS IN THE WINDOW
def jh_proceduralRibbon():
#Create a variable for the window name
winName = ‘jh_proceduralRibbon’
winTitle = ‘Set up a ribbon with twist/sine/volume’
#Delete the window if it exists
if cmds.window(winName, exists=True):
cmds.deleteUI(winName, window=True)
#Build the main window
cmds.window(winName, title=winTitle, sizeable=True)
cmds.columnLayout(adjustableColumn=True)
#Create the columnLayout
cmds.columnLayout(adjustableColumn=True)

#Build tab
cmds.frameLayout(l=’Prefix’, mw=4, mh=4, bs=’out’, bgc=[0.18, 0.21, 0.25])
cmds.columnLayout(adjustableColumn=True)
cmds.textField(‘prefixField’, text=’ribbon_’)
cmds.setParent( ‘..’ )
cmds.setParent( ‘..’ )
#Build tab
cmds.frameLayout(l=’Scale Group (optional)’, mw=4, mh=4, bs=’out’, bgc=[0.18, 0.21, 0.25])
cmds.columnLayout(adjustableColumn=True)
cmds.button(label=’Load the scale group’, command=loadScaleGrp)
cmds.textField(‘scaleGrpField’, enable=False)
cmds.setParent( ‘..’ )
cmds.setParent( ‘..’ )
#Build tab
cmds.frameLayout(l=’Setup’, mw=4, mh=4, bs=’out’, bgc=[0.18, 0.21, 0.25])
cmds.columnLayout(adjustableColumn=True)
cmds.text(label=’Define the width of the ribbon:’)
cmds.floatField(‘widthField’, minValue=1.0, value=10.0)
cmds.text(label=’Define the number of joints:’)
cmds.intField(‘jointsField’, minValue=3, value=10)
cmds.setParent( ‘..’ )
cmds.setParent( ‘..’ )
#Build tab
cmds.frameLayout(l=’Create’, mw=4, mh=4, bs=’out’, bgc=[0.18, 0.21, 0.25])
cmds.columnLayout(adjustableColumn=True)
cmds.button(label=’Create the ribbon’, command=createRibbon)
cmds.setParent( ‘..’ )
cmds.setParent( ‘..’ )
cmds.setParent( ‘..’ )

#Show the window
cmds.showWindow(winName)
cmds.window(winName, edit=True, width=378, height=210)

# GENERAL FUNCTION: ADD ATTRIBUTE(S) ON MULTIPLE OBJECTS
def addAttribute(objects=[], longName=”, niceName=”, lock=False, **kwargs):
#For each object
for obj in objects:
#For each attribute
for x in range(0, len(longName)):
#See if a niceName was defined
attrNice = ” if not niceName else niceName[x]
#If the attribute does not exists
if not cmds.attributeQuery(longName[x], node=obj, exists=True):
#Add the attribute
cmds.addAttr(obj, longName=longName[x], niceName=attrNice, **kwargs)
#If lock was set to True
cmds.setAttr((obj + ‘.’ + longName[x]), lock=1) if lock else cmds.setAttr((obj + ‘.’ + longName[x]), lock=0)

# GENERAL FUNCTION: CREATE A CONTROL MADE OUT OF A CURVE
def createCurveCtrl(pos=(0,0,0), name=’curveCtrl’, scale=1, color=6, freezeTransforms=0):
#Create the controller
crvCtrl = cmds.curve(p=[(0,1,0),(0,-1,0),(0,0,0),(0,0,1),(0,0,-1),(0,0,0),(1,0,0),(-1,0,0)], d=1)
crvCtrl = cmds.rename(crvCtrl, name)
#Set the scale
cmds.setAttr((crvCtrl + ‘.scale’), scale, scale, scale)
cmds.makeIdentity(crvCtrl, apply=True, translate=False, rotate=False, scale=True)
#Set the color for the curve
cmds.setAttr((cmds.listRelatives(crvCtrl, shapes=True)[0] + ‘.overrideEnabled’), 1)
cmds.setAttr((cmds.listRelatives(crvCtrl, shapes=True)[0] + ‘.overrideColor’), color)
#If a position was defined
if len(pos) == 3:
#Position the locator
cmds.setAttr((crvCtrl + ‘.translate’), pos[0], pos[1], pos[2])
#If freeze transforms was set to true
if freezeTransforms:
cmds.makeIdentity(crvCtrl, apply=True, translate=True)
#Return the locator
return crvCtrl

# GENERAL FUNCTION: CREATE A FOLLICLE AND ATTACH IT TO A SURFACE
def createFollicle(inputSurface=[], scaleGrp=”, uVal=0.5, vVal=0.5, hide=1, name=’follicle’):
#Create a follicle
follicleShape = cmds.createNode(‘follicle’)
#Get the transform of the follicle
follicleTrans = cmds.listRelatives(follicleShape, parent=True)[0]
#Rename the follicle
follicleTrans = cmds.rename(follicleTrans, name)
follicleShape = cmds.rename(cmds.listRelatives(follicleTrans, c=True)[0], (name + ‘Shape’))
#If the inputSurface is of type ‘nurbsSurface’, connect the surface to the follicle
if cmds.objectType(inputSurface[0]) == ‘nurbsSurface’:
cmds.connectAttr((inputSurface[0] + ‘.local’), (follicleShape + ‘.inputSurface’))
#If the inputSurface is of type ‘mesh’, connect the surface to the follicle
if cmds.objectType(inputSurface[0]) == ‘mesh’:
cmds.connectAttr((inputSurface[0] + ‘.outMesh’), (follicleShape + ‘.inputMesh’))
#Connect the worldMatrix of the surface into the follicleShape
cmds.connectAttr((inputSurface[0] + ‘.worldMatrix[0]‘), (follicleShape + ‘.inputWorldMatrix’))
#Connect the follicleShape to it’s transform
cmds.connectAttr((follicleShape + ‘.outRotate’), (follicleTrans + ‘.rotate’))
cmds.connectAttr((follicleShape + ‘.outTranslate’), (follicleTrans + ‘.translate’))
#Set the uValue and vValue for the current follicle
cmds.setAttr((follicleShape + ‘.parameterU’), uVal)
cmds.setAttr((follicleShape + ‘.parameterV’), vVal)
#Lock the translate/rotate of the follicle
cmds.setAttr((follicleTrans + ‘.translate’), lock=True)
cmds.setAttr((follicleTrans + ‘.rotate’), lock=True)
#If it was set to be hidden, hide the follicle
if hide:
cmds.setAttr((follicleShape + ‘.visibility’), 0)
#If a scale-group was defined and exists
if scaleGrp and cmds.objExists(scaleGrp):
#Connect the scale-group to the follicle
cmds.connectAttr((scaleGrp + ‘.scale’), (follicleTrans + ‘.scale’))
#Lock the scale of the follicle
cmds.setAttr((follicleTrans + ‘.scale’), lock=True)
#Return the follicle and it’s shape
return follicleTrans, follicleShape

# GENERAL FUNCTION: GROUP MULTIPLE OBJECTS
def grpObject(objects=[], snapTrans=1, snapRot=1, keepHi=1, keepTransforms=1, empty=False, name=”, suffix=’_grp’):
#Create a variable to store the groups in
groups = []
#For each object passed in
for obj in objects:
#Create an empty group for the current object
newGrp = cmds.group(empty=True, name=(obj + suffix))
#If a name was specified, rename the group
if name:
cmds.rename(newGrp, name)
#Set the rotateOrder of the current group to the same order as the current object
cmds.setAttr((newGrp + ‘.rotateOrder’), cmds.getAttr(obj + ‘.rotateOrder’))
#If snapTrans was set to true, PointConstraint the group to the current object
if snapTrans:
cmds.delete(cmds.pointConstraint(obj, newGrp))
#If snapRot was set to true, OrientConstraint the group to the current object
if snapRot:
cmds.delete(cmds.orientConstraint(obj, newGrp))
#If keepHi was set to true
if keepHi:
#Get the first parent of the current object
currParent = cmds.listRelatives(obj, parent=True)
#If a parent was found, parent the group in the first parent of the current object
if currParent:
cmds.parent(newGrp, currParent[0])
#If keepTransforms was set to false, Freeze the transformations of the group
if not keepTransforms:
cmds.makeIdentity(newGrp, apply=True, translate=True, rotate=True)
#If empty was set to false, parent the current object into the group
if not empty:
cmds.parent(obj, newGrp)
#Append the current group into the result
groups.append(newGrp)
#Return the groups
return groups

# GENERAL FUNCTION: CREATE A NONLINEAR DEFORMER
def nonlinearDeformer(objects=[], defType=None, lowBound=-1, highBound=1, translate=None, rotate=None, name=’nonLinear’):
#If something went wrong or the type is not valid, raise exception
if not objects or defType not in ['bend','flare','sine','squash','twist','wave']:
raise Exception, “function: ‘nonlinearDeformer’ – Make sure you specified a mesh and a valid deformer”
#Create and rename the deformer
nonLinDef = cmds.nonLinear(objects[0], type=defType, lowBound=lowBound, highBound=highBound)
nonLinDef[0] = cmds.rename(nonLinDef[0], (name + ‘_’ + defType + ‘_def’))
nonLinDef[1] = cmds.rename(nonLinDef[1], (name + ‘_’ + defType + ‘Handle’))
#If translate was specified, set the translate
if translate:
cmds.setAttr((nonLinDef[1] + ‘.translate’), translate[0], translate[1], translate[2])
#If rotate was specified, set the rotate
if rotate:
cmds.setAttr((nonLinDef[1] + ‘.rotate’), rotate[0], rotate[1], rotate[2])
#Return the deformer
return nonLinDef

# GENERAL FUNCTION: SET PIVOT OF OBJECT(S)
def setPivot(objects=[], rotatePivot=1, scalePivot=1, pivot=(0,0,0)):
#Make sure the input is passed on as a list
objects = [objects] if isinstance(objects, (str, unicode)) else objects
#For each object
for obj in objects:
#If rotatePivot was set to True, set the rotatePivot
if rotatePivot:
cmds.xform(obj, worldSpace=True, rotatePivot=pivot)
#If scalePivot was set to True, set the scalePivot
if scalePivot:
cmds.xform(obj, worldSpace=True, scalePivot=pivot)

# SCRIPT FUNCTION: LOAD THE SCALE GROUP
def loadScaleGrp(*args):
#Get the selected object
scaleGrp = cmds.ls(selection=True, type=”transform”)
#Update the scaleGrpField
if scaleGrp:
cmds.textField(‘scaleGrpField’, edit=True, text=scaleGrp[0])

# SCRIPT FUNCTION: CREATE THE RIBBON
def createRibbon(*args):
#Gather information
width = cmds.floatField(‘widthField’, query=True, value=True)
numJoints = cmds.intField(‘jointsField’, query=True, value=True)
prefix = cmds.textField(‘prefixField’, query=True, text=True)
scaleGrp = cmds.textField(‘scaleGrpField’, query=True, text=True)
topPoint = (width/2)
endPoint = (width/2*-1)

#Create the main groups
grpNoTransform = cmds.group(empty=True, name=(prefix + ‘noTransform_grp’))
grpTransform = cmds.group(empty=True, name=(prefix + ‘transform_grp’))
grpCtrl = cmds.group(empty=True, name=(prefix + ‘ctrl_grp’), parent=grpTransform)
grpSurface = cmds.group(empty=True, name=(prefix + ‘surface_grp’), parent=grpTransform)
grpSurfaces = cmds.group(empty=True, name=(prefix + ‘surfaces_grp’), parent=grpNoTransform)
grpDeformers = cmds.group(empty=True, name=(prefix + ‘deformer_grp’), parent=grpNoTransform)
grpFollMain = cmds.group(empty=True, name=(prefix + ‘follicles_skin_grp’), parent=grpNoTransform)
grpFollVolume = cmds.group(empty=True, name=(prefix + ‘follicles_volume_grp’), parent=grpNoTransform)
grpCluster = cmds.group(empty=True, name=(prefix + ‘cluster_grp’), parent=grpNoTransform)
grpMisc = cmds.group(empty=True, name=(prefix + ‘misc_grp’), parent=grpNoTransform)

#Create a NURBS-plane to use as a base
tmpPlane = cmds.nurbsPlane(axis=(0,1,0), width=width, lengthRatio=(1.0 / width), u=numJoints, v=1, degree=3, ch=0)[0]
#Create the NURBS-planes to use in the setup
geoPlane = cmds.duplicate(tmpPlane, name=(prefix + ‘geo’))
geoPlaneTwist = cmds.duplicate(tmpPlane, name=(prefix + ‘twist_blnd_geo’))
geoPlaneSine = cmds.duplicate(tmpPlane, name=(prefix + ‘sine_blnd_geo’))
geoPlaneWire = cmds.duplicate(tmpPlane, name=(prefix + ‘wire_blnd_geo’))
geoPlaneVolume = cmds.duplicate(tmpPlane, name=(prefix + ‘volume_geo’))
#Offset the volume-plane
cmds.setAttr((geoPlaneVolume[0] + ‘.translateZ’), -0.5)
#Delete the base surface
cmds.delete(tmpPlane)

#Create the controllers
ctrlTop = createCurveCtrl(name=(prefix + ‘top_ctrl’), freezeTransforms=1, color=9, pos=(topPoint,0,0))
ctrlMid = createCurveCtrl(name=(prefix + ‘mid_ctrl’), freezeTransforms=1, color=9, pos=(0,0,0))
ctrlEnd = createCurveCtrl(name=(prefix + ‘end_ctrl’), freezeTransforms=1, color=9, pos=(endPoint,0,0))
#Group the controllers
grpTop = grpObject(objects=[ctrlTop], snapTrans=1, keepTransforms=0, keepHi=1, empty=0, suffix=’_grp’)[0]
grpMid = grpObject(objects=[ctrlMid], snapTrans=1, keepTransforms=0, keepHi=1, empty=0, suffix=’_grp’)[0]
grpEnd = grpObject(objects=[ctrlEnd], snapTrans=1, keepTransforms=0, keepHi=1, empty=0, suffix=’_grp’)[0]
#PointConstraint the midCtrl between the top/end
midConst = cmds.pointConstraint(ctrlTop, ctrlEnd, grpMid)

#Add attributes: Twist/Roll attributes
addAttribute(objects=[ctrlTop,ctrlMid,ctrlEnd],longName=['twistSep'],niceName=['---------------'],at=”enum”,en=’Twist’,lock=1,k=True)
addAttribute(objects=[ctrlTop,ctrlEnd],longName=['twist'],at=”float”,k=True)
addAttribute(objects=[ctrlTop,ctrlEnd],longName=['twistOffset'],at=”float”,k=True)
addAttribute(objects=[ctrlTop,ctrlEnd],longName=['affectToMid'],at=”float”,min=0, max=10,dv=10,k=True)
addAttribute(objects=[ctrlMid],longName=['roll'],at=”float”,k=True)
addAttribute(objects=[ctrlMid],longName=['rollOffset'],at=”float”,k=True)
#Add attributes: Volume attributes
addAttribute(objects=[ctrlMid],longName=['volumeSep'],niceName=['---------------'],at=”enum”,en=’Volume’,lock=1,k=True)
addAttribute(objects=[ctrlMid],longName=['volume'],at=”float”,min=-1,max=1,k=True)
addAttribute(objects=[ctrlMid],longName=['volumeMultiplier'],at=”float”,min=1,dv=3,k=True)
addAttribute(objects=[ctrlMid],longName=['startDropoff'],at=”float”,min=0, max=1, dv=1,k=True)
addAttribute(objects=[ctrlMid],longName=['endDropoff'],at=”float”,min=0, max=1, dv=1, k=True)
addAttribute(objects=[ctrlMid],longName=['volumeScale'],at=”float”,min=endPoint*0.9, max=topPoint*2,k=True)
addAttribute(objects=[ctrlMid],longName=['volumePosition'],min=endPoint,max=topPoint,at=”float”,k=True)
#Add attributes: Sine attributes
addAttribute(objects=[ctrlMid], longName=['sineSep'], niceName=['---------------'], attributeType=’enum’, en=”Sine:”, keyable=True, lock=1)
addAttribute(objects=[ctrlMid], longName=['amplitude'], attributeType=”float”, keyable=True)
addAttribute(objects=[ctrlMid], longName=['offset'], attributeType=”float”, keyable=True)
addAttribute(objects=[ctrlMid], longName=['twist'], attributeType=”float”, keyable=True)
addAttribute(objects=[ctrlMid], longName=['sineLength'], min=0.1, dv=2, attributeType=”float”, keyable=True)
#Add attributes: Extra attributes
addAttribute(objects=[ctrlMid],longName=['extraSep'],niceName=['---------------'],at=”enum”,en=’Extra’,lock=1,k=True)
addAttribute(objects=[ctrlMid],longName=['showExtraCtrl'],at=”enum”,en=’Hide:Show:’,k=True)
cmds.setAttr((ctrlMid + ‘.showExtraCtrl’), 1)

#Create deformers: Twist deformer, Sine deformer, Squash deformer
twistDef = nonlinearDeformer(objects=[geoPlaneTwist[0]], defType=’twist’, name=geoPlaneTwist[0], lowBound=-1, highBound=1, rotate=(0,0,90))
sineDef = nonlinearDeformer(objects=[geoPlaneSine[0]], defType=’sine’, name=geoPlaneSine[0], lowBound=-1, highBound=1, rotate=(0,0,90))
squashDef = nonlinearDeformer(objects=[geoPlaneVolume[0]], defType=’squash’, name=geoPlaneVolume[0], lowBound=-1, highBound=1, rotate=(0,0,90))
cmds.setAttr((sineDef[0] + ‘.dropoff’), 1)
#Create deformers: Wire deformer
deformCrv = cmds.curve(p=[(topPoint,0,0),(0,0,0),(endPoint,0,0)], degree=2)
deformCrv = cmds.rename(deformCrv, (prefix + ‘ribbon_wire_crv’))
wireDef = cmds.wire(geoPlaneWire, dds=(0,15), wire=deformCrv)
wireDef[0] = cmds.rename(wireDef[0], (geoPlaneWire[0] + ‘_wire’))
#Create deformers: Clusters
clsTop = cmds.cluster((deformCrv + ‘.cv[0:1]‘), relative=1)
clsMid = cmds.cluster((deformCrv + ‘.cv[1]‘), relative=1)
clsEnd = cmds.cluster((deformCrv + ‘.cv[1:2]‘), relative=1)
clsTop[0] = cmds.rename(clsTop[0], (ctrlTop + ‘_top_cluster’))
clsTop[1] = cmds.rename(clsTop[1], (ctrlTop + ‘_top_clusterHandle’))
clsMid[0] = cmds.rename(clsMid[0], (ctrlMid + ‘_mid_cluster’))
clsMid[1] = cmds.rename(clsMid[1], (ctrlMid + ‘_mid_clusterHandle’))
clsEnd[0] = cmds.rename(clsEnd[0], (ctrlEnd + ‘_end_cluster’))
clsEnd[1] = cmds.rename(clsEnd[1], (ctrlEnd + ‘_end_clusterHandle’))
cmds.setAttr((cmds.listRelatives(clsTop[1], type=”shape”)[0] + ‘.originX’), topPoint)
cmds.setAttr((cmds.listRelatives(clsEnd[1], type=”shape”)[0] + ‘.originX’), endPoint)
setPivot(objects=[clsTop[1]], rotatePivot=1, scalePivot=1, pivot=(topPoint,0,0))
setPivot(objects=[clsEnd[1]], rotatePivot=1, scalePivot=1, pivot=(endPoint,0,0))
cmds.percent(clsTop[0], (deformCrv + ‘.cv[1]‘), v=0.5)
cmds.percent(clsEnd[0], (deformCrv + ‘.cv[1]‘), v=0.5)
posTopPma = cmds.shadingNode(‘plusMinusAverage’, asUtility=1, name = (prefix + ‘top_ctrl_pos_pma’))
cmds.connectAttr((ctrlTop + ‘.translate’), (posTopPma + ‘.input3D[0]‘))
cmds.connectAttr((grpTop + ‘.translate’), (posTopPma + ‘.input3D[1]‘))
posEndPma = cmds.shadingNode(‘plusMinusAverage’, asUtility=1, name = (prefix + ‘end_ctrl_pos_pma’))
cmds.connectAttr((ctrlEnd + ‘.translate’), (posEndPma + ‘.input3D[0]‘))
cmds.connectAttr((grpEnd + ‘.translate’), (posEndPma + ‘.input3D[1]‘))
cmds.connectAttr((posTopPma + ‘.output3D’), (clsTop[1] + ‘.translate’))
cmds.connectAttr((ctrlMid + ‘.translate’), (clsMid[1] + ‘.translate’))
cmds.connectAttr((posEndPma + ‘.output3D’), (clsEnd[1] + ‘.translate’))
#Create deformers: Blendshape
blndDef = cmds.blendShape(geoPlaneWire[0], geoPlaneTwist[0], geoPlaneSine[0], geoPlane[0], name=(prefix + ‘blendShape’),weight=[(0,1),(1,1),(2,1)])

#Twist deformer: Sum the twist and the roll
sumTopPma = cmds.shadingNode(‘plusMinusAverage’, asUtility=1, name = (prefix + ‘twist_top_sum_pma’))
cmds.connectAttr((ctrlTop + ‘.twist’), (sumTopPma + ‘.input1D[0]‘))
cmds.connectAttr((ctrlTop + ‘.twistOffset’), (sumTopPma + ‘.input1D[1]‘))
cmds.connectAttr((ctrlMid + ‘.roll’), (sumTopPma + ‘.input1D[2]‘))
cmds.connectAttr((ctrlMid + ‘.rollOffset’), (sumTopPma + ‘.input1D[3]‘))
cmds.connectAttr((sumTopPma + ‘.output1D’), (twistDef[0] + ‘.startAngle’))
sumEndPma = cmds.shadingNode(‘plusMinusAverage’, asUtility=1, name = (prefix + ‘twist_low_sum_pma’))
cmds.connectAttr((ctrlEnd + ‘.twist’), (sumEndPma + ‘.input1D[0]‘))
cmds.connectAttr((ctrlEnd + ‘.twistOffset’), (sumEndPma + ‘.input1D[1]‘))
cmds.connectAttr((ctrlMid + ‘.roll’), (sumEndPma + ‘.input1D[2]‘))
cmds.connectAttr((ctrlMid + ‘.rollOffset’), (sumEndPma + ‘.input1D[3]‘))
cmds.connectAttr((sumEndPma + ‘.output1D’), (twistDef[0] + ‘.endAngle’))
#Twist deformer: Set up the affect of the deformer
topAffMdl = cmds.shadingNode(‘multDoubleLinear’, asUtility=1, name = (prefix + ‘twist_top_affect_mdl’))
cmds.setAttr((topAffMdl + ‘.input1′), -0.1)
cmds.connectAttr((ctrlTop + ‘.affectToMid’), (topAffMdl + ‘.input2′))
cmds.connectAttr((topAffMdl + ‘.output’), (twistDef[0] + ‘.lowBound’))
endAffMdl = cmds.shadingNode(‘multDoubleLinear’, asUtility=1, name = (prefix + ‘twist_end_affect_mdl’))
cmds.setAttr((endAffMdl + ‘.input1′), 0.1)
cmds.connectAttr((ctrlEnd + ‘.affectToMid’), (endAffMdl + ‘.input2′))
cmds.connectAttr((endAffMdl + ‘.output’), (twistDef[0] + ‘.highBound’))

#Squash deformer: Set up the connections for the volume control
volumeRevfMdl = cmds.shadingNode(‘multDoubleLinear’, asUtility=1, name = (prefix + ‘volume_reverse_mdl’))
cmds.setAttr((volumeRevfMdl + ‘.input1′), -1)
cmds.connectAttr((ctrlMid + ‘.volume’), (volumeRevfMdl + ‘.input2′))
cmds.connectAttr((volumeRevfMdl + ‘.output’), (squashDef[0] + ‘.factor’))
cmds.connectAttr((ctrlMid + ‘.startDropoff’), (squashDef[0] + ‘.startSmoothness’))
cmds.connectAttr((ctrlMid + ‘.endDropoff’), (squashDef[0] + ‘.endSmoothness’))
cmds.connectAttr((ctrlMid + ‘.volumePosition’), (squashDef[1] + ‘.translateX’))
#Squash deformer: Set up the volume scaling
sumScalePma = cmds.shadingNode(‘plusMinusAverage’, asUtility=1, name = (prefix + ‘volume_scale_sum_pma’))
cmds.setAttr((sumScalePma + ‘.input1D[0]‘), topPoint)
cmds.connectAttr((ctrlMid + ‘.volumeScale’), (sumScalePma + ‘.input1D[1]‘))
cmds.connectAttr((sumScalePma + ‘.output1D’), (squashDef[1] + ‘.scaleY’))

#Sine deformer: Set up the connections for the sine
cmds.connectAttr((ctrlMid + ‘.amplitude’), (sineDef[0] + ‘.amplitude’))
cmds.connectAttr((ctrlMid + ‘.offset’), (sineDef[0] + ‘.offset’))
cmds.connectAttr((ctrlMid + ‘.twist’), (sineDef[1] + ‘.rotateY’))
cmds.connectAttr((ctrlMid + ‘.sineLength’), (sineDef[0] + ‘.wavelength’))

#Cleanup: Hierarchy
cmds.parent(geoPlaneWire[0], geoPlaneTwist[0], geoPlaneSine[0], geoPlaneVolume[0], grpSurfaces)
cmds.parent(twistDef[1], sineDef[1], squashDef[1], grpDeformers)
cmds.parent(clsTop[1], clsMid[1], clsEnd[1], grpCluster)
cmds.parent(grpTop, grpMid, grpEnd, grpCtrl)
cmds.parent(geoPlane[0], grpSurface)
cmds.parent(deformCrv, (cmds.listConnections(wireDef[0] + ‘.baseWire[0]‘)[0]), grpMisc)
#Cleanup: Visibility
cmds.hide(grpSurfaces, grpDeformers, grpCluster, grpMisc)
for x in cmds.listConnections(ctrlMid):
cmds.setAttr((x + ‘.isHistoricallyInteresting’), 0)
for y in cmds.listConnections(x):
cmds.setAttr((y + ‘.isHistoricallyInteresting’), 0)

#Update the scale-group
scaleGrp = scaleGrp if scaleGrp else grpTransform
#Create follicles: The main-surface and the volume-surface
for x in range(0, numJoints):
#Declare a variable for the current index
num = str(x + 1)
#Get the normalized position of where to place the current follicle
uVal = ((0.5 / numJoints) * (x + 1) * 2) – ((0.5 / (numJoints * 2)) * 2)
#Create a follicle for the bind-plane and the volume-plane
follicleS = createFollicle(scaleGrp=scaleGrp, inputSurface=cmds.listRelatives(geoPlane[0], type=”shape”), uVal=uVal, name=(prefix + num + ‘_follicle’))
follicleV = createFollicle(scaleGrp=None, inputSurface=cmds.listRelatives(geoPlaneVolume[0], type=”shape”), uVal=uVal, vVal=0, name=(prefix + num + ‘_volume_follicle’))
cmds.parent(follicleS[0], grpFollMain)
cmds.parent(follicleV[0], grpFollVolume)
#Create a joint, controller and a group for the current skin-follicle
cmds.select(clear=True)
follicleJoint = cmds.joint(name=(prefix + num + ‘_jnt’), radius=0.1)
follicleCtrl = cmds.circle(name=(prefix + num + ‘_ctrl’), c=(0,0,0), nr=(1,0,0), sw=360, r=0.5, d=3, s=8, ch=0)[0]
follicleXform = cmds.group(name=(prefix + num + ‘_xform_grp’), empty=True)
cmds.parent(follicleXform, follicleS[0])
cmds.parent(follicleCtrl, follicleXform)
cmds.parent(follicleJoint, follicleCtrl)
cmds.delete(cmds.parentConstraint(follicleS[0], follicleXform))
#Set the color and connect the visibility-switch for the controller
cmds.setAttr((cmds.listRelatives(follicleCtrl, shapes=True)[0] + ‘.overrideEnabled’), 1)
cmds.setAttr((cmds.listRelatives(follicleCtrl, shapes=True)[0] + ‘.overrideColor’), 12)
cmds.connectAttr((ctrlMid + ‘.showExtraCtrl’), (cmds.listRelatives(follicleCtrl, shapes=True)[0] + ‘.visibility’))
#Make the connections for the volume
multMpd = cmds.shadingNode(‘multiplyDivide’, asUtility=1, name = (prefix + num + ‘_multiplier_mpd’))
cmds.connectAttr((ctrlMid + ‘.volumeMultiplier’), (multMpd + ‘.input1Z’))
cmds.connectAttr((follicleV[0] + ‘.translate’), (multMpd + ‘.input2′))
sumPma = cmds.shadingNode(‘plusMinusAverage’, asUtility=1, name = (prefix + num + ‘_volume_sum_pma’))
cmds.connectAttr((multMpd + ‘.outputZ’), (sumPma + ‘.input1D[0]‘))
cmds.setAttr((sumPma + ‘.input1D[1]‘), 1)
cmds.connectAttr((sumPma + ‘.output1D’), (follicleXform + ‘.scaleY’))
cmds.connectAttr((sumPma + ‘.output1D’), (follicleXform + ‘.scaleZ’))

jh_proceduralRibbon()

Download Script

Tutorial: Create a Sine with Nodes in Maya

Tutorial: Create a Sine with Nodes in Maya

Wow! It’s been more than a year since my last post here… Time flies, huh?  :-? Anyway, I’ve gotten a lot of questions about the procedural ribbon from my rigging reel, and I promised I’d do a tutorial on that, I’ll try to get that done next week or so :)

This however, is actually the thing that got me started on the procedural ribbon in the first place. My first thought was to start with getting the sine-function to work with nodes, and then work my way from there. The ribbon turned out totally different from this tutorial though, but I thought this still could be a useful technique to share.

First off, there’s several tutorials out there on how to create a sine-function in Maya, but I still haven’t seen anyone do them with nodes. The main reason you would want to do things with nodes instead of using expressions is that expressions doesn’t always evaluate when you want them to, some times you have to scrub the timeline or playback the scene to force an evaluation of the expression. That can be really cumbersome if you need to adjust attributes that affects the result of the expression without seeing the changes instantly. Also, nodes evaluate way faster than expressions :)

The concept:

This is very basic, and the concept is very well demonstrated here, the tutorial just shows you how you can use this concept on several objects, so that you can control the entire chain of object affected by the sine with one controller.

Even if you know how a sine works, there’s still something to take out of doing it this way in Maya. Instead of dealing with just the math to get this to work (which you do with expressions), you’re dealing with a more practical setup, which can be easily modified to behave however you want it to. So as long as you know the “pattern” of the mathematical function, let your rigging brain take over and do a practical setup in Maya that lets you extract the behaviour of the function.

Here’s an interesting example Disney Research Lab released a little more than a year ago:
Computational Design of Mechanical Characters

The tutorial:

I’m using the translateY to output the sine in the tutorial, but remember that if you for example use this on a joint-chain, you could just connect the output to the rotate of the joints instead.

Script: Measure Rig Performance

Script: Measure Rig Performance

As a rigger you have to get along with the animators, and a good startingpoint for that is to provide them with fast rigs. If the animators have to playblast for each time they want to see the actual animation, they are going to waste a lot of time!

I asked this question over at Rigging Dojo about a year ago:

I often find myself implementing a lot of stuff in a rig, stuff I think the animators may need. As I’m improving my rigs, I get more nodes and more calculations going on to achieve what I want – which in the end may result in performance issues with the rig when animating. I’m trying to find out if there’s some way to measure the performance of a rig-component? I find it really hard to find out which components that slows down my rig, as I have to add and animate all of the rig-components together, and then try to compare the playback-speed, remove components, re-check, etc. How do you guys approach this? Also, is there any documentation of speed differences for expressions vs nodes, extra joints vs corrective blendshapes, skinClusters vs wrapDeformers, constraints vs normal parenting, etc?

I got a lot of interesting answers in the thread, but one in particular from Josh Carey helped a lot. He tipped me off on dgtimer, which does exactly what I asked for. So I’ve just wrapped this into a clean little script.

The Script
The script measures the speed by playing off the scene and evaluating each DG node’s performance, which means that for the object(s) to actually be evaluated you have to animate each component (that you want to be included in the evaluation) over a given timespan, the more frames you calculate from the more accurate the results will be.

jh_measurePerformance

So if you have a rig that runs too slow, you can use this script to see which node(s) that does the hardest impact on the performance. You often find that it’s just a couple of nodes that really takes onto the percentage, stuff like wrap deformers are something you want to avoid (at least in the proxy-rig) :)

Video Demonstration:
I’ve uploaded a video to demonstrate how it can be used, in the video I’m using it to compare normal parenting, direct connections, constraints and skinclusters.

jh_measurePerformance (Measure Scene Performance)

//*************************************************************************************************************
// Title: jh_measurePerformance.mel
// Author: Jorn-Harald Paulsen
// Created: December 7, 2011
// Last Update: June 06, 2013
// Description: Utility to measure the speed of a scene, especially useful for rig-performance tweaks.
//*************************************************************************************************************
// MAIN WINDOW
//*************************************************************************************************************
global proc jh_measurePerformance ()
{
//Delete window if it already exists
if (`window -q -ex jh_measurePerformance`) deleteUI jh_measurePerformance;

//Main Window
window -te 30 -t “Measure Performance” -mxb 0 -s 1 -rtf 0 -mb 0 -mbv 0 -w 350 -h 544 jh_measurePerformance;

//Window content
columnLayout -adjustableColumn true;
text -label “\nUtility to measure performance/evaluation of the scene” -fn boldLabelFont;
separator -w 300 -h 10;
text -label “It measures by playing off the scene, so you should”;
text -label “have animation on the obects you want to evaluate!”;
text -label “Note: Longer playback = More accurate results”;
separator -w 300 -h 15;
text -label “Startframe:”;
intField startFrame;
text -label “Endframe:”;
intField endFrame;
separator -w 300 -h 15;
text -label “Number of nodes to return:”;
intField -v 20 numNodes;
separator -w 300 -h 10;
button -label “Measure performance” -c jh_evaluate;
textField -text “Time taken: ” -ed 0 timeField;
textField -text “FPS: ” -ed 0 fpsField;
textScrollList -ams 0 -h 250 -sc jh_selNode evaluatedNodes;
separator -w 300 -h 15;
text -label “Filter by nodetype” -fn boldLabelFont;
textField -text “” nodeField;
button -label “Filter” -c jh_filterResult;
separator -w 300 -h 15;
button -label “Choose where to save the file” -c jh_getExportDir;
textField -en 0 evalExportField;
button -label “Export the results” -c jh_exportToFile;
separator -w 300 -h 15;
//Set the startFrame/endFrame min/max to the timeline’s min/max
intField -e -v `playbackOptions -q -min` startFrame;
intField -e -v `playbackOptions -q -max` endFrame;
//Create the window
window -e -w 350 -h 544 jh_measurePerformance;
showWindow jh_measurePerformance;
}

global proc jh_evaluate()
{
//Remove all items in the textScrollList
textScrollList -e -ra evaluatedNodes;
//Get the frames to playback
int $min = `intField -q -v startFrame`;
int $max = `intField -q -v endFrame`;
//Set the timeSlider to the min/max values
playbackOptions -min $min -max $max;
//Get the number of frames to playback
int $frames = $max – $min;
//Get the number of nodes to return
int $nodesCount = `intField -q -v numNodes`;

//Set the playBack to Free/Play Every Frame
playbackOptions -e -playbackSpeed 0 -maxPlaybackSpeed 0;
//Set the timeSlider to the startFrame
currentTime `playbackOptions -q -min`;
//Reset the dgtimer
dgtimer -on -reset;
//Play the scene (don’t loop)
play -wait;
//Turn off the dgtimer
dgtimer -off;
//Stor the result of the dgTimer
string $evalResult[] = `dgtimer -outputFile “MEL” -maxDisplay $nodesCount -query`;
//Extract the elapsed time and the FPS
string $tempToken[];
tokenize $evalResult[12] “: ” $tempToken;
float $time = $tempToken[3];
float $fps = ($frames / $time);
//Update the textField for time and FPS
textField -e -text (“Time taken: ” + $tempToken[3] + ” seconds”) timeField;
textField -e -text (“FPS: ” + $fps) fpsField;
//For each returned node
for($a = 40; $a < (40 + $nodesCount); $a++)
{
//Remove all of the spaces between each element
string $el[];
tokenize $evalResult[$a] ” ” $el;
//If the result has a % at index 3
if (`gmatch $el[3] “*%”` == 1)
{
//Create a separator string
string $sep = ” | “;
//Generate the string to put into the textScrollList
string $string = ($el[0] + $sep + $el[3] + $sep + $el[7] + $sep + $el[8]);
//Update the textScrollList
textScrollList -e -append $string evaluatedNodes;
}
}
//Get the actual number of elements returned
int $returnedElements = size(`textScrollList -q -ai evaluatedNodes`);
//Update the node count to the actual number
intField -e -v $returnedElements numNodes;
//Print the result
print $evalResult;
print “\n\n\nSee the script editor for details!\n”;
}

global proc jh_selNode()
{
//Get the selected item in the textScrollList
string $selItem[] = `textScrollList -q -si evaluatedNodes`;
//Extract the elapsed time and the FPS
string $tempToken[];
tokenize $selItem[0] “|” $tempToken;
//If the object exists
if(objExists($tempToken[3]) == 1)
{
//Select the item
select -r $tempToken[3];
//Print information
print (“\nSelected: ” + $tempToken[3] + “\n”);
}
}

global proc jh_filterResult()
{
//Get the text to filter from
string $filter = `textField -q -text nodeField`;
//Get all of the items in the textScrollList
string $allItems[] = `textScrollList -q -ai evaluatedNodes`;
//Remove all items in the textScrollList
textScrollList -e -ra evaluatedNodes;
//For each item in the textScrollList
for($item in $allItems)
{
//Separate the string
string $tempToken[];
tokenize $item ” | ” $tempToken;
//If the current element matches the filter-tekst, add it to the textScrollList
if (`gmatch $tempToken[2] $filter` == 1) textScrollList -e -append $item evaluatedNodes;
}
//Get the actual number of elements returned
int $returnedElements = size(`textScrollList -q -ai evaluatedNodes`);
//If no elements was returned
if($returnedElements == 0)
{
//Add the original elements back into the textScrollList
for($element in $allItems) textScrollList -e -append $element evaluatedNodes;
//Print warning message
warning “\nCould not find any matching items!\n”;
}
//Else, print information
else print (“A total of ” + $returnedElements + ” nodes was found matching \”” + $filter + “\”\n”);
}

global proc jh_getExportDir()
{
//Open the file-dialog, and get the results from it
string $getExportDir = `fileDialog -m 1 -dm “*.mel”`;
//Put the directory in the textField
textField -e -text $getExportDir evalExportField;
}

global proc jh_exportToFile()
{
//Get all of the items in the textScrollList
string $allItems[] = `textScrollList -q -ai evaluatedNodes`;
//Get the directory for where to store the animation
string $exportDir = `textField -q -text evalExportField`;
//If a directory wasn’t defined, print error
if($exportDir == “”) error “\nYou need to define a directory to store the animation in!\n”;

//Create and open the storefile for writing
int $fileId = `fopen $exportDir “w”`;
//For each item in the textScrollList, print the item to the file
for($item in $allItems) fprint $fileId ($item + “\n”);
//Close the file for writing
fclose $fileId;
//Print information
print “\nDone!”;
}

jh_measurePerformance;

Download Script

Rig: Rigging Reel

Rigging Reel 2013

Download Rig Breakdown

Rig: Procedural Sine

Rig: Procedural Sine

When animating sea-creatures I found that a sine deformation was invaluable to get the dynamic flow that most of those creatures naturally had. Normally I’ve done this by using expressions, but I hate dealing with expressions when animating as you have to play the animation to evaluate and see the actual result of the expression.

So I’ve been experimenting a little on my own to see if it’s possible to achieve this with the standard nodes within Maya, and as Maya actually do have a sine deformer, I’ve been looking at ways to extract that behaviour so that it can be applied to joints.

Here’s the result:

It’s amazing what you can do with rivets, that’s what I used to extract the sine behaviour :)

Script: Cartoony Wheel Rig v2

Script: Cartoony Wheel Rig v2

So, I’ve finally started to look into Python :) I haven’t taken the OOP functionality into use yet, I’m just starting off by getting used to the syntax before I dive into classes and stuff. On that note, I recommend checking out Zeth Willie‘s video: The basics of using Classes and OOP in Maya/Python. That was the kick in the butt that I needed to get off the fence and try something other than MEL, quite refreshing actually!

So the first version of the Cartoony Wheel Rig looked to work ok when playing around with the rig, but when doing some actual animation I wasn’t too happy with it. There’s particularly two things that I wanted to fix in the next version, the first thing is that you were totally limited to the lattice, which means that you couldn’t hit a specific shape unless the lattice allowed for it. The second issue was that you couldn’t do big things with just one controller, I would always end up having to move/rotate three or more controllers to hit each shape, which means slower workflow.

wheelScript_r2_img

So for this version I added the possibility to actually add blendShapes to the wheel while maintining the shape when spinning the wheel, so if the rig can’t reach that specific shape you want, you can just add it yourselves. I also got rid of the motionPath-controllers, they were a bit messy and the rotations didn’t work too well. I think this rig will work a lot better than the previous, but I’ll do some test-animations with it in a while to see how well it works.

Here’s the script in action:

This version is a bit cleaner than the first version, though it’s a bit slower because of the wrap-deformer. I’ve added an option to turn on a proxy-object through main-controller so that it evaluates a bit faster.

If you have any feedback/critique I’ll be more than happy to hear about it :)

jh_wheelRigger (Cartoony Wheel Rig)


# ************************************************************************************************************
# Title: jh_wheelRigger.py
# Author: Jørn-Harald Paulsen
# Created: February 06, 2013
# Last Update: February 09, 2013
# Description: Utility to rig wheels with the possibility to use blendShapes.
# ************************************************************************************************************

#Import everything in maya as the namespace “cmds”
import maya.cmds as cmds
#Import the math module
import math

# ************************************************************************************************************
# FUNCTION: MAIN WINDOW
# ************************************************************************************************************
def jh_wheelRiggerUI():
#If the window already exists, delete it
if cmds.windowPref(“jh_wheelRigWindow”, exists=True):
cmds.windowPref(“jh_wheelRigWindow”, remove=True)
if cmds.window(“jh_wheelRigWindow”, exists=True):
cmds.deleteUI(“jh_wheelRigWindow”, wnd=True)
#Create the window
window = cmds.window(“jh_wheelRigWindow”, title=”Wheel Rigger”, width=370, height=296 )
#Create a main layout
mainLayout = cmds.columnLayout(adjustableColumn=True, width=370, height=296)
#Create the content
cmds.separator(style=’single’)
cmds.text(“\nMake sure your wheel is pointing in Z, so if it were to roll”, font=’boldLabelFont’)
cmds.text(“it would roll forward towards positive Z. Also make sure that”, font=’boldLabelFont’)
cmds.text(“your wheel-geo is grouped, and has it’s pivot in the center.”, font=’boldLabelFont’)
cmds.separator(style=’single’, height=40)
cmds.text(“Enter the prefix for the wheel (example: car_l_front_):”)
cmds.textField(“prefixField”, enable=True)
cmds.separator(style=’single’, height=40)
cmds.button(‘Load the wheelGeo-group’, command=jh_loadWheelGeo)
cmds.textField(“wheelGeoField”, enable=False)
cmds.separator(style=’single’, height=40)
cmds.button(“Rig The Wheel”, command=jh_rigWheel)
cmds.separator(style=’single’, height=20)
#Open the window
cmds.showWindow(window)

# ************************************************************************************************************
# FUNCTION: LOAD THE WHEEL
# ************************************************************************************************************
def jh_loadWheelGeo(*args):
#Clear the textField
cmds.textField(“wheelGeoField”, edit=True, text=””)
#Get the selected object
selObj = cmds.ls(sl=True,type=”transform”)
#If the selection isn’t empty
if(len(selObj) != 0):
#Update the textField
cmds.textField(“wheelGeoField”, edit=True, text=selObj[0])
#Print information
print “Loaded %s.” % (selObj[0]),

# ************************************************************************************************************
# FUNCTION: RIG THE WHEEL
# ************************************************************************************************************
def jh_rigWheel(*args):
# ************************************************************************************************************
# GATHER SOME DATA
# ************************************************************************************************************
#Get the prefix
prefix = cmds.textField(“prefixField”, query=True, text=True) + “wheel_”
#Get the wheelGeo
wheelGeo = cmds.textField(“wheelGeoField”, query=True, text=True)
#Get the boundingBox of the wheel
boundingBox = cmds.xform( wheelGeo, query=True, boundingBox=True )
#Get the radius of the wheel
wheelRadius = abs((boundingBox[2] – boundingBox[5]) / 2)
#Get the height of the wheel
wheelHeight = abs(boundingBox[2] – boundingBox[5])
#Get the width of the wheel
wheelWidth = abs(boundingBox[0] – boundingBox[3])

# ************************************************************************************************************
# CREATE THE GROUPS NEEDED
# ************************************************************************************************************
geoGrp = cmds.group( empty=True, name=prefix + “geo_grp” )
ctrlGrp = cmds.group( empty=True, name=prefix + “ctrl_grp” )
ctrlDeformGrp = cmds.group( empty=True, name=prefix + “deform_ctrl_grp” )
clusterGrp = cmds.group( empty=True, name=prefix + “cluster_grp” )
latticeGrp = cmds.group( empty=True, name=prefix + “lattice_grp” )
noTransformGrp = cmds.group( empty=True, name=prefix + “noTransform_grp” )
latt1ClsTopGrp = cmds.group( empty=True, name=prefix + “top1_cluster_grp” )
latt1ClsLowGrp = cmds.group( empty=True, name=prefix + “low1_cluster_grp” )
latt2ClsTopGrp = cmds.group( empty=True, name=prefix + “top2_cluster_grp” )
latt2ClsMidGrp = cmds.group( empty=True, name=prefix + “mid2_cluster_grp” )
latt2ClsLowGrp = cmds.group( empty=True, name=prefix + “low2_cluster_grp” )
topCtrlGrp = cmds.group( empty=True, name=prefix + “top_ctrl_grp” )
midCtrlGrp = cmds.group( empty=True, name=prefix + “mid_ctrl_grp” )
lowCtrlGrp = cmds.group( empty=True, name=prefix + “low_ctrl_grp” )
posCtrlGrp = cmds.group( empty=True, name=prefix + “main_ctrl_grp” )
rotCtrlGrp = cmds.group( empty=True, name=prefix + “rotate_ctrl_grp” )
topExtraMicroGrp = cmds.group( empty=True, name=prefix + “top_extra_micro_ctrl_grp” )
lowExtraMicroGrp = cmds.group( empty=True, name=prefix + “low_extra_micro_ctrl_grp” )

# ************************************************************************************************************
# CREATE AND POSITION THE PROXY-WHEEL AND THE DEFORMER-WHEELS
# ************************************************************************************************************
#Create the proxy-wheel
proxyWheel = cmds.polyCylinder(name=prefix + “proxy_geo”, sx=32, sy=1, sz=4, ax=(1,0,0), ch=0)
#Create a lattice for the proxy-wheel
lattice1 = cmds.lattice( proxyWheel, divisions=(2, 2, 2), ldivisions=(2, 2, 2), objectCentered=True )
#Position the proxy-wheel at the original wheel
cmds.move( boundingBox[3], boundingBox[1], boundingBox[2], lattice1[1] + “.pt[1][0][0]” )
cmds.move( boundingBox[0], boundingBox[1], boundingBox[2], lattice1[1] + “.pt[0][0][0]” )
cmds.move( boundingBox[3], boundingBox[1], boundingBox[5], lattice1[1] + “.pt[1][0][1]” )
cmds.move( boundingBox[0], boundingBox[1], boundingBox[5], lattice1[1] + “.pt[0][0][1]” )
cmds.move( boundingBox[0], boundingBox[4], boundingBox[2], lattice1[1] + “.pt[0][1][0]” )
cmds.move( boundingBox[3], boundingBox[4], boundingBox[2], lattice1[1] + “.pt[1][1][0]” )
cmds.move( boundingBox[0], boundingBox[4], boundingBox[5], lattice1[1] + “.pt[0][1][1]” )
cmds.move( boundingBox[3], boundingBox[4], boundingBox[5], lattice1[1] + “.pt[1][1][1]” )
#Delete history and center pivot of the proxy-wheel
cmds.delete( proxyWheel, constructionHistory=True )
cmds.xform( proxyWheel, centerPivots=True )
#Create the deformer-wheels
deformWheel = cmds.duplicate( proxyWheel, returnRootsOnly=True, name=prefix + “deformInput_geo” )
latt1Wheel = cmds.duplicate( proxyWheel, returnRootsOnly=True, name=prefix + “lattice_1_geo” )
latt2Wheel = cmds.duplicate( proxyWheel, returnRootsOnly=True, name=prefix + “lattice_2_geo” )

# ************************************************************************************************************
# CREATE THE CONTROLLERS FOR THE WHEEL
# ************************************************************************************************************
#Create the mid-controller for the wheel
midCtrl = cmds.curve( p=[(-1,0,1),(1,0,1),(1,0,-1),(-1,0,-1),(-1,0,1)], degree=1 )
#Create a the controller for the wheel rotation/position
posCtrl = cmds.circle( radius=wheelRadius, nr=(1,0,0), center=(0,0,0), degree=3, sections=32, ch=0 )
#Create a the controller for the wheel rotation
rotCtrl = cmds.circle( radius=wheelRadius / 1.5, nr=(1,0,0), center=(0,0,0), degree=3, sections=32, ch=0 )
#Create the deform-controller for the wheel
wheelCtrl = cmds.curve( p=[(0,0,1),(0,0.4,0.9),(0,0.7,0.7),(0,0.9,0.4),(0,1,0),(-0.4,0.9,0),(-0.7,0.7,0),
(-0.9,0.4,0),(-1,0,0),(-0.9,-0.4,0),(-0.7,-0.7,0),(-0.4,-0.9,0),(0,-1,0),(0,-0.9,0.4),
(0,-0.7,0.7),(0,-0.4,0.9),(0,0,1),(-0.4,0,0.9),(-0.7,0,0.7),(-0.9,0,0.4),(-1,0,0),(-0.9,0,-0.4),
(-0.7,0,-0.7),(-0.4,0,-0.9),(0,0,-1),(0,-0.4,-0.9),(0,-0.7,-0.7),(0,-0.9,-0.4),(0,-1,0),
(0.4,-0.9,0),(0.7,-0.7,0),(0.9,-0.4,0),(1,0,0),(0.9,0,-0.4),(0.7,0,-0.7),(0.4,0,-0.9),
(0,0,-1),(0,0.4,-0.9),(0,0.7,-0.7),(0,0.9,-0.4),(0,1,0),(0.4,0.9,0),(0.7,0.7,0),(0.9,0.4,0),
(1,0,0),(0.9,0,0.4),(0.7,0,0.7),(0.4,0,0.9),(0,0,1)], degree=1 )

#Rename the controllers
midCtrl = cmds.rename( midCtrl, prefix + “mid_ctrl” )
posCtrl[0] = cmds.rename( posCtrl[0], prefix + “main_ctrl” )
rotCtrl[0] = cmds.rename( rotCtrl[0], prefix + “rotate_ctrl” )

#Create the deform-controllers for the upper/lower part of the wheel
cmds.scale( wheelHeight / 8, wheelHeight / 8, wheelHeight / 8, wheelCtrl, relative=True )
topCtrl = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “top_ctrl” )
lowCtrl = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “low_ctrl” )
#Set the rotation orders for the deform-controllers
cmds.setAttr( posCtrl[0] + “.rotateOrder”, 3 )
cmds.setAttr( posCtrlGrp + “.rotateOrder”, 3 )
cmds.setAttr( ctrlDeformGrp + “.rotateOrder”, 3 )
#Create the micro deform-controllers
cmds.scale( 0.3, 0.3, 0.3, wheelCtrl, relative=True )
topCtrl1 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “top_1_micro_ctrl” )
topCtrl2 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “top_2_micro_ctrl” )
topCtrl3 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “top_3_micro_ctrl” )
topCtrl4 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “top_4_micro_ctrl” )
topCtrl5 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “top_5_micro_ctrl” )
topCtrl6 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “top_6_micro_ctrl” )
midCtrl1 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “mid_1_micro_ctrl” )
midCtrl2 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “mid_2_micro_ctrl” )
midCtrl3 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “mid_3_micro_ctrl” )
midCtrl4 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “mid_4_micro_ctrl” )
midCtrl5 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “mid_5_micro_ctrl” )
midCtrl6 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “mid_6_micro_ctrl” )
lowCtrl1 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “low_1_micro_ctrl” )
lowCtrl2 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “low_2_micro_ctrl” )
lowCtrl3 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “low_3_micro_ctrl” )
lowCtrl4 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “low_4_micro_ctrl” )
lowCtrl5 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “low_5_micro_ctrl” )
lowCtrl6 = cmds.duplicate( wheelCtrl, returnRootsOnly=True, name=prefix + “low_6_micro_ctrl” )

#Shape the controller for the wheel rotation/position
for i in range(0,32,2): cmds.scale(0.85,0.85,0.85, posCtrl[0] + “.cv[%d]“%i, r=True)
#Shape the controller for the wheel rotation
cmds.scale( 0.5,0.5,0.5,rotCtrl[0]+”.cv[1]“,rotCtrl[0]+”.cv[9]“,rotCtrl[0]+”.cv[17]“,rotCtrl[0]+”.cv[25]” )
#Scale the mid-controller to the correct size
cmds.scale( wheelWidth / 1.5, wheelHeight / 1.5, wheelHeight / 1.5, midCtrl )

# ************************************************************************************************************
# POSITION THE CONTROLLERS
# ************************************************************************************************************
#Position all of the controllers at the center of the wheel
cmds.delete(cmds.pointConstraint( proxyWheel, midCtrl ) )
cmds.delete(cmds.pointConstraint( proxyWheel, posCtrl[0] ) )
cmds.delete(cmds.pointConstraint( proxyWheel, rotCtrl[0] ) )
cmds.delete(cmds.pointConstraint( proxyWheel, topCtrl[0] ) )
cmds.delete(cmds.pointConstraint( proxyWheel, lowCtrl[0] ) )
#Position the upper/lower deform-controllers
cmds.move( 0, (wheelHeight / 2), 0, topCtrl[0], r=True, os=True, wd=True )
cmds.move( 0, ((wheelHeight / 2) * -1), 0, lowCtrl[0], r=True, os=True, wd=True )
#Position the micro-controllers
cmds.move( boundingBox[3], boundingBox[4], boundingBox[2], topCtrl1[0] )
cmds.move( boundingBox[0], boundingBox[4], boundingBox[2], topCtrl2[0] )
cmds.move( boundingBox[3], boundingBox[4], boundingBox[5], topCtrl3[0] )
cmds.move( boundingBox[0], boundingBox[4], boundingBox[5], topCtrl4[0] )
cmds.move( boundingBox[3], boundingBox[1], boundingBox[2], lowCtrl1[0] )
cmds.move( boundingBox[0], boundingBox[1], boundingBox[2], lowCtrl2[0] )
cmds.move( boundingBox[3], boundingBox[1], boundingBox[5], lowCtrl3[0] )
cmds.move( boundingBox[0], boundingBox[1], boundingBox[5], lowCtrl4[0] )
cmds.delete(cmds.pointConstraint( topCtrl1[0], topCtrl3[0], topCtrl5[0] ) )
cmds.delete(cmds.pointConstraint( topCtrl2[0], topCtrl4[0], topCtrl6[0] ) )
cmds.delete(cmds.pointConstraint( lowCtrl1[0], lowCtrl3[0], lowCtrl5[0] ) )
cmds.delete(cmds.pointConstraint( lowCtrl2[0], lowCtrl4[0], lowCtrl6[0] ) )
cmds.delete(cmds.pointConstraint( topCtrl1[0], lowCtrl1[0], midCtrl1[0] ) )
cmds.delete(cmds.pointConstraint( topCtrl2[0], lowCtrl2[0], midCtrl2[0] ) )
cmds.delete(cmds.pointConstraint( topCtrl3[0], lowCtrl3[0], midCtrl3[0] ) )
cmds.delete(cmds.pointConstraint( topCtrl4[0], lowCtrl4[0], midCtrl4[0] ) )
cmds.delete(cmds.pointConstraint( topCtrl5[0], lowCtrl5[0], midCtrl5[0] ) )
cmds.delete(cmds.pointConstraint( topCtrl6[0], lowCtrl6[0], midCtrl6[0] ) )

#If the wheel is in negative Z, place the controllers on negative Z
if ( cmds.getAttr(midCtrl + “.tx”) < 0 ) == True: cmds.move( ((wheelWidth / 1.5) * -1), 0, 0, posCtrl[0] + “.cv[*]“, r=True, os=True, wd=True ) cmds.move( ((wheelWidth / 1.5) * -1), 0, 0, rotCtrl[0], r=True, os=True, wd=True ) #If the wheel is in positive Z, place the controllers on positive Z if ( cmds.getAttr(midCtrl + “.tx”) > 0 ) == True:
cmds.move( (wheelWidth / 1.5), 0, 0, posCtrl[0] + “.cv[*]“, r=True, os=True, wd=True )
cmds.move( (wheelWidth / 1.5), 0, 0, rotCtrl[0], r=True, os=True, wd=True )
#If the wheel is in neither positive or negative Z, print error
if ( cmds.getAttr(midCtrl + “.tx”) == 0 ) == True:
cmds.error( “You have to have the wheel pointing towards Z” ),

#Position the groups for the deform-controllers
cmds.delete(cmds.pointConstraint( topCtrl[0], topCtrlGrp ) )
cmds.delete(cmds.pointConstraint( posCtrl[0], midCtrlGrp ) )
cmds.delete(cmds.pointConstraint( lowCtrl[0], lowCtrlGrp ) )
cmds.delete(cmds.pointConstraint( posCtrl[0], posCtrlGrp ) )
cmds.delete(cmds.pointConstraint( posCtrl[0], rotCtrlGrp ) )
cmds.delete(cmds.pointConstraint( posCtrl[0], ctrlDeformGrp ) )
cmds.delete(cmds.pointConstraint( posCtrl[0], ctrlGrp ) )

# ************************************************************************************************************
# CLEANUP THE HIERARCHY
# ************************************************************************************************************
#Delete the template-controller
cmds.delete( wheelCtrl )
#Parent the controllers in their groups
cmds.parent( topCtrl[0], topCtrlGrp )
cmds.parent( lowCtrl[0], lowCtrlGrp )
cmds.parent( posCtrl[0], posCtrlGrp )
cmds.parent( rotCtrl[0], rotCtrlGrp )
cmds.parent( midCtrl, midCtrlGrp )
cmds.parent( rotCtrlGrp, posCtrl[0] )
cmds.parent( topCtrl5[0], topCtrl6[0], topExtraMicroGrp )
cmds.parent( lowCtrl5[0], lowCtrl6[0], lowExtraMicroGrp )
cmds.parent( topCtrlGrp, midCtrlGrp, lowCtrlGrp, ctrlDeformGrp )
cmds.parent( ctrlDeformGrp, posCtrlGrp, ctrlGrp )
#Parent the micro-controllers to the macro-controllers
cmds.parent( topCtrl1[0], topCtrl2[0], topCtrl3[0], topCtrl4[0], topExtraMicroGrp, topCtrl[0] )
cmds.parent( lowCtrl1[0], lowCtrl2[0], lowCtrl3[0], lowCtrl4[0], lowExtraMicroGrp, lowCtrl[0] )
cmds.parent( midCtrl1[0], midCtrl2[0], midCtrl3[0], midCtrl4[0], midCtrl5[0], midCtrl6[0], midCtrl )
#Center the controllers pivots
cmds.xform( topExtraMicroGrp, lowExtraMicroGrp, centerPivots=True )
#Freese the transforms of the controllers
cmds.makeIdentity( ctrlGrp, apply=True, t=1, r=1, s=1, n=2 )

# ************************************************************************************************************
# CREATE THE ATTRIBUTES FOR THE CONTROLLERS
# ************************************************************************************************************
cmds.addAttr( topCtrl[0], ln=’extraAttr’, nn=”—————-”, at=”enum”, en=”Extra:”, k=True )
cmds.addAttr( topCtrl[0], ln=’extraCtrl’, at=”enum”, en=”Hide:Show:”, k=True )
cmds.addAttr( topCtrl[0], ln=’wheelSpin’, at=”double”, k=True )
cmds.addAttr( lowCtrl[0], ln=’extraAttr’, nn=”—————-”, at=”enum”, en=”Extra:”, k=True )
cmds.addAttr( lowCtrl[0], ln=’extraCtrl’, at=”enum”, en=”Hide:Show:”, k=True )
cmds.addAttr( lowCtrl[0], ln=’wheelSpin’, at=”double”, k=True )
cmds.addAttr( posCtrl[0], ln=’extraAttr’, nn=”—————-”, at=”enum”, en=”Extra:”, k=True )
cmds.addAttr( posCtrl[0], ln=’wheelSpin’, at=”double”, k=True )
cmds.addAttr( posCtrl[0], ln=’affectArea’, at=”double”, min=0, max=1, k=True )
cmds.addAttr( posCtrl[0], ln=’showProxy’, at=”enum”, en=”No:Yes:”, k=True )
cmds.addAttr( rotCtrl[0], ln=’extraAttr’, nn=”—————-”, at=”enum”, en=”Extra:”, k=True )
cmds.addAttr( rotCtrl[0], ln=’wheelSpin’, at=”double”, k=True )
cmds.addAttr( midCtrl, ln=’extraAttr’, nn=”—————-”, at=”enum”, en=”Extra:”, k=True )
cmds.addAttr( midCtrl, ln=’extraCtrl’, at=”enum”, en=”Hide:Show:”, k=True )
cmds.addAttr( midCtrl, ln=’wheelSpin’, at=”double”, k=True )
cmds.setAttr( topCtrl[0] + “.extraAttr”, lock=True )
cmds.setAttr( lowCtrl[0] + “.extraAttr”, lock=True )
cmds.setAttr( posCtrl[0] + “.extraAttr”, lock=True )
cmds.setAttr( rotCtrl[0] + “.extraAttr”, lock=True )
cmds.setAttr( midCtrl + “.extraAttr”, lock=True )

# ************************************************************************************************************
# RIG THE WHEEL
# ************************************************************************************************************
#Create a lattice for each of the lattice-wheels
lattice1 = cmds.lattice( latt1Wheel, divisions=(2, 2, 2), ldivisions=(2, 2, 2), objectCentered=True )
lattice2 = cmds.lattice( latt2Wheel, divisions=(2, 3, 3), ldivisions=(2, 3, 3), objectCentered=True )
#Rename the lattices
lattice1[0] = cmds.rename( lattice1[0], prefix + “lattice_1_latticeShape” )
lattice1[1] = cmds.rename( lattice1[1], prefix + “lattice_1_lattice” )
lattice1[2] = cmds.rename( lattice1[2], prefix + “lattice_1_lattice_base” )
lattice2[0] = cmds.rename( lattice2[0], prefix + “lattice_2_latticeShape” )
lattice2[1] = cmds.rename( lattice2[1], prefix + “lattice_2_lattice” )
lattice2[2] = cmds.rename( lattice2[2], prefix + “lattice_2_lattice_base” )
#Parent the lattices to the lattice-group
cmds.parent( lattice1[1], lattice1[2], lattice2[1], lattice2[2], latticeGrp )
#Parent the extra cluster-groups to the main cluster-group
cmds.parent( latt1ClsTopGrp, latt1ClsLowGrp, latt2ClsTopGrp, latt2ClsMidGrp, latt2ClsLowGrp, clusterGrp )
#Create the clusters for lattice1
top1cls1 = cmds.cluster( (lattice1[1] + “.pt[1][1][0]“), name=(prefix + “top_1_1_cluster”) )
top1cls2 = cmds.cluster( (lattice1[1] + “.pt[0][1][0]“), name=(prefix + “top_1_2_cluster”) )
top1cls3 = cmds.cluster( (lattice1[1] + “.pt[1][1][1]“), name=(prefix + “top_1_3_cluster”) )
top1cls4 = cmds.cluster( (lattice1[1] + “.pt[0][1][1]“), name=(prefix + “top_1_4_cluster”) )
low1cls1 = cmds.cluster( (lattice1[1] + “.pt[1][0][0]“), name=(prefix + “low_1_1_cluster”) )
low1cls2 = cmds.cluster( (lattice1[1] + “.pt[0][0][0]“), name=(prefix + “low_1_2_cluster”) )
low1cls3 = cmds.cluster( (lattice1[1] + “.pt[1][0][1]“), name=(prefix + “low_1_3_cluster”) )
low1cls4 = cmds.cluster( (lattice1[1] + “.pt[0][0][1]“), name=(prefix + “low_1_4_cluster”) )
#Create the clusters for lattice2
top2cls1 = cmds.cluster( (lattice2[1] + “.pt[1][2][0]“), name=(prefix + “top_2_1_cluster”) )
top2cls2 = cmds.cluster( (lattice2[1] + “.pt[0][2][0]“), name=(prefix + “top_2_2_cluster”) )
top2cls3 = cmds.cluster( (lattice2[1] + “.pt[1][2][2]“), name=(prefix + “top_2_3_cluster”) )
top2cls4 = cmds.cluster( (lattice2[1] + “.pt[0][2][2]“), name=(prefix + “top_2_4_cluster”) )
top2cls5 = cmds.cluster( (lattice2[1] + “.pt[1][2][1]“), name=(prefix + “top_2_5_cluster”) )
top2cls6 = cmds.cluster( (lattice2[1] + “.pt[0][2][1]“), name=(prefix + “top_2_6_cluster”) )
mid2cls1 = cmds.cluster( (lattice2[1] + “.pt[1][1][0]“), name=(prefix + “mid_2_1_cluster”) )
mid2cls2 = cmds.cluster( (lattice2[1] + “.pt[0][1][0]“), name=(prefix + “mid_2_2_cluster”) )
mid2cls3 = cmds.cluster( (lattice2[1] + “.pt[1][1][2]“), name=(prefix + “mid_2_3_cluster”) )
mid2cls4 = cmds.cluster( (lattice2[1] + “.pt[0][1][2]“), name=(prefix + “mid_2_4_cluster”) )
mid2cls5 = cmds.cluster( (lattice2[1] + “.pt[1][1][1]“), name=(prefix + “mid_2_5_cluster”) )
mid2cls6 = cmds.cluster( (lattice2[1] + “.pt[0][1][1]“), name=(prefix + “mid_2_6_cluster”) )
low2cls1 = cmds.cluster( (lattice2[1] + “.pt[1][0][0]“), name=(prefix + “low_2_1_cluster”) )
low2cls2 = cmds.cluster( (lattice2[1] + “.pt[0][0][0]“), name=(prefix + “low_2_2_cluster”) )
low2cls3 = cmds.cluster( (lattice2[1] + “.pt[1][0][2]“), name=(prefix + “low_2_3_cluster”) )
low2cls4 = cmds.cluster( (lattice2[1] + “.pt[0][0][2]“), name=(prefix + “low_2_4_cluster”) )
low2cls5 = cmds.cluster( (lattice2[1] + “.pt[1][0][1]“), name=(prefix + “low_2_5_cluster”) )
low2cls6 = cmds.cluster( (lattice2[1] + “.pt[0][0][1]“), name=(prefix + “low_2_6_cluster”) )
#Parent the clusters in the cluster-group
cmds.parent( top1cls1[1], top1cls2[1], top1cls3[1], top1cls4[1], latt1ClsTopGrp )
cmds.parent( low1cls1[1], low1cls2[1], low1cls3[1], low1cls4[1], latt1ClsLowGrp )
cmds.parent( top2cls1[1], top2cls2[1], top2cls3[1], top2cls4[1], top2cls5[1], top2cls6[1], latt2ClsTopGrp )
cmds.parent( mid2cls1[1], mid2cls2[1], mid2cls3[1], mid2cls4[1], mid2cls5[1], mid2cls6[1], latt2ClsMidGrp )
cmds.parent( low2cls1[1], low2cls2[1], low2cls3[1], low2cls4[1], low2cls5[1], low2cls6[1], latt2ClsLowGrp )
#Hide the clusterGrp, latticeGrp and the deformer-wheels
cmds.hide( clusterGrp, latticeGrp, latt1Wheel, latt2Wheel, deformWheel )
#ParentConstraint and ScaleConstraint the clusters to the main controllers
cmds.parentConstraint( topCtrl[0], latt1ClsTopGrp, mo=True )
cmds.parentConstraint( topCtrl[0], latt2ClsTopGrp, mo=True )
cmds.parentConstraint( lowCtrl[0], latt1ClsLowGrp, mo=True )
cmds.parentConstraint( lowCtrl[0], latt2ClsLowGrp, mo=True )
cmds.parentConstraint( midCtrl, latt2ClsMidGrp, mo=True )
cmds.scaleConstraint( topCtrl[0], latt1ClsTopGrp )
cmds.scaleConstraint( topCtrl[0], latt2ClsTopGrp )
cmds.scaleConstraint( lowCtrl[0], latt1ClsLowGrp )
cmds.scaleConstraint( lowCtrl[0], latt2ClsLowGrp )
cmds.scaleConstraint( midCtrl, latt2ClsMidGrp )
#ParentConstraint the deformer-controls to the main controller
cmds.parentConstraint( posCtrl[0], ctrlDeformGrp, mo=True )
#Connect the lattice-rigged wheels into the deformedInput-mesh
wheelBlnd = cmds.blendShape( latt1Wheel, latt2Wheel, deformWheel, name=(prefix + “blendShape”) )

# ************************************************************************************************************
# SET UP THE CONNECTIONS FOR THE CONTROLLERS
# ************************************************************************************************************
#Connect the micro controllers directly to the clusters
cmds.connectAttr( (topCtrl1[0] + “.translate”), (top1cls1[1] + “.translate”) )
cmds.connectAttr( (topCtrl2[0] + “.translate”), (top1cls2[1] + “.translate”) )
cmds.connectAttr( (topCtrl3[0] + “.translate”), (top1cls3[1] + “.translate”) )
cmds.connectAttr( (topCtrl4[0] + “.translate”), (top1cls4[1] + “.translate”) )
cmds.connectAttr( (topCtrl1[0] + “.translate”), (top2cls1[1] + “.translate”) )
cmds.connectAttr( (topCtrl2[0] + “.translate”), (top2cls2[1] + “.translate”) )
cmds.connectAttr( (topCtrl3[0] + “.translate”), (top2cls3[1] + “.translate”) )
cmds.connectAttr( (topCtrl4[0] + “.translate”), (top2cls4[1] + “.translate”) )
cmds.connectAttr( (topCtrl5[0] + “.translate”), (top2cls5[1] + “.translate”) )
cmds.connectAttr( (topCtrl6[0] + “.translate”), (top2cls6[1] + “.translate”) )
cmds.connectAttr( (midCtrl1[0] + “.translate”), (mid2cls1[1] + “.translate”) )
cmds.connectAttr( (midCtrl2[0] + “.translate”), (mid2cls2[1] + “.translate”) )
cmds.connectAttr( (midCtrl3[0] + “.translate”), (mid2cls3[1] + “.translate”) )
cmds.connectAttr( (midCtrl4[0] + “.translate”), (mid2cls4[1] + “.translate”) )
cmds.connectAttr( (midCtrl5[0] + “.translate”), (mid2cls5[1] + “.translate”) )
cmds.connectAttr( (midCtrl6[0] + “.translate”), (mid2cls6[1] + “.translate”) )
cmds.connectAttr( (lowCtrl1[0] + “.translate”), (low1cls1[1] + “.translate”) )
cmds.connectAttr( (lowCtrl2[0] + “.translate”), (low1cls2[1] + “.translate”) )
cmds.connectAttr( (lowCtrl3[0] + “.translate”), (low1cls3[1] + “.translate”) )
cmds.connectAttr( (lowCtrl4[0] + “.translate”), (low1cls4[1] + “.translate”) )
cmds.connectAttr( (lowCtrl1[0] + “.translate”), (low2cls1[1] + “.translate”) )
cmds.connectAttr( (lowCtrl2[0] + “.translate”), (low2cls2[1] + “.translate”) )
cmds.connectAttr( (lowCtrl3[0] + “.translate”), (low2cls3[1] + “.translate”) )
cmds.connectAttr( (lowCtrl4[0] + “.translate”), (low2cls4[1] + “.translate”) )
cmds.connectAttr( (lowCtrl5[0] + “.translate”), (low2cls5[1] + “.translate”) )
cmds.connectAttr( (lowCtrl6[0] + “.translate”), (low2cls6[1] + “.translate”) )
#Connect the visibility of the controllers
cmds.connectAttr( (topCtrl[0] + “.extraCtrl”), (topCtrl1[0] + “.visibility”) )
cmds.connectAttr( (topCtrl[0] + “.extraCtrl”), (topCtrl2[0] + “.visibility”) )
cmds.connectAttr( (topCtrl[0] + “.extraCtrl”), (topCtrl3[0] + “.visibility”) )
cmds.connectAttr( (topCtrl[0] + “.extraCtrl”), (topCtrl4[0] + “.visibility”) )
cmds.connectAttr( (topCtrl[0] + “.extraCtrl”), (topCtrl5[0] + “.visibility”) )
cmds.connectAttr( (topCtrl[0] + “.extraCtrl”), (topCtrl6[0] + “.visibility”) )
cmds.connectAttr( (midCtrl + “.extraCtrl”), (midCtrl1[0] + “.visibility”) )
cmds.connectAttr( (midCtrl + “.extraCtrl”), (midCtrl2[0] + “.visibility”) )
cmds.connectAttr( (midCtrl + “.extraCtrl”), (midCtrl3[0] + “.visibility”) )
cmds.connectAttr( (midCtrl + “.extraCtrl”), (midCtrl4[0] + “.visibility”) )
cmds.connectAttr( (midCtrl + “.extraCtrl”), (midCtrl5[0] + “.visibility”) )
cmds.connectAttr( (midCtrl + “.extraCtrl”), (midCtrl6[0] + “.visibility”) )
cmds.connectAttr( (lowCtrl[0] + “.extraCtrl”), (lowCtrl1[0] + “.visibility”) )
cmds.connectAttr( (lowCtrl[0] + “.extraCtrl”), (lowCtrl2[0] + “.visibility”) )
cmds.connectAttr( (lowCtrl[0] + “.extraCtrl”), (lowCtrl3[0] + “.visibility”) )
cmds.connectAttr( (lowCtrl[0] + “.extraCtrl”), (lowCtrl4[0] + “.visibility”) )
cmds.connectAttr( (lowCtrl[0] + “.extraCtrl”), (lowCtrl5[0] + “.visibility”) )
cmds.connectAttr( (lowCtrl[0] + “.extraCtrl”), (lowCtrl6[0] + “.visibility”) )
cmds.connectAttr( (posCtrl[0] + “.affectArea”), (topExtraMicroGrp + “.visibility”) )
cmds.connectAttr( (posCtrl[0] + “.affectArea”), (lowExtraMicroGrp + “.visibility”) )
cmds.connectAttr( (posCtrl[0] + “.affectArea”), (midCtrl + “.visibility”) )
#Set up the SDKs for the blending of the blendShapes
cmds.setDrivenKeyframe( (wheelBlnd[0] + “.” + latt1Wheel[0]), cd=(posCtrl[0] + “.affectArea”), dv=0, v=1 )
cmds.setDrivenKeyframe( (wheelBlnd[0] + “.” + latt1Wheel[0]), cd=(posCtrl[0] + “.affectArea”), dv=1, v=0 )
cmds.setDrivenKeyframe( (wheelBlnd[0] + “.” + latt2Wheel[0]), cd=(posCtrl[0] + “.affectArea”), dv=0, v=0 )
cmds.setDrivenKeyframe( (wheelBlnd[0] + “.” + latt2Wheel[0]), cd=(posCtrl[0] + “.affectArea”), dv=1, v=1 )
cmds.setDrivenKeyframe( (proxyWheel[0] + “.visibility”), cd=(posCtrl[0] + “.showProxy”), dv=0, v=0 )
cmds.setDrivenKeyframe( (proxyWheel[0] + “.visibility”), cd=(posCtrl[0] + “.showProxy”), dv=1, v=1 )
cmds.setDrivenKeyframe( (wheelGeo + “.visibility”), cd=(posCtrl[0] + “.showProxy”), dv=0, v=1 )
cmds.setDrivenKeyframe( (wheelGeo + “.visibility”), cd=(posCtrl[0] + “.showProxy”), dv=1, v=0 )
#Set up the wheelSpin
wheelSpinPma = cmds.shadingNode(‘plusMinusAverage’, asUtility=True, name=(prefix + “spin_sum_pma”) )
cmds.connectAttr( (posCtrl[0] + “.wheelSpin”), (wheelSpinPma + “.input1D[0]“) )
cmds.connectAttr( (rotCtrl[0] + “.wheelSpin”), (wheelSpinPma + “.input1D[1]“) )
cmds.connectAttr( (topCtrl[0] + “.wheelSpin”), (wheelSpinPma + “.input1D[2]“) )
cmds.connectAttr( (lowCtrl[0] + “.wheelSpin”), (wheelSpinPma + “.input1D[3]“) )
cmds.connectAttr( (midCtrl + “.wheelSpin”), (wheelSpinPma + “.input1D[4]“) )
cmds.connectAttr( (rotCtrl[0] + “.rotateX”), (wheelSpinPma + “.input1D[5]“) )
cmds.connectAttr( (wheelSpinPma + “.output1D”), (proxyWheel[0] + “.rotateX”) )

# ************************************************************************************************************
# WRAP-DEFORM THE WHEEL SO THAT THE WHEEL CAN SPIN RELATIVE TO IT’S SHAPE
# ************************************************************************************************************
#Wrap the proxy-geo to the deformedInput-mesh
cmds.select( proxyWheel )
cmds.select( deformWheel[0], add=True )
cmds.CreateWrap()
#Get the name of the wrap, then rename it
wrap = cmds.listConnections( deformWheel[0], type=”wrap” )
proxyWrap = cmds.rename( wrap[0], prefix + “deformInput_wrap”)
#Set the options for the wrap
cmds.setAttr( proxyWrap + “.exclusiveBind”, 0 )
cmds.setAttr( proxyWrap + “.weightThreshold”, 0 )
cmds.setAttr( proxyWrap + “.maxDistance”, 0 )
cmds.setAttr( proxyWrap + “.autoWeightThreshold”, 0 )
#Wrap the original geo to the proxy-mesh
cmds.select( wheelGeo )
cmds.select( proxyWheel, add=True )
cmds.CreateWrap()
#Get the name of the wrap, then rename it
wrap = cmds.listConnections( proxyWheel[0], type=”wrap” )
originalWrap = cmds.rename( wrap[0], prefix + “proxy_wrap”)
#Set the options for the wrap
cmds.setAttr( originalWrap + “.exclusiveBind”, 0 )
cmds.setAttr( originalWrap + “.weightThreshold”, 0 )
cmds.setAttr( originalWrap + “.maxDistance”, 0 )
cmds.setAttr( originalWrap + “.autoWeightThreshold”, 0 )

# ************************************************************************************************************
# CLEANUP
# ************************************************************************************************************
#Clean the main hierarchy
cmds.parent( wheelGeo, proxyWheel, deformWheel[0], latt1Wheel[0], latt2Wheel[0], geoGrp )
cmds.parent( (proxyWheel[0] + “Base”), (deformWheel[0] + “Base”), geoGrp )
cmds.parent( geoGrp, clusterGrp, latticeGrp, noTransformGrp)
#Lock unused attributes
cmds.setAttr( posCtrlGrp + “.scale”, lock=True )
cmds.setAttr( posCtrl[0] + “.scale”, lock=True )
cmds.setAttr( rotCtrlGrp + “.translate”, lock=True )
cmds.setAttr( rotCtrlGrp + “.rotate”, lock=True )
cmds.setAttr( rotCtrlGrp + “.scale”, lock=True )
cmds.setAttr( rotCtrl[0] + “.translate”, lock=True )
cmds.setAttr( rotCtrl[0] + “.scale”, lock=True )
cmds.setAttr( rotCtrl[0] + “.rotateY”, lock=True )
cmds.setAttr( rotCtrl[0] + “.rotateZ”, lock=True )
#Lock unused attributes on each micro-control
microControllers = cmds.ls( prefix + “*_*_micro_ctrl”)
for micro in microControllers:
cmds.setAttr( micro + “.rotate”, lock=True)
cmds.setAttr( micro + “.scale”, lock=True)
#Rename all of the tweak-nodes
tweakNodes = cmds.ls(type=’tweak’)
for tweak in tweakNodes:
tweakShape = cmds.listConnections( tweak, type=”shape” )
cmds.rename(tweak, (tweakShape[0] + “_tweak”) )

# ************************************************************************************************************
# SET THE COLOR OF THE CONTROLLERS
# ************************************************************************************************************
cmds.setAttr (posCtrl[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (rotCtrl[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (topCtrl[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (lowCtrl[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (midCtrl + “Shape.overrideEnabled”, 1)
cmds.setAttr (topCtrl1[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (topCtrl2[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (topCtrl3[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (topCtrl4[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (topCtrl5[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (topCtrl6[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (midCtrl1[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (midCtrl2[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (midCtrl3[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (midCtrl4[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (midCtrl5[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (midCtrl6[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (lowCtrl1[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (lowCtrl2[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (lowCtrl3[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (lowCtrl4[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (lowCtrl5[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (lowCtrl6[0] + “Shape.overrideEnabled”, 1)
cmds.setAttr (posCtrl[0] + “Shape.overrideColor”, 19)
cmds.setAttr (rotCtrl[0] + “Shape.overrideColor”, 13)
cmds.setAttr (topCtrl[0] + “Shape.overrideColor”, 17)
cmds.setAttr (midCtrl + “Shape.overrideColor”, 17)
cmds.setAttr (lowCtrl[0] + “Shape.overrideColor”, 17)
cmds.setAttr (topCtrl1[0] + “Shape.overrideColor”, 9)
cmds.setAttr (topCtrl2[0] + “Shape.overrideColor”, 9)
cmds.setAttr (topCtrl3[0] + “Shape.overrideColor”, 9)
cmds.setAttr (topCtrl4[0] + “Shape.overrideColor”, 9)
cmds.setAttr (topCtrl5[0] + “Shape.overrideColor”, 9)
cmds.setAttr (topCtrl6[0] + “Shape.overrideColor”, 9)
cmds.setAttr (midCtrl1[0] + “Shape.overrideColor”, 9)
cmds.setAttr (midCtrl2[0] + “Shape.overrideColor”, 9)
cmds.setAttr (midCtrl3[0] + “Shape.overrideColor”, 9)
cmds.setAttr (midCtrl4[0] + “Shape.overrideColor”, 9)
cmds.setAttr (midCtrl5[0] + “Shape.overrideColor”, 9)
cmds.setAttr (midCtrl6[0] + “Shape.overrideColor”, 9)
cmds.setAttr (lowCtrl1[0] + “Shape.overrideColor”, 9)
cmds.setAttr (lowCtrl2[0] + “Shape.overrideColor”, 9)
cmds.setAttr (lowCtrl3[0] + “Shape.overrideColor”, 9)
cmds.setAttr (lowCtrl4[0] + “Shape.overrideColor”, 9)
cmds.setAttr (lowCtrl5[0] + “Shape.overrideColor”, 9)
cmds.setAttr (lowCtrl6[0] + “Shape.overrideColor”, 9)

# ************************************************************************************************************
# ************************************************************************************************************

#Run the script
jh_wheelRiggerUI()

Download Script

Rig: Mecha Cat

Nico Strobbe posted a rigging-challenge over at CGCociety in 2010, he has provided a stunning model of a mechanical cat consisting of 437 different parts which can be downloaded here:
http://forums.cgsociety.org/showthread.php?f=216&t=907295 

I started to rig this creature about a year ago, being my first attempt at mechanical rigging I quickly ran out of ideas on how to rig the legs properly, so I gave it up.

However I picked up the rig again about two months ago, starting from scratch on the entire thing, after a hard struggle I finally managed to figure out how to solve the legs (which is the key to the entire rig) and complete the rest of the rig.

Here’s the rig-presentation:

 

I intend to make a breakdown-video eventually, just gotta find some time for it :)

Script: Cartoony Wheel Rig v1

Script: Cartoony Wheel Rig v1

When rigging vehicles you’ll usually have to deal with at least two wheels, and (especially if you’re going cartoony) you’re going to waste a lot of time if you’re rigging them manually, one by one. So I decided to write a script for this, the script is supposed to do all of the work automaticly, and it should work with wheels regardless of size and position.

The requirements I set for the rig was that it had to be clean, fast and flexible. It should also be animation friendly, so the controllers have to be logical and easy to select. With this current version of the rig I’m not too happy with the flexibility in particular, it works, but it can’t be pushed as far as I want it to. I’m going back to the drawing board.

Here’s the script in action:

The current version is basicly just a lattice with some clusters, and a nurbs-circle with all of the extra-controllers connected to the circle through MotionPath-nodes. As for the main controllers, I have a contact-controller for floor contact, two controllers for the rolling of the wheel, and one main-controller for translation/rotation/scaling.

 

jh_wheelRigger (Cartoony Wheel Rig)

//*************************************************************************************************************

// Title: jh_wheelRigger.mel
// Author: Jørn-Harald Paulsen
// Created: July 20, 2012
// Last Update: July 23, 2012
// Description: Utility to rig wheels with lattice and joints.
//*************************************************************************************************************
// MAIN WINDOW
//*************************************************************************************************************
global proc jh_wheelRigger()
{
//Close window if it already exists
if (`window -q -ex jh_wheelRigger`) deleteUI jh_wheelRigger;

//Main Window
window -topEdge 30 -title “Wheelrigger”
-mxb false -s true -rtf false -mb false -mbv false -w 412 -h 268 jh_wheelRigger;

//Window content
columnLayout -adjustableColumn true;
text -label “\nMake sure your wheel is pointing in Z, so if it were to roll” -fn boldLabelFont;
text -label “it would roll forward in positive Z. Also make sure that your” -fn boldLabelFont;
text -label “wheel-geo is grouped, and has it’s pivot in the center.” -fn boldLabelFont;
separator -w 240 -h 40;
text -label “Enter the prefix for the wheel (example: car_l_front_):”;
textField prefixField;
separator -w 240 -h 40;
button -label “Load the wheel-geo group” -c jh_loadWheelGeo;
textField -en 0 wheelGeoField;
separator -w 240 -h 40;
button -label “Rig the wheel” -c jh_rigWheel;
separator -w 240 -h 40;
window -e -w 412 -h 268 jh_wheelRigger;
showWindow jh_wheelRigger;
}

global proc jh_loadWheelGeo()
{
//Get the selected object
string $selObj[] = `ls -sl`;
//Add it to the textField
textField -e -text $selObj[0] wheelGeoField;
}

global proc jh_rigWheel()
{
//Get the group with the wheel
string $wheelGeo = `textField -q -text wheelGeoField`;
//Get the prefix
string $prefix = `textField -q -text prefixField`;
//*************************************************************************************************************
// CREATE THE GROUPS NEEDED
//*************************************************************************************************************
string $wheelGrp = `group -em -n ($prefix + “wheel_grp”)`;
string $wheelGeoGrp = `group -em -n ($prefix + “wheel_geo_grp”)`;
string $clusterGrp = `group -em -n ($prefix + “wheel_cluster_grp”)`;
string $latticeGrp = `group -em -n ($prefix + “wheel_lattice_grp”)`;
string $controlGrp = `group -em -n ($prefix + “wheel_ctrl_grp”)`;
//*************************************************************************************************************
// CREATE/POSITION THE CONTACT-CONTROLLER
//*************************************************************************************************************
//Get the boundingBox of the wheel
float $boundingBox[] = `xform -q -bb $wheelGeo`;
//Create a contact-controller
string $contactCtrl = `curve -d 1 -p -1 0 -4 -p 1 0 -4 -p 1 0 -3 -p -1 0 -3 -p -1 0 -2 -p 1 0 -2 -p 1 0 -1 -p -1 0 -1 -p -1 0 0 -p 1 0 0 -p 1 0 1 -p -1 0 1 -p -1 0 2 -p 1 0 2 -p 1 0 3 -p -1 0 3 -p -1 0 4 -p 1 0 4 -p 1 0 3 -p -1 0 3 -p -1 0 2 -p 1 0 2 -p 1 0 1 -p -1 0 1 -p -1 0 0 -p 1 0 0 -p 1 0 -1 -p -1 0 -1 -p -1 0 -2 -p 1 0 -2 -p 1 0 -3 -p -1 0 -3 -p -1 0 -4 -p 0 0 -4 -p 0 0 4`;
//Create a lattice for the control
string $ctrlLattice[] = `lattice -divisions 2 2 2 -objectCentered true -ldv 2 2 2 $contactCtrl`;
//Position the control with the lattice
move -a -ws $boundingBox[3] $boundingBox[1] $boundingBox[2] ($ctrlLattice[1] + “.pt[1][0:1][0]“);
move -a -ws $boundingBox[0] $boundingBox[1] $boundingBox[2] ($ctrlLattice[1] + “.pt[0][0:1][0]“);
move -a -ws $boundingBox[3] $boundingBox[1] $boundingBox[5] ($ctrlLattice[1] + “.pt[1][0:1][1]“);
move -a -ws $boundingBox[0] $boundingBox[1] $boundingBox[5] ($ctrlLattice[1] + “.pt[0][0:1][1]“);
//Delete history and center pivot of the controller
delete -ch $contactCtrl;
xform -cp $contactCtrl;
//Create and position the group for the contact-controller
string $contactCtrlGrp = `group -em -n ($prefix + “wheel_contact_ctrl_grp”)`;
delete `pointConstraint $contactCtrl $contactCtrlGrp`;
//Parent the controller to the group and freeze the transforms
parent $contactCtrl $contactCtrlGrp;
makeIdentity -apply true -t 1 -r 1 -s 1 -n 0 $contactCtrlGrp;
//*************************************************************************************************************
// CREATE THE LATTICE AND SET UP THE CLUSTERS
//*************************************************************************************************************
//Create a lattice on the wheel
string $wheelLattice[] = `lattice -divisions 5 5 5 -objectCentered true -ldv 5 5 5 $wheelGeo`;
//Set the lattice to affect outside of the boundingBox
setAttr ($wheelLattice[0] + “.outsideLattice”) 1;
//Position the lattice-group to the lattice
delete `pointConstraint $wheelLattice[1] $latticeGrp`;
//Parent it to it’s respective group
parent $wheelLattice[1] $wheelLattice[2] $latticeGrp;
//Create a cluster for the contact of the wheel
string $contactCls[] = `cluster -n ($prefix + “wheel_contact_cluster”) ($wheelLattice[1] + “.pt[0:4][0:1][0:4]“)`;
percent -v 0.25 $contactCls[0] ($wheelLattice[1] + “.pt[0:4][1][0:4]“);
//Create a clusters for the extra control of the wheel
string $upFrontCls[] = `cluster -n ($prefix + “wheel_1_cluster”) ($wheelLattice[1] + “.pt[0:4][3:4][3:4]“)`;
percent -v 0.5 $upFrontCls[0] ($wheelLattice[1] + “.pt[0:4][3:4][3]“) ($wheelLattice[1] + “.pt[0:4][3][4]“);
string $sideFrontCls[] = `cluster -n ($prefix + “wheel_2_cluster”) ($wheelLattice[1] + “.pt[0:4][1:3][4]“)`;
percent -v 0.5 $sideFrontCls[0] ($wheelLattice[1] + “.pt[0:4][3][4]“) ($wheelLattice[1] + “.pt[0:4][1][4]“);
string $lowFrontCls[] = `cluster -n ($prefix + “wheel_3_cluster”) ($wheelLattice[1] + “.pt[0:4][0:1][3:4]“)`;
percent -v 0.5 $lowFrontCls[0] ($wheelLattice[1] + “.pt[0:4][0:1][3]“) ($wheelLattice[1] + “.pt[0:4][1][4]“);
string $lowMidCls[] = `cluster -n ($prefix + “wheel_4_cluster”) ($wheelLattice[1] + “.pt[0:4][0][1:3]“)`;
percent -v 0.5 $lowMidCls[0] ($wheelLattice[1] + “.pt[0:4][0][1]“) ($wheelLattice[1] + “.pt[0:4][0][3]“);
string $lowBackCls[] = `cluster -n ($prefix + “wheel_5_cluster”) ($wheelLattice[1] + “.pt[0:4][0:1][0:1]“)`;
percent -v 0.5 $lowBackCls[0] ($wheelLattice[1] + “.pt[0:4][1][0:1]“) ($wheelLattice[1] + “.pt[0:4][0][1]“);
string $sideBackCls[] = `cluster -n ($prefix + “wheel_6_cluster”) ($wheelLattice[1] + “.pt[0:4][1:3][0]“)`;
percent -v 0.5 $sideBackCls[0] ($wheelLattice[1] + “.pt[0:4][1][0]“) ($wheelLattice[1] + “.pt[0:4][3][0]“);
string $upBackCls[] = `cluster -n ($prefix + “wheel_7_cluster”) ($wheelLattice[1] + “.pt[0:4][3:4][0:1]“)`;
percent -v 0.5 $upBackCls[0] ($wheelLattice[1] + “.pt[0:4][3][0]“) ($wheelLattice[1] + “.pt[0:4][3:4][1]“);
string $upMidCls[] = `cluster -n ($prefix + “wheel_8_cluster”) ($wheelLattice[1] + “.pt[0:4][4][1:3]“)`;
percent -v 0.5 $upMidCls[0] ($wheelLattice[1] + “.pt[0:4][4][1]“) ($wheelLattice[1] + “.pt[0:4][4][3]“);
//Parent the clusters to the cluster-group
parent $contactCls[1] $upFrontCls[1] $sideFrontCls[1] $lowFrontCls[1] $lowMidCls[1] $lowBackCls[1] $sideBackCls[1] $upBackCls[1] $upMidCls[1] $clusterGrp;
//*************************************************************************************************************
// CREATE/POSITION AND CONNECT THE EXTRA WHEEL-CONTROLLERS
//*************************************************************************************************************
//Create string-arrays to store the joints and controllers in
string $ctrlGrp[];
string $ctrl[];
string $clsGrp[];
//Get the radius of the wheel
float $wheelRadius = `abs(($boundingBox[2] – $boundingBox[5]) / 2)`;
//Create a nurbs-circle as reference for positioning the controllers
string $ctrlCircle[] = `circle -c 0 0 0 -nr 1 0 0 -sw 360 -r $wheelRadius -d 3 -ut 0 -s 32 -ch 0`;
//Get the shape of the curve
string $ctrlCircleShape[] = `listRelatives -s $ctrlCircle[0]`;
//Position the circle at the wheel
delete `pointConstraint $wheelGeo $ctrlCircle[0]`;
//Create and position a group for the circle
string $ctrlCircleGrp = `group -em -n ($prefix + “wheel_ctrlCurve_crv_grp”)`;
delete `pointConstraint $ctrlCircle[0] $ctrlCircleGrp`;
//Parent the circle to the group
parent $ctrlCircle[0] $ctrlCircleGrp;

//Create a variable to define the U-value of the curve for where to place the joints
float $uVal = 1.000000 / 8;
float $incrementVar = $uVal;

//For each joint
for($a = 0; $a < 8; $a++)
{
//Create a motionPath-node
string $mpNode = `shadingNode -asUtility motionPath -n ($prefix + “wheel_” + ($a + 1) + “_mPath”)`;
//Create a controller and a group
string $mpCtrl[] = `circle -c 0 0 0 -nr 1 0 0 -sw 360 -r ($wheelRadius / 6) -d 3 -ut 0 -s 8 -ch 0 -n ($prefix + “wheel_” + ($a + 1) + “_ctrl”)`;
string $mpCtrlGrp = `group -n ($prefix + “wheel_” + ($a + 1) + “_ctrl_grp”) $mpCtrl[0]`;
//Connect the ctrl to the curve through the motionPath-node
connectAttr -f ($ctrlCircleShape[0] + “.worldSpace[0]“) ($mpNode + “.geometryPath”);
connectAttr -f ($mpNode + “.allCoordinates”) ($mpCtrlGrp + “.translate”);
connectAttr -f ($mpNode + “.rotate”) ($mpCtrlGrp + “.rotate”);
//Position the ctrl at it’s correct U-value along the curve
setAttr ($mpNode + “.fractionMode”) 1;
setAttr ($mpNode + “.uValue”) $uVal;
setAttr ($mpNode + “.worldUpType”) 1;
setAttr ($mpNode + “.frontAxis”) 2;
setAttr ($mpNode + “.upAxis”) 1;
setAttr ($mpNode + “.inverseUp”) 1;
connectAttr ($ctrlCircle[0] + “.worldMatrix[0]“) ($mpNode + “.worldUpMatrix”);
//Add the objects into the pre-defined arrays
$ctrlGrp[$a] = $mpCtrlGrp;
$ctrl[$a] = $mpCtrl[0];
//Add the object to it’s respective groups
parent $ctrlGrp[$a] $controlGrp;

//Create groups for the cluster
string $tmpGrp1 = `group -em -n ($prefix + “wheel_” + ($a + 1) + “_cluster_offset_grp”)`;
string $tmpGrp2 = `group -em -n ($prefix + “wheel_” + ($a + 1) + “_cluster_grp”)`;
string $tmpGrp3 = `group -em -n ($prefix + “wheel_” + ($a + 1) + “_cluster_norm_grp”)`;
//Parent the clusterGrp to the offsetGrp
parent $tmpGrp2 $tmpGrp1;
//Position and orient the group to the controller
delete `pointConstraint $ctrl[$a] $tmpGrp1`;
delete `orientConstraint $ctrl[$a] $tmpGrp1`;
//Place the cluster in the group
parent $tmpGrp1 $clusterGrp;
parent $tmpGrp3 $tmpGrp2;
parent ($prefix + “wheel_” + ($a + 1) + “_clusterHandle”) $tmpGrp3;

//Connect the controller to the cluster-group
connectAttr -f ($ctrl[$a] + “.translate”) ($tmpGrp2 + “.translate”);
connectAttr -f ($ctrl[$a] + “.rotate”) ($tmpGrp2 + “.rotate”);
connectAttr -f ($ctrl[$a] + “.scale”) ($tmpGrp2 + “.scale”);

//Increment the $uVal-variable
$uVal += $incrementVar;
}
//*************************************************************************************************************
// CREATE/POSITION THE MAIN WHEEL-CONTROLLERS
//*************************************************************************************************************
//Create a the controller for the wheel rotation/position
string $rotCtrl[] = `circle -c 0 0 0 -nr 1 0 0 -sw 360 -r ($wheelRadius / 1.7) -d 3 -ut 0 -s 32 -ch 0`;
string $minRotCtrl[] = `circle -c 0 0 0 -nr 1 0 0 -sw 360 -r ($wheelRadius / 6) -d 3 -ut 0 -s 32 -ch 0`;
string $posCtrl[] = `circle -c 0 0 0 -nr 1 0 0 -sw 360 -r ($wheelRadius / 1.2) -d 3 -ut 0 -s 32 -ch 0`;
string $minPosCtrl[] = `circle -c 0 0 0 -nr 1 0 0 -sw 360 -r ($wheelRadius / 1.5) -d 3 -ut 0 -s 32 -ch 0`;
//Shape the rotation-controllers
scale -r 0.5 0.5 0.5 ($rotCtrl[0] + “.cv[1]“) ($rotCtrl[0] + “.cv[9]“) ($rotCtrl[0] + “.cv[17]“) ($rotCtrl[0] + “.cv[25]“);
scale -r 0.3 0.3 0.3 ($minRotCtrl[0] + “.cv[1]“) ($minRotCtrl[0] + “.cv[9]“) ($minRotCtrl[0] + “.cv[17]“) ($minRotCtrl[0] + “.cv[25]“);
//Shape the position-controller
for($a=1; $a < 32; $a=$a + 2) scale -r -p 0cm 0cm 0cm 0.85 0.85 0.85 ($posCtrl[0] + “.cv[" + $a + "]“);
//Create groups for the controllers
string $rotCtrlGrp = `group -em -n ($prefix + “wheel_rotate_ctrl_grp”)`;
string $posCtrlGrp = `group -em -n ($prefix + “wheel_position_ctrl_grp”)`;
//Parent the controllers to their groups
parent $rotCtrl[0] $rotCtrlGrp;
parent $posCtrl[0] $posCtrlGrp;
parent $minPosCtrl[0] $posCtrl[0];
parent $minRotCtrl[0] $rotCtrl[0];
//Position the control-groups
delete `pointConstraint $wheelGeo $rotCtrlGrp`;
delete `orientConstraint $wheelGeo $rotCtrlGrp`;
delete `pointConstraint $wheelGeo $posCtrlGrp`;
delete `orientConstraint $wheelGeo $posCtrlGrp`;
//Get the worldSpace position of the rotate-group
float $wheelPivot[] = `xform -q -ws -t $rotCtrlGrp`;
//Find the position of where to offset the controllers
float $offsetCtrl = ($boundingBox[3] / 10);
//Offset the controllers
setAttr ($rotCtrlGrp + “.tx”) ($boundingBox[3] + $offsetCtrl);
setAttr ($posCtrlGrp + “.tx”) ($boundingBox[3] + $offsetCtrl);
//Freeze the transforms of the controllers
makeIdentity -apply true -t 1 -r 1 -s 1 -n 0 $rotCtrlGrp $posCtrlGrp;
//Set the pivot of the controllers to the center of the wheel
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($rotCtrlGrp + “.scalePivot”) ($rotCtrlGrp + “.rotatePivot”);
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($rotCtrl[0] + “.scalePivot”) ($rotCtrl[0] + “.rotatePivot”);
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($posCtrlGrp + “.scalePivot”) ($posCtrlGrp + “.rotatePivot”);
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($posCtrl[0] + “.scalePivot”) ($posCtrl[0] + “.rotatePivot”);
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($minPosCtrl[0] + “.scalePivot”) ($minPosCtrl[0] + “.rotatePivot”);
//Set the rotation order of the objects and controllers
setAttr ($ctrlCircle[0] + “.rotateOrder”) 3;
setAttr ($wheelGeo + “.rotateOrder”) 3;
setAttr ($wheelLattice[1] + “.rotateOrder”) 3;
setAttr ($wheelLattice[2] + “.rotateOrder”) 3;
setAttr ($rotCtrlGrp + “.rotateOrder”) 3;
setAttr ($posCtrlGrp + “.rotateOrder”) 3;
setAttr ($rotCtrl[0] + “.rotateOrder”) 3;
setAttr ($posCtrl[0] + “.rotateOrder”) 3;
setAttr ($minPosCtrl[0] + “.rotateOrder”) 3;
setAttr ($minRotCtrl[0] + “.rotateOrder”) 3;
//*************************************************************************************************************
// RIG THE MAIN WHEEL-CONTROLLERS
//*************************************************************************************************************
//Create a plusMinAvgNode so that we can sum the rotate-group and the rotate-controllers values to the wheel
string $rotAvgPma = `shadingNode -asUtility plusMinusAverage -n ($prefix + “wheel_rotate_sum_pma”)`;
//Connect the rotate-group and the rotate-controller to the plusMinAvgNode
connectAttr -f ($rotCtrlGrp + “.rotate”) ($rotAvgPma + “.input3D[0]“);
connectAttr -f ($rotCtrl[0] + “.rotate”) ($rotAvgPma + “.input3D[1]“);
connectAttr -f ($minRotCtrl[0] + “.rotate”) ($rotAvgPma + “.input3D[2]“);
//Connect the output of the plusMinAvgNode to the rotate of the wheel components
connectAttr -f ($rotAvgPma + “.output3Dx”) ($wheelGeo + “.rotateX”);

//Orient-Constraint the main position-controller to the lattice
orientConstraint -mo $posCtrl[0] $ctrlLattice[1];
//Point/Scale-Constraint the minPosCtrl to the lattice
pointConstraint -mo $minPosCtrl[0] $ctrlLattice[1];
scaleConstraint -mo $minPosCtrl[0] $ctrlLattice[1];
//Parent/Scale-Constraint the minPosCtrl to the extra control-curve
parentConstraint -mo $minPosCtrl[0] $ctrlCircle[0];
scaleConstraint -mo $minPosCtrl[0] $ctrlCircle[0];
//Parent/Scale-Constraint the minPosCtrl to the group of the rotation-controllers
parentConstraint -mo $minPosCtrl[0] $rotCtrlGrp;
scaleConstraint -mo $minPosCtrl[0] $rotCtrlGrp;
//Scale-Constraint the posCtrl to the group of the extra-controllers
for($each in $ctrlGrp) scaleConstraint -mo $posCtrl[0] $each;
//*************************************************************************************************************
// RIG THE CONTACT-CONTROLLER
//*************************************************************************************************************
//Move the pivot of the contact-cluster
float $contactPivot[] = `xform -q -piv $contactCtrl`;
move -a $contactPivot[0] $contactPivot[1] $contactPivot[2] ($contactCls[1] + “.scalePivot”) ($contactCls[1] + “.rotatePivot”);

//Create a multiplyDivideNode so that we can subtract the micro-pos-controller from the contact-controller
string $posSubMpd = `shadingNode -asUtility multiplyDivide -n ($prefix + “wheel_contact_pos_subtract_mpd”)`;
connectAttr -f ($minPosCtrl[0] + “.translate”) ($posSubMpd + “.input1″);
setAttr ($posSubMpd + “.input2″) -1 -1 -1;

//Create a plusMinAvgNode so that we can sum all of the components to the contact-cluster
string $contactClsPosAvgPma = `shadingNode -asUtility plusMinusAverage -n ($prefix + “wheel_contact_cls_pos_sum_pma”)`;
connectAttr -f ($contactCtrl + “.translate”) ($contactClsPosAvgPma + “.input3D[0]“);
connectAttr -f ($posSubMpd + “.output”) ($contactClsPosAvgPma + “.input3D[1]“);
//Connect the output of the plusMinAvgNode to the translate of the contact-cluster
connectAttr -f ($contactClsPosAvgPma + “.output3D”) ($contactCls[1] + “.translate”);
//Connect the contact-controller to the contact-cluster
connectAttr -f ($contactCtrl + “.rotate”) ($contactCls[1] + “.rotate”);
connectAttr -f ($contactCtrl + “.scale”) ($contactCls[1] + “.scale”);

//Parent the contact-ctrl group to the main position controller
parent $contactCtrlGrp $posCtrl[0];
//*************************************************************************************************************
// SET THE CONTROL COLOURS
//*************************************************************************************************************
//Get the shape of the controllers
string $contactCtrlShape[] = `listRelatives -s $contactCtrl`;
string $rotCtrlShape[] = `listRelatives -s $rotCtrl[0]`;
string $posCtrlShape[] = `listRelatives -s $posCtrl[0]`;
string $minRotCtrlShape[] = `listRelatives -s $minRotCtrl[0]`;
string $minPosCtrlShape[] = `listRelatives -s $minPosCtrl[0]`;
//Turn on overrideEnable of the controllers
setAttr ($contactCtrlShape[0] + “.overrideEnabled”) 1;
setAttr ($rotCtrlShape[0] + “.overrideEnabled”) 1;
setAttr ($posCtrlShape[0] + “.overrideEnabled”) 1;
setAttr ($minRotCtrlShape[0] + “.overrideEnabled”) 1;
setAttr ($minPosCtrlShape[0] + “.overrideEnabled”) 1;
//Set the color of the controllers
setAttr ($contactCtrlShape[0] + “.overrideColor”) 4;
setAttr ($rotCtrlShape[0] + “.overrideColor”) 31;
setAttr ($posCtrlShape[0] + “.overrideColor”) 19;
setAttr ($minRotCtrlShape[0] + “.overrideColor”) 9;
setAttr ($minPosCtrlShape[0] + “.overrideColor”) 17;
//For each of the extra controllers
for($each in $ctrl)
{
//Get the shape of the current control
string $extraCtrl[] = `listRelatives -s $each`;
//Turn on overrideEnable of the current controller
setAttr ($each + “.overrideEnabled”) 1;
//Set the color of the current controller
setAttr ($each + “.overrideColor”) 6;
}
//*************************************************************************************************************
// CLEANUP
//*************************************************************************************************************
//Rename the objects
$wheelGeo = `rename $wheelGeo ($prefix + “wheel_geo”)`;
$contactCtrl = `rename $contactCtrl ($prefix + “wheel_contact_ctrl”)`;
$ctrlCircle[0] = `rename $ctrlCircle[0] ($prefix + “wheel_ctrlCurve_crv”)`;
$rotCtrl[0] = `rename $rotCtrl[0] ($prefix + “wheel_rotate_ctrl”)`;
$posCtrl[0] = `rename $posCtrl[0] ($prefix + “wheel_position_ctrl”)`;
$minRotCtrl[0] = `rename $minRotCtrl[0] ($prefix + “wheel_micro_rotate_ctrl”)`;
$minPosCtrl[0] = `rename $minPosCtrl[0] ($prefix + “wheel_micro_position_ctrl”)`;
$wheelLattice[0] = `rename $wheelLattice[0] ($wheelGeo + “_latticeShape”)`;
$wheelLattice[1] = `rename $wheelLattice[1] ($wheelGeo + “_lattice”)`;
$wheelLattice[2] = `rename $wheelLattice[2] ($wheelGeo + “_lattice_base”)`;

//Set the pivot to the center of the wheel of the main groups
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($wheelGrp + “.scalePivot”) ($wheelGrp + “.rotatePivot”);
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($controlGrp + “.scalePivot”) ($controlGrp + “.rotatePivot”);
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($clusterGrp + “.scalePivot”) ($clusterGrp + “.rotatePivot”);
move -a $wheelPivot[0] $wheelPivot[1] $wheelPivot[2] ($wheelGeoGrp + “.scalePivot”) ($wheelGeoGrp + “.rotatePivot”);

//Clean the hierarchy
parent $wheelGeo $wheelGeoGrp;
parent $rotCtrlGrp $posCtrlGrp $ctrlCircleGrp $clusterGrp $latticeGrp $controlGrp $wheelGeoGrp $wheelGrp;

//Turn of inherit transform so that scaling works
setAttr ($controlGrp + “.inheritsTransform”) 0;
setAttr ($clusterGrp + “.inheritsTransform”) 0;
setAttr ($latticeGrp + “.inheritsTransform”) 0;
setAttr ($wheelGeoGrp + “.inheritsTransform”) 0;
setAttr ($ctrlCircleGrp + “.inheritsTransform”) 0;

//Hide objects/groups we don’t need to see
setAttr ($clusterGrp + “.visibility”) 0;
setAttr ($latticeGrp + “.visibility”) 0;
setAttr ($ctrlCircleGrp + “.visibility”) 0;

//Lock attributes that shouln’t be used
setAttr -lock 1 -keyable 0 -channelBox 0 ($minPosCtrl[0] + “.rotate”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($minPosCtrl[0] + “.scale”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($rotCtrl[0] + “.translate”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($rotCtrl[0] + “.scale”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($rotCtrl[0] + “.rotateY”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($rotCtrl[0] + “.rotateZ”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($minRotCtrl[0] + “.translate”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($minRotCtrl[0] + “.scale”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($minRotCtrl[0] + “.rotateY”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($minRotCtrl[0] + “.rotateZ”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($clusterGrp + “.translate”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($clusterGrp + “.rotate”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($clusterGrp + “.scale”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($latticeGrp + “.translate”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($latticeGrp + “.rotate”);
setAttr -lock 1 -keyable 0 -channelBox 0 ($latticeGrp + “.scale”);
}

jh_wheelRigger;

Download Script

Script: Delete Flat Keys

Script: Delete Flat Keys

When you’re working with animation you eventually end up with a lot of extra keys, by that I mean flat keys, keys we don’t need.

See the image below, I’ve marked the flat keys with red dots:

 

With the script you just select all of the objects that you want to clean the keyframes on, then you define the angle threshold, and the script will go through and delete all of the keys with a lower angle threshold than defined.

When run on the example above, we end up with this:

 

jh_delFlatKeys (Delete Flat Keys)

//*************************************************************************************************************
// Title: jh_delFlatKeys.mel
// Author: Jørn-Harald Paulsen
// Created: September 08, 2010
// Last Update: May 28, 2011
// Description: Utility to remove key’s with a lower value-difference than specified.
//*************************************************************************************************************
// MAIN WINDOW
//*************************************************************************************************************
global proc jh_delFlatKeys()
{
//Close window if it already exists
if (`window -q -ex jh_delFlatKeys`) deleteUI jh_delFlatKeys;

//Main Window
window -topEdge 30 -title “Keyframe cleanup”
-mxb false -s true -rtf false -mb false -mbv false -w 412 -h 268 jh_delFlatKeys;

//Window content
columnLayout -adjustableColumn true;
text -label “\nUtility to remove key’s with a lower value-difference than specified.\n” -fn boldLabelFont;
separator -w 240 -h 40;
text -label “- Select the object(s) that you want to clean keyframes on”;
text -label “- Define the angle/value tolerance (keys with values lower than these will be deleted)\n”;
floatSliderGrp -label “Angle tolerance” -f true -min 0.001 -max 5.000 -fmn 0.001 -fmx 100.000 -v 0.001 -s 0.001 sliderAngleTol;
floatSliderGrp -label “Value tolerance” -f true -min 0.001 -max 5.000 -fmn 0.001 -fmx 100.000 -v 0.001 -s 0.001 sliderValueTol;
separator -w 240 -h 40;
button -label “Clean keyframes” -c jh_deleteKeys;
separator -w 240 -h 40;
window -e -w 412 -h 268 jh_delFlatKeys;
showWindow jh_delFlatKeys;
}

global proc jh_deleteKeys()
{
//Get the selected object(s)
string $selObj[] = `ls -sl`;
//Create a variable to store the number of keys deleted
int $keysDeleted = 0;
//For each selected object
for($object in $selObj)
{
//Get the animation curves
string $animCurve[] = `keyframe -query -name $object`;
//If the object have animation curves
if (size($animCurve) > 0)
{
//Tangents with angles below this value will be considered flat
float $angleTol = `floatSliderGrp -q -v sliderAngleTol`;
//Neighboring values that fall within this tolerance will be considered matching
float $valueTol = `floatSliderGrp -q -v sliderValueTol`;

//Define the variables
string $keyedAttrs[], $outTanType[], $evalStr, $plural;
float $keyedValues[], $inAngle[], $outAngle[], $keyedFrames[], $prevValDif, $nextValDif;
int $deleteIndex[], $stepped, $i;
int $counter = 0;

//For each animation curve
for ($curve in $animCurve)
{
clear($inAngle);
clear($outAngle);
clear($deleteIndex);
clear($keyedValues);

//Get all of the keyed attributes
$keyedAttrs = `listConnections -plugs yes -source no $curve`;

//Go through the keyframes on the current animation-curve and get inAngle and outAngle
float $inAngle[] = `keyTangent -query -inAngle $curve`;
float $outAngle[] = `keyTangent -query -outAngle $curve`;
string $outTanType[] = `keyTangent -query -outTangentType $curve`;

//Make $inAngle and $outAngle absolute values, for less work on the comparison
for ($i=0;$ifor ($i=0;$i

//Get each keyed frame, and get the value of each keyed frame
$keyedFrames = `keyframe -query -timeChange $curve`;
$keyedValues = `keyframe -query -valueChange $curve`;

//For each keyeframe on the current animation-curve, find deleteable keys
for ($i=0;$i {
//As long as the current keyeframe is between 0, and less than the number of keyframes, continue
if ($i == 0 || $i == (`size $keyedFrames`-1)) continue;

//Get the outTangentType of the previus frame, the current frame, and the next frame.
//The result of this command will go to $stepped. If true, $stepped = 1, if false, $stepped = 0
$stepped = ($outTanType[$i] == “step” && $outTanType[$i-1] == “step” && $outTanType[$i+1] == “step”);

//If one of the following conditions (1 or 2) is true, rund the script inside of the if-brackets:
// 1) $stepped is true (1)
// 2) outAngle of the previous frame is less than angleTol
// & inAngle of the current frame is less than angleTol
// & outAngle of the current frame is less than angleTol
// & inAngle of the next frame is less than angleTol
if ($stepped || ($outAngle[$i-1] < $angleTol && $inAngle[$i] < $angleTol && $outAngle[$i] < $angleTol && $inAngle[$i+1] < $angleTol))
{
//Get the difference of the value of the current attribute between the previous frame and the current frame
$prevValDif = abs($keyedValues[$i-1] – $keyedValues[$i]);
//Get the difference of the value of the current attribute between the current frame and the next frame
$nextValDif = abs($keyedValues[$i+1] – $keyedValues[$i]);

//If one of the following (1 or 2) conditions is true, set $deleteIndex:
// 1) $stepped is true (1)
// & $prevValDif is less than valueTol
// 2) $stepped is false (0)
// & $prevValDif is larger than valueTol
// & $nextValDif is larger than $valueTol
if ($stepped && $prevValDif < $valueTol || !$stepped && $prevValDif < $valueTol && $nextValDif < $valueTol) $deleteIndex = $i; } } //Make the string to delete the keys if (size($deleteIndex) > 0)
{
//The first command of the delete -string
$evalStr = “cutKey -clear”;
//Add the indexes to the string
for ($i in $deleteIndex) $evalStr += ” -index ” + $i;
//Add the animation-curve to the string
$evalStr += ” ” + $curve;
//Execute the generated cutKey -string
eval($evalStr);
//Get the number of keys deleted
$counter += size($deleteIndex);
}
}

//If more than one key has been deleted, set the $plural to true
$plural = ($counter > 1 || $counter == 0) ? “s” : “”;
//Put the number of deleted keys on the current attribute to the total deleted keys -variable
$keysDeleted = $keysDeleted + $counter;
//Print information
print ($counter + ” keyframe” + $plural + ” removed from ” + $object + “.\n”);
}
}
//Print number of keys deleted in total
print (“Deleted keyframes in total: ” + $keysDeleted + “.\n”);
}

jh_delFlatKeys;

Download Script

 

Video: Kung Fu Bunny 3

This is a short film created by Zhiyong Li from the Communication University of China. Awesome stuff, I can feel a Kung Fu chill riding up my spine!