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