
With a greater number of potential characters than Mothhead, our previous Unity project; I went into Zombie Playground’s pre-production with my mind already set on creating an automated rigging solution. I took some inspiration from a Modular Rigging course I followed on 3dBuzz and begun to break down my rigging process into object-oriented code in suite I named Automato.
class BaseSubstruct(object):
'''
Abstract class to derive new rig modules from
'''
NAME = 'abstractobject'
NICE_NAME = ''
CREATION_PARAM_JSON_ATTRNAME = 'creationParams'
DESCRIPTION = 'generic abstract description'
MIN_JOINTS = 1 # number of joints needed to create substruct
REQUIRES_SKELETON_CHAIN = True # if true, subsruct requires a skeletal chain for creation
def __init__(self, basename, joints, parent, mainControl):
'''
Builds rig in maya and creates an asset node to contain relevant nodes
'''
self.container = None
self.layer = None
self.containedNodes = list()
self._rigControls = dict()
self.lockAttrs = list()
self.basename = basename # unique name to identify nodes created by this substruct
self.parent = parent # parent to attach top-most rig node
self.joints = joints # base skeleton joints to attach
self.mainControl = mainControl
self.mainColorAttr = None
if self.parent is None:
self.parent = self.joints[0].firstParent2()
if self.parent:
self.parent = pm.PyNode(self.parent)
else:
if mainControl:
self.parent = mainControl
self.verifyParams()
self.container = pm.container(name='_'.join([Prefix.CONTAINER, basename, self.NAME]))
## Parameter dictionary for storing settings for assets
## Children classes can further expand this dictionary with parameters of their own
self.paramDict = {'classname': self.NAME,
'basename': self.basename,
'joints': [str(i) for i in self.joints],
'parent': str(self.parent),
'mainControl': str(self.mainControl),
'container': str(self.container)}
pm.addAttr(self.container, dataType='string', ln=self.CREATION_PARAM_JSON_ATTRNAME)
pm.addAttr(self.container, at=int, ln='color', min=0, max=31, defaultValue=0, keyable=False, hidden=False)
self.mainColorAttr = self.container.attr('color')
self.mainColorAttr.showInChannelBox(True)
if self.layer is None:
self.layer = Layers.getLayer(Layers.ANIMATION_CONTROLS)
self.transform = self.install()
utils.lockAndHide(self.lockAttrs, True)
connectControlColors(self.rigControls, self.mainColorAttr)
self.updateSelectionSets()
self.layer.addMembers(self._rigControls.values())
self.containedNodes.append(self.transform)
self.containedNodes.extend(self._rigControls.values())
self.container.addNode(self.containedNodes)
self.containerPublish(self.container)
self.saveCreationParams()
pm.parent(self.transform, self.mainControl)
@property
def rigControls(self):
return self._rigControls.values()
def verifyParams(self):
if self.REQUIRES_SKELETON_CHAIN and not checkJointsAreHierarchy(self.joints):
raise SkeletonError('The specified joints must be from the same skeletal chain')
if len(self.joints) < self.MIN_JOINTS:
raise SkeletonError('Only works with {0} joints!!'.format(self.MIN_JOINTS))
def install(self):
'''
Main juice function
Basic rig framework is created
Return the top-most group node for __init__ method to utilize
'''
raise NotImplementedError("Derive me please")
This is a small snippet of the base class I designed all the rig components (known as “Substructs” in my code) to derive from. The BaseSubstruct class contains a set of parameters stored as members that most, or all, of my rigging structures (leg, arm, etc.) share. Using a OOP approach to creating the rig makes for a very scaleable solution for our pipeline. As new creatures are designed and implemented; I can plan out the automation right away, simply making a new class with most of the ground work derived from the original base class. Continue reading →