The following is a sample from the core of an auto-rigging tool that I built.


 """
Created 2020
@author Michelle Swann
This module contains definitions for rig class objects that themselves contain methods for generating and managing rig
related data
"""
import maya.cmds as cmds
import os
from Rig.Utils import Ctrl_Utils
from UI.Utils import UI_Utils
class Rig(object):
    """
    This class is intended to create a class object to contain and manage all of the data stored in the rigConfig file
    """
    def __init__(self, **kwargs):
        """
        Sources data for self.__dict__ from the given rigConfig file or, if None, from kwargs
            kwargs:
                name: str(), given rig name (character name, prop name, etc..) should be formatted like so:
                    'PROJECT_Asset'
                moduleDict: dict(), entry for modules' data if any exists
                skinWeights: str(), file path to skin weight data if any
                proxy: str(), file path to proxy geo if any
        """
        self.CurrentFile = cmds.file(q=True, sn=True)
        self.RigConfigPath = self.getRigConfigPath(**kwargs)
        if UI_Utils.checkFile(self.RigConfigPath):
            self.readWriteRigConfig(mode='r')
        else:
            self.Name = kwargs.get('name', None)
            self.Modules = kwargs.get('moduleDict', dict())
            self.SkinWeights = kwargs.get('skinWeights', None)
            self.Proxy = kwargs.get('proxy', None)
            hierarchy = DefaultHierarchy(name=self.Name)
            self.__dict__['Hierarchy'] = hierarchy.__dict__
            self.readWriteRigConfig(mode='w')
    def manageRig(self, **kwargs):
        """
        Updates the class object's rigConfig file with its current dictionary
            kwargs: Used to update the class object's dictionary ( self.__dict__[kwarg] = kwargs[kwarg] )
        """
        for kwarg in kwargs:
            self.__dict__[kwarg] = kwargs[kwarg]
        self.readWriteRigConfig(mode='w')
    def getRigConfigPath(self, **kwargs):
        """
        If a configPath is not given the user will be prompted to choose a location to save one to
            kwargs:
                configPath: str(), existing path or None if one is not given
            returns:
                configPath: str(), file path to which the rigConfig file can be written or read from
        """
        configPath = kwargs.get('configPath', None)
        if configPath is None:
            if self.CurrentFile == '':
                result = str(cmds.fileDialog2(ff='*.yaml', fm=3, ds=2,
                                              cap='Select existing config file or target directory')[0])
                configPath = os.path.join(result, 'rigConfig.yaml').replace('\\', '/')
            else:
                configPath = os.path.join('/'.join(self.CurrentFile.split('/')[0:-1]), 'rigConfig.yaml').replace('\\',
                                                                                                                 '/')
        return configPath
    def readWriteRigConfig(self, mode):
        """
        Reads or writes to/from the class object's rigConfig file, either overwriting it's own dictionary with the
        loaded data or writing its dictionary data to the file
            Args:
                mode: read or write
        """
        if mode == 'w':
            UI_Utils.readWriteYamlFile(mode=mode, filePath=self.RigConfigPath, data=self.__dict__)
        if mode == 'r':
            self.__dict__ = UI_Utils.readWriteYamlFile(mode=mode, filePath=self.RigConfigPath)
    def manageModule(self, **kwargs):
        """
        Contains all rig module management functions from an overall rig perspective
            kwargs:
                add: bool(), if true, used to add the given module's data entry to the rigConfig file
                remove: bool(), if true, used to remove the given module's entry from the rigConfig file
                edit: bool(), if true, used to update the given module's data entry in the rigConfig file
                module: str(), python module path to the file containing the moduleType/className class definition
                moduleType: str(), also className such as JntSingle, JntArray, or JntChain
                name: str(), name of module to be managed
                (other): additional kwargs are passed to the module instance during entry creation or edits
        """
        add = kwargs.get('add', False)
        remove = kwargs.get('remove', False)
        edit = kwargs.get('edit', False)
        module = kwargs.get('module', None)
        moduleType = kwargs.get('moduleType', None)
        className = moduleType
        name = kwargs.get('name', None)
        if add:
            Instance = getattr(module, className)(**kwargs)
            name = Instance.Name
            if name not in self.Modules:
                self.Modules[name] = Instance.__dict__
                self.readWriteRigConfig(mode='w')
            else:
                cmds.error('{0} already in Modules'.format(Instance.Name))
        if remove:
            self.Modules.pop(name, None)
            self.readWriteRigConfig(mode='w')
        if edit:
            Instance = getattr(module, className)(**kwargs)
            name = Instance.Name
            self.Modules[name] = Instance.__dict__
            self.readWriteRigConfig(mode='w')
    def build(self, **kwargs):
        """
        Contains options for building rigs or layouts. This function is undoable.
            kwargs:
                mode: 'All' or 'Selected', determines which modules are to be built
                layout: if true the given layout(s) will be built
                rig: if true the given rig(s) will be built
                name: if mode is 'Selected' a module name will need to be specified
        """
        cmds.undoInfo(openChunk=True)
        mode = kwargs.get('mode', 'Selected')
        layout = kwargs.get('layout', False)
        rig = kwargs.get('rig', False)
        if mode == 'Selected':
            name = kwargs.get('name', None)
            if name is not None:
                data = self.Modules[name]
                moduleType = data['ModuleType']
                module = data['Module']
                className = moduleType
                Instance = getattr(module, className)(data=data)
                Instance.Module = module
                options = data['Options']
                if layout:
                    Instance.buildLayout(options=options)
                elif rig:
                    Instance.updateLayoutData()
                    self.manageModule(edit=True, module=module, moduleType=moduleType, data=Instance.__dict__)
                    Instance.buildRig(options=options)
                self.Modules[name] = Instance.__dict__
        elif mode == 'All':
            if self.Modules:
                for name in self.Modules:
                    data = self.Modules[name]
                    moduleType = data['ModuleType']
                    module = data['Module']
                    className = moduleType
                    Instance = getattr(module, className)(data=data)
                    Instance.Module = module
                    options = data['Options']
                    if layout:
                        Instance.buildLayout(options=options)
                    elif rig:
                        Instance.updateLayoutData()
                        self.manageModule(edit=True, module=module, moduleType=moduleType, data=Instance.__dict__)
                        Instance.buildRig(options=options)
                    self.Modules[name] = Instance.__dict__
        self.readWriteRigConfig(mode='w')
        cmds.undoInfo(closeChunk=True)
class RigModule(object):
    """
    This class is intended to create a class object to contain and manage rig module data
    """
    def __init__(self, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            kwargs:
                data: dict(), existing data input
                module: str(), python module path to the file containing the moduleType/className class definition
                moduleType: str(), also className, such as JntSingle, JntArray, or JntChain
                side: str(), rig side ('L', 'R', 'C', 'N')
                name: str(), user defined module name, moduleType if None
        """
        data = kwargs.get('data', None)
        if data:
            self.__dict__ = data
        elif not data:
            self.ModuleType = kwargs.get('moduleType', None)  # rig module type
            self.Module = kwargs.get('module', None)  # rig module
            self.Side = kwargs.get('side', 'N')  # module side
            self.Name = kwargs.get('name', self.ModuleType)  # module name
            master = RigMaster('{0}_{1}_01_Master'.format(self.Name, self.Side))
            self.Master = master.__dict__  # master ctrl name
            modIO = '{0}_{1}_01_IO'.format(self.Name, self.Side)
            modIn = '{0}_{1}_01_Input'.format(self.Name, self.Side)
            modOut = '{0}_{1}_01_Output'.format(self.Name, self.Side)
            self.IO = {'modIO': {'Name': modIO,
                                 'Attributes': dict()},
                       'modIn': {'Name': modIn,
                                 'Attributes': dict()},
                       'modOut': {'Name': modOut,
                                  'Attributes': dict()}}  # module IO group names
            self.JointsGroup = RigTransform(name='{0}_{1}_01_Joints'.format(self.Name, self.Side),
                                            parent=self.Master['Name']).__dict__  # module joint group info dict
            self.ControlsGroup = RigTransform(name='{0}_{1}_01_Controls'.format(self.Name, self.Side),
                                              parent=self.Master['Name']).__dict__  # module controls group info dict
            self.KinematicsGroup = RigTransform(name='{0}_{1}_01_Kinematics'.format(self.Name, self.Side),
                                                parent=self.Master['Name']).__dict__  # module kn joint group info dict
            self.GeometryGroup = RigTransform(name='{0}_{1}_01_Geometry'.format(self.Name, self.Side),
                                              parent=self.Master['Name']).__dict__  # module geo group info dict
            self.LayoutGroup = RigTransform(name='{0}_{1}_01_Layout'.format(self.Name, self.Side),
                                            parent=self.Master['Name']).__dict__  # module layout group info dict
            self.JointCount = kwargs.get('jointCount', 1)  # number of joints in the module
            self.Vector = kwargs.get('vector', None)  # direction the module is built
            self.MirrorDict = kwargs.get('mirrorDict')  # map for mirroring control attrs
            self.Controls = kwargs.get('controls')  # controls and their attributes
            self.Constraints = kwargs.get('constraints')  # constraints and their attributes
            self.Options = kwargs.get('options', None)
            self.Layout = self.generateLayoutData()
    def buildLayout(self):
        """
        Builds a master ctrl locator and all layout joints stored in self.Layout
        """
        print('building layout')
        master = RigMaster(name=self.Master['Name'], data=self.Master)
        self.createMasterControl(name=self.Master['Name'], position=(0, 0, 0), orientation=(0, 0, 0), side=self.Side)
        master.SetAttributeValues()
        layoutGroup = RigTransform(name=self.LayoutGroup['Name'], data=self.LayoutGroup)
        cmds.group(n=layoutGroup.Name, em=True)
        cmds.parent(layoutGroup.Name, layoutGroup.Parent)
        layoutGroup.SetAttributeValues()
        # generate layout joints
        for i in range(0, self.JointCount):
            cmds.select(cl=True)
            jnt = RigLayoutJoint(name=self.Layout['jntIndexDict'][i]['Name'], data=self.Layout['jntIndexDict'][i])
            cmds.joint(n=jnt.Name)
            jnt.SetAttributeValues()
            cmds.parent(jnt.Name, jnt.Parent)
        self.updateLayoutData()
    def buildRig(self):
        """
        Builds this rig module's kinematic systems and controls
        """
        print('building rig')
        # update layout data with current positions and rotations
        self.updateLayoutData()
    def updateLayoutData(self):
        """
        Overwrites Attributes data in self.Layout and self.Master
        """
        print('updating layout data')
        for i in range(0, self.JointCount):
            data = self.Layout['jntIndexDict'][i]
            jnt = RigLayoutJoint(name=data['Name'], data=data)
            jnt.RefreshAttributeValues()
        master = RigMaster(name=self.Master['Name'], data=self.Master)
        master.RefreshAttributeValues()
    def createMasterControl(self, **kwargs):
        """
        Creates a master ctrl locator
            kwargs:
                position: tuple(), a given position to place the master ctrl, (0, 0, 0) if None
                orientation: tuple(), a given orientation for the master ctrl, (0, 0, 0) if None
        """
        print('creating master ctrl')
        name = self.Master['Name']
        position = kwargs.get('position', (0, 0, 0))
        orientation = kwargs.get('orientation', (0, 0, 0))
        if name is None:
            cmds.error('module is missing a name')
        else:
            masterCtrl = Ctrl_Utils.createCtrl(shape='master',
                                               name=name,
                                               color='darkGreen')[0]
            cmds.setAttr('{0}.translate'.format(masterCtrl), position[0], position[1], position[2])
            cmds.setAttr('{0}.rotate'.format(masterCtrl), orientation[0], orientation[1], orientation[2])
            cmds.setAttr('{0}.moduleType'.format(masterCtrl), self.ModuleType, type='string')
            cmds.select(masterCtrl)
            return masterCtrl
    def createIO(self, input, output):
        """
        Creates IO groups and adds IO attrs
            args:
                input: dict(), input attrs, their types, values, parents, etc..
                output: dict(), output attrs, their types, values, parents, etc..
        """
        print('creating IO')
        modIO = self.IO['modIO']['Name']
        modIn = self.IO['modIn']['Name']
        modOut = self.IO['modOut']['Name']
        cmds.group(n=modIO, em=True)
        cmds.group(n=modIn, em=True)
        cmds.group(n=modOut, em=True)
        cmds.parent(modIO, self.Master['Name'])
        cmds.parent(modIn, modIO)
        cmds.parent(modOut, modIO)
        for grp in [input, output]:
            self.addIOAttr(attrs=grp, side=self.Side)
    def addIOAttr(self, attrs, side):
        """
        Adds IO attrs to the IO groups
            args:
                attrs: dict(), attrs, their types, values, parents, etc..
                side: str(), rig side ('L', 'R', 'C', 'N')
        """
        attrDict = {}
        for attr in attrs:
            typ = attrs[attr]['Type']
            parent = attrs[attr]['Parent']
            value = attrs[attr]['Value']
            nullTgt = attrs[attr]['Null Target']
            mode = attrs[attr]['Mode']
            IOGrp = self.IO['modIO']['Name']
            grpDict = {'In': self.IO['modIn']['Name'],
                       'Out': self.IO['modOut']['Name']}
            grp = grpDict[mode]
            # add nulls
            if nullTgt is not None:
                null = cmds.group(n='{0}{1}_{2}_01_Null'.format(self.Name, ''.join(attr.split('_')), side), em=True)
                cmds.parent(null, grp)
            else:
                null = None
            # add attr
            if parent is not None:
                if value is not None and 'Matrix' not in typ:
                    cmds.addAttr(grp, ln=attr, at=typ, p=parent, k=True, h=False, dv=value)
                    cmds.addAttr(IOGrp, ln=attr, at=typ, p=parent, k=True, h=False, dv=value)
                else:
                    cmds.addAttr(grp, ln=attr, at=typ, p=parent, k=True, h=False)
                    cmds.addAttr(IOGrp, ln=attr, at=typ, p=parent, k=True, h=False)
            else:
                if value is not None and 'Matrix' not in typ:
                    cmds.addAttr(grp, ln=attr, at=typ, k=True, h=False, dv=value)
                    cmds.addAttr(IOGrp, ln=attr, at=typ, k=True, h=False, dv=value)
                else:
                    cmds.addAttr(grp, ln=attr, at=typ, k=True, h=False)
                    cmds.addAttr(IOGrp, ln=attr, at=typ, k=True, h=False)
            # add decomposeMatrix node if necessary and set values if values exist
            if typ == 'fltMatrix':
                if value is not None:
                    cmds.setAttr('{0}.{1}'.format(grp, attr), value, typ='matrix')
                mat = cmds.createNode('decomposeMatrix',
                                      n='{0}_{1}_decomposeMatrix'.format(''.join(grp.split('_')),
                                                                         ''.join(attr.split('_'))))
            else:
                if value is not None:
                    cmds.setAttr('{0}.{1}'.format(grp, attr), value)
                mat = None
            # connect attrs
            if mode == 'In':
                cmds.connectAttr('{0}.{1}'.format(IOGrp, attr), '{0}.{1}'.format(grp, attr), f=True)
                if mat is not None:
                    cmds.connectAttr('{0}.{1}'.format(grp, attr), '{0}.inputMatrix'.format(mat), f=True)
                    cmds.connectAttr('{0}.outputTranslate'.format(mat), '{0}.translate'.format(null), f=True)
                    cmds.connectAttr('{0}.outputRotate'.format(mat), '{0}.rotate'.format(null), f=True)
                    cmds.connectAttr('{0}.outputScale'.format(mat), '{0}.scale'.format(null), f=True)
                    cmds.connectAttr('{0}.outputShear'.format(mat), '{0}.shear'.format(null), f=True)
                    cmds.connectAttr('{0}.outputQuat'.format(mat), '{0}.rotateQuaternion'.format(null), f=True)
            if mode == 'Out':
                cmds.connectAttr('{0}.{1}'.format(grp, attr), '{0}.{1}'.format(IOGrp, attr), f=True)
                if mat is not None:
                    cmds.connectAttr('{0}.worldMatrix[0]'.format(null), '{0}.{1}'.format(grp, attr), f=True)
                    cmds.connectAttr('{0}.outputTranslate'.format(mat), '{0}.translate'.format(null), f=True)
                    cmds.connectAttr('{0}.outputRotate'.format(mat), '{0}.rotate'.format(null), f=True)
                    cmds.connectAttr('{0}.outputScale'.format(mat), '{0}.scale'.format(null), f=True)
                    cmds.connectAttr('{0}.outputShear'.format(mat), '{0}.shear'.format(null), f=True)
                    cmds.connectAttr('{0}.outputQuat'.format(mat), '{0}.rotateQuaternion'.format(null), f=True)
                    if nullTgt is not None:
                        cmds.connectAttr('{0}.worldMatrix[0]'.format(nullTgt), '{0}.inputMatrix'.format(mat), f=True)
            attrDict[attr] = [null, mat]
            self.IO['mod{0}'.format(mode)]['Attributes'] = attrDict
        return attrDict
    def createKinematicJoints(self):
        """
        Creates this rig module's kinematic system(s) joints
        """
        print('creating kinematic joints')
        jointsGroup = RigTransform(name=self.JointsGroup['Name'], data=self.JointsGroup)
        cmds.group(n=jointsGroup.Name, em=True)
        cmds.parent(jointsGroup.Name, jointsGroup.Parent)
        jointsGroup.SetAttributeValues()
        for i in range(0, self.JointCount):
            cmds.select(cl=True)
            layoutJnt = self.Layout['jntIndexDict'][i]['Name']
            jnt = layoutJnt.replace('_LayoutJnt', '_Kn')
            parent = jointsGroup.Name
            cmds.joint(n=jnt)
            cmds.parent(jnt, parent)
            cmds.delete(cmds.parentConstraint(layoutJnt, jnt))
    def createControls(self):
        """
        Creates this rig module's kinematic system(s) controls
        """
        print('creating controls')
        colorDict = {'L': 'blue',
                     'R': 'red',
                     'C': 'yellow',
                     'N': None,
                     None: None}
        controlsGroup = RigTransform(name=self.ControlsGroup['Name'], data=self.ControlsGroup)
        cmds.group(n=controlsGroup.Name, em=True)
        cmds.parent(controlsGroup.Name, controlsGroup.Parent)
        controlsGroup.SetAttributeValues()
        ctrlIndexDict = {}
        for i in range(0, self.JointCount):
            jnt = self.Layout['jntIndexDict'][i]['Name'].replace('_LayoutJnt', '_Kn')
            ctrlInstance = RigControl(name=jnt.replace('_Kn', '_Ctrl'), color=colorDict[self.Side],
                                      buffer=True, parent=controlsGroup.Name)
            ctrlIndexDict[ctrlInstance.Name] = ctrlInstance.__dict__
            ctrlInstance.create(snapTo=jnt)
            cmds.parentConstraint(ctrlInstance.Name, jnt, mo=True)
        self.Controls = ctrlIndexDict
    def hideMaster(self):
        """
        Hides this rig module's master ctrl locator and associated annotations
        """
        print('hiding master')
        for item in cmds.listRelatives(self.Master['Name']):
            if cmds.nodeType(item) == 'annotationShape' or cmds.nodeType(item) == 'locator':
                cmds.setAttr('{0}.visibility'.format(item), 0)
    def finalize(self):
        """
        Performs actions to finalize this rig module's rig build
        """
        print('finalizing')
        # hide masterCtrl shapes
        self.hideMaster()
        # hide layout
        print('hiding layout group')
        cmds.setAttr('{0}.visibility'.format(self.LayoutGroup['Name']), 0)
    def generateLayoutData(self):
        """
        Generates this rig module's layout data including master controls, layout joints and their positions and
        orientations
        """
        print('generating layout data')
class RigObject(object):
    """
    This is the base class from which other rig objects are derived
    """
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                parent: str(), this object's parent
        """
        self.Name = name  # object name
        data = kwargs.get('data', None)
        if not data:
            self.Parent = kwargs.get('parent', None)  # object parent
            self.Attributes = dict()  # object attributes and their associated properties
        else:
            self.__dict__ = data
    def RefreshAttributeValues(self):
        """
        Overwrites data in self.Attributes by querying those values from the associated Maya object
        """
        axes = ['X', 'Y', 'Z']
        axisVal = float()
        for attr in self.Attributes.keys():
            if 'translate' in attr:
                value = cmds.xform(self.Name, t=True, q=True, ws=True)
                for i in range(0, 3):
                    axis = axes[i]
                    if attr[-1] == axis:
                        axisVal = value[i]
                self.Attributes[attr]['value'] = axisVal
            elif 'rotate' in attr:
                value = cmds.xform(self.Name, ro=True, q=True, ws=True)
                for i in range(0, 3):
                    axis = axes[i]
                    if attr[-1] == axis:
                        axisVal = value[i]
                self.Attributes[attr]['value'] = axisVal
            elif 'scale' in attr:
                value = cmds.xform(self.Name, s=True, q=True, ws=True)
                for i in range(0, 3):
                    axis = axes[i]
                    if attr[-1] == axis:
                        axisVal = value[i]
                self.Attributes[attr]['value'] = axisVal
            else:
                self.Attributes[attr]['value'] = cmds.getAttr('{0}.{1}'.format(self.Name, attr))
            self.Attributes[attr]['locked'] = cmds.getAttr('{0}.{1}'.format(self.Name, attr), l=True)
            self.Attributes[attr]['hidden'] = cmds.getAttr('{0}.{1}'.format(self.Name, attr), cb=True)
            self.Attributes[attr]['input'] = cmds.connectionInfo('{0}.{1}'.format(self.Name, attr), sfd=True)
            self.Attributes[attr]['output'] = cmds.connectionInfo('{0}.{1}'.format(self.Name, attr), dfs=True)
    def SetAttributeValues(self):
        """
        Sets and makes connections to and from attributes in self.Attributes for the associated Maya object
        """
        for attr in self.Attributes.keys():
            Hidden = self.Attributes[attr]['hidden']
            Input = self.Attributes[attr]['input']
            Locked = self.Attributes[attr]['locked']
            Output = self.Attributes[attr]['output']
            Value = self.Attributes[attr]['value']
            try:
                cmds.setAttr('{0}.{1}'.format(self.Name, attr), Value, cb=Hidden, l=Locked)
            except RuntimeError as err:
                cmds.warning('failed to set {0}.{1}\n{2}'.format(self.Name, attr, err))
            if Input:
                try:
                    cmds.connectAttr(Input, '{0}.{1}'.format(self.Name, attr), f=True)
                except RuntimeError as err:
                    cmds.warning('failed to connect input attr {0} to {1}.{2}\n{3}'.format(Input, self.Name, attr, err))
            if Output:
                for tgt in Output:
                    try:
                        cmds.connectAttr('{0}.{1}'.format(self.Name, attr), tgt, f=True)
                    except RuntimeError as err:
                        cmds.warning('failed to connect output attr {0} to {1}.{2}\n{3}'.format(tgt, self.Name, attr,
                                                                                                err))
    def RefreshParent(self):
        """
        Overwrites the object's parent in self.Parent by querying the associated Maya object
        """
        self.Parent = cmds.listRelatives(self.Name, p=True)[0]
class RigTransform(RigObject):
    """
    Expanded RigObject. Contains new variable Type and additional Attributes entries
    """
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                (other): additional kwargs are passed to the parent instance
        """
        RigObject.__init__(self, name, **kwargs)
        data = kwargs.get('data', None)
        if not data:
            self.Type = 'transform'
            self.Attributes['translateX'] = {'value': 0,
                                             'locked': False,
                                             'hidden': False,
                                             'input': None,
                                             'output': None}
            self.Attributes['translateY'] = {'value': 0,
                                             'locked': False,
                                             'hidden': False,
                                             'input': None,
                                             'output': None}
            self.Attributes['translateZ'] = {'value': 0,
                                             'locked': False,
                                             'hidden': False,
                                             'input': None,
                                             'output': None}
            self.Attributes['rotateX'] = {'value': 0,
                                          'locked': False,
                                          'hidden': False,
                                          'input': None,
                                          'output': None}
            self.Attributes['rotateY'] = {'value': 0,
                                          'locked': False,
                                          'hidden': False,
                                          'input': None,
                                          'output': None}
            self.Attributes['rotateZ'] = {'value': 0,
                                          'locked': False,
                                          'hidden': False,
                                          'input': None,
                                          'output': None}
            self.Attributes['scaleX'] = {'value': 1,
                                         'locked': False,
                                         'hidden': False,
                                         'input': None,
                                         'output': None}
            self.Attributes['scaleY'] = {'value': 1,
                                         'locked': False,
                                         'hidden': False,
                                         'input': None,
                                         'output': None}
            self.Attributes['scaleZ'] = {'value': 1,
                                         'locked': False,
                                         'hidden': False,
                                         'input': None,
                                         'output': None}
            self.Attributes['visibility'] = {'value': 1,
                                             'locked': False,
                                             'hidden': False,
                                             'input': None,
                                             'output': None}
        else:
            self.__dict__ = data
class RigJoint(RigTransform):
    """
    Expanded RigTransform. Contains additional Attributes entries
    """
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                (other): additional kwargs are passed to the parent instance
        """
        RigTransform.__init__(self, name, **kwargs)
        data = kwargs.get('data', None)
        if not data:
            self.Type = 'joint'
            self.Attributes['radius'] = {'value': 1,
                                         'locked': False,
                                         'hidden': False,
                                         'input': None,
                                         'output': None}
            self.Attributes['segmentScaleCompensate'] = {'value': 0,
                                                         'locked': False,
                                                         'hidden': True,
                                                         'input': None,
                                                         'output': None}
            self.Attributes['jointOrientX'] = {'value': 0,
                                               'locked': False,
                                               'hidden': True,
                                               'input': None,
                                               'output': None}
            self.Attributes['jointOrientY'] = {'value': 0,
                                               'locked': False,
                                               'hidden': True,
                                               'input': None,
                                               'output': None}
            self.Attributes['jointOrientZ'] = {'value': 0,
                                               'locked': False,
                                               'hidden': True,
                                               'input': None,
                                               'output': None}
            self.Attributes['overrideEnabled'] = {'value': 0,
                                                  'locked': False,
                                                  'hidden': True,
                                                  'input': None,
                                                  'output': None}
            self.Attributes['overrideColor'] = {'value': 0,
                                                'locked': False,
                                                'hidden': True,
                                                'input': None,
                                                'output': None}
            self.Attributes['overrideDisplayType'] = {'value': 0,
                                                      'locked': False,
                                                      'hidden': True,
                                                      'input': None,
                                                      'output': None}
        else:
            self.__dict__ = data
class RigKinematicJoint(RigJoint):
    """
    Expanded RigJoint. Contains additional Attributes entries
    """
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                (other): additional kwargs are passed to the parent instance
        """
        RigJoint.__init__(self, name, **kwargs)
        data = kwargs.get('data', None)
        if not data:
            self.Attributes['overrideEnabled'] = {'value': 1,
                                                  'locked': False,
                                                  'hidden': True,
                                                  'input': None,
                                                  'output': None}
            self.Attributes['overrideColor'] = {'value': 0,
                                                'locked': False,
                                                'hidden': True,
                                                'input': None,
                                                'output': None}
            self.Attributes['overrideDisplayType'] = {'value': 2,
                                                      'locked': False,
                                                      'hidden': True,
                                                      'input': None,
                                                      'output': None}
        else:
            self.__dict__ = data
class RigLayoutJoint(RigJoint):
    """
    Expanded RigJoint. Contains additional Attributes entries
    """
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                (other): additional kwargs are passed to the parent instance
        """
        RigJoint.__init__(self, name, **kwargs)
        data = kwargs.get('data', None)
        if not data:
            self.Attributes['overrideEnabled'] = {'value': 1,
                                                  'locked': False,
                                                  'hidden': True,
                                                  'input': None,
                                                  'output': None}
            self.Attributes['overrideColor'] = {'value': 18,
                                                'locked': False,
                                                'hidden': True,
                                                'input': None,
                                                'output': None}
            self.Attributes['overrideDisplayType'] = {'value': 0,
                                                      'locked': False,
                                                      'hidden': True,
                                                      'input': None,
                                                      'output': None}
        else:
            self.__dict__ = data
class RigBindJoint(RigJoint):
    """
    Expanded RigJoint. Contains additional Attributes entries
    """
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                (other): additional kwargs are passed to the parent instance
        """
        RigJoint.__init__(self, name, **kwargs)
        data = kwargs.get('data', None)
        if not data:
            self.Attributes['overrideEnabled'] = {'value': 1,
                                                  'locked': False,
                                                  'hidden': True,
                                                  'input': None,
                                                  'output': None}
            self.Attributes['overrideColor'] = {'value': 13,
                                                'locked': False,
                                                'hidden': True,
                                                'input': None,
                                                'output': None}
            self.Attributes['overrideDisplayType'] = {'value': 2,
                                                      'locked': False,
                                                      'hidden': True,
                                                      'input': None,
                                                      'output': None}
        else:
            self.__dict__ = data
class RigControl(RigTransform):
    """
    Expanded RigTransform. Contains additional Attributes entries, control shape and color definitions, and functions
    for building those shapes and managing their CV data
    """
    #todo fix naming on shape nodes
    colorDict = {
        'blue': 6,
        'lightBlue': 18,
        'darkBlue': 5,
        'red': 13,
        'lightRed': 20,
        'darkRed': 4,
        'yellow': 17,
        'lightYellow': 23,
        'darkYellow': 22,
        'green': 14,
        'lightGreen': 19,
        'darkGreen': 7,
        'black': 1,
        'white': 16
        }
    shapeDict = {
        'cube': {
            'type': 'curve',
            'shapeCount': 1,
            'defaultShapeCVDict': {
                0: {'degree': 1,
                    0: (-0.5, -0.5, -0.5),
                    1: (-0.5, -0.5, 0.5),
                    2: (0.5, -0.5, 0.5),
                    3: (0.5, -0.5, -0.5),
                    4: (-0.5, -0.5, -0.5),
                    5: (-0.5, 0.5, -0.5),
                    6: (0.5, 0.5, -0.5),
                    7: (0.5, -0.5, -0.5),
                    8: (0.5, 0.5, -0.5),
                    9: (0.5, 0.5, 0.5),
                    10: (0.5, -0.5, 0.5),
                    11: (0.5, 0.5, 0.5),
                    12: (-0.5, 0.5, 0.5),
                    13: (-0.5, -0.5, 0.5),
                    14: (-0.5, 0.5, 0.5),
                    15: (-0.5, 0.5, -0.5)
                    }
                }
            },
        'circle': {
            'type': 'curve',
            'shapeCount': 1,
            'defaultShapeCVDict': {
                0: {'degree': 1,
                    0: [0.783612, 0.0, -0.783612],
                    1: [0.0, 0.0, -1.108194],
                    2: [-0.783612, 0.0, -0.783612],
                    3: [-1.108194, 0.0, -0.0],
                    4: [-0.783612, -0.0, 0.783612],
                    5: [-0.0, -0.0, 1.108194],
                    6: [0.783612, -0.0, 0.783612],
                    7: [1.108194, -0.0, 0.0],
                    8: [1.108194, -0.0, 0.0],
                    9: [1.108194, -0.0, 0.0],
                    10: [0.783612, 0.0, -0.783612]
                    }
                }
            },
        'sphere': {
            'type': 'curve',
            'shapeCount': 3,
            'defaultShapeCVDict': {
                0: {'degree': 3,
                    0: [0.0, 0.783612, -0.783612],
                    1: [0.0, 1.108194, -0.0],
                    2: [-0.0, 0.783612, 0.783612],
                    3: [-0.0, 0.0, 1.108194],
                    4: [-0.0, -0.783612, 0.783612],
                    5: [-0.0, -1.108194, 0.0],
                    6: [0.0, -0.783612, -0.783612],
                    7: [0.0, -0.0, -1.108194],
                    8: [0.0, -0.0, -1.108194],
                    9: [0.0, -0.0, -1.108194],
                    10: [0.0, -0.0, -1.108194]
                    },
                1: {'degree': 3,
                    0: [0.783612, 0.0, -0.783612],
                    1: [0.0, 0.0, -1.108194],
                    2: [-0.783612, 0.0, -0.783612],
                    3: [-1.108194, 0.0, -0.0],
                    4: [-0.783612, -0.0, 0.783612],
                    5: [-0.0, -0.0, 1.108194],
                    6: [0.783612, -0.0, 0.783612],
                    7: [1.108194, -0.0, 0.0],
                    8: [1.108194, -0.0, 0.0],
                    9: [1.108194, -0.0, 0.0],
                    10: [1.108194, -0.0, 0.0]
                    },
                2: {'degree': 3,
                    0: [0.783612, 0.783612, 0.0],
                    1: [0.0, 1.108194, 0.0],
                    2: [-0.783612, 0.783612, 0.0],
                    3: [-1.108194, 0.0, 0.0],
                    4: [-0.783612, -0.783612, 0.0],
                    5: [-0.0, -1.108194, 0.0],
                    6: [0.783612, -0.783612, 0.0],
                    7: [1.108194, -0.0, 0.0],
                    8: [1.108194, -0.0, 0.0],
                    9: [1.108194, -0.0, 0.0],
                    10: [1.108194, -0.0, 0.0]
                    }
                }
            },
        'hemisphere': {
            'type': 'curve',
            'shapeCount': 3,
            'defaultShapeCVDict': {
                0: {'degree': 3,
                    0: [0.0, 0.783612, -0.783612],
                    1: [0.0, 1.108194, -0.0],
                    2: [-0.0, 0.783612, 0.783612],
                    3: [-0.0, 0.0, 1.108194],
                    4: [-0.0, 2e-06, 0.783612],
                    5: [-0.0, -2e-06, 0.0],
                    6: [0.0, 2e-06, -0.783612],
                    7: [0.0, -0.0, -1.108194],
                    8: [0.0, -0.0, -1.108194],
                    9: [0.0, -0.0, -1.108194],
                    10: [0.0, -0.0, -1.108194]
                    },
                1: {'degree': 3,
                    0: [0.783612, 0.0, -0.783612],
                    1: [0.0, 0.0, -1.108194],
                    2: [-0.783612, 0.0, -0.783612],
                    3: [-1.108194, 0.0, -0.0],
                    4: [-0.783612, -0.0, 0.783612],
                    5: [-0.0, -0.0, 1.108194],
                    6: [0.783612, -0.0, 0.783612],
                    7: [1.108194, -0.0, 0.0],
                    8: [1.108194, -0.0, 0.0],
                    9: [1.108194, -0.0, 0.0],
                    10: [1.108194, -0.0, 0.0]
                    },
                2: {'degree': 3,
                    0: [0.783612, 0.783612, 0.0],
                    1: [0.0, 1.108194, 0.0],
                    2: [-0.783612, 0.783612, 0.0],
                    3: [-1.108194, 0.0, 0.0],
                    4: [-0.783612, 2e-06, 0.0],
                    5: [-0.0, -2e-06, 0.0],
                    6: [0.783612, 2e-06, 0.0],
                    7: [1.108194, -0.0, 0.0],
                    8: [1.108194, -0.0, 0.0],
                    9: [1.108194, -0.0, 0.0],
                    10: [1.108194, -0.0, 0.0]
                    }
                }
            },
        'square': {
            'type': 'curve',
            'shapeCount': 1,
            'defaultShapeCVDict': {
                0: {'degree': 1,
                    0: (-0.5, 0, -0.5),
                    1: (-0.5, 0, 0.5),
                    2: (0.5, 0, 0.5),
                    3: (0.5, 0, -0.5)
                    }
                }
            },
        'master': {
            'type': 'locator',
            'shapeCount': 1
            }
        }
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                shape: str(), which shape this object will build
                color: str(), the color of the shapes this object will build
                buffer: bool(), if True, builds a buffer control group over the associated Maya object
                input: int(), defines the number of additional buffer/input groups that will be built over the
                    associated Maya object
                (other): additional kwargs are passed to the parent instance
        """
        RigTransform.__init__(self, name, **kwargs)
        data = kwargs.get('data', None)
        if not data:
            self.Type = 'control'
            self.Shape = kwargs.get('shape', 'circle')
            self.Color = kwargs.get('color', None)
            self.Buffer = kwargs.get('buffer', None)
            self.InputLayerCount = kwargs.get('input', 0)
            self.InputLayerDict = dict()
            self.ShapeCVDict = self.shapeDict[self.Shape]['defaultShapeCVDict']
            self.ShapeCount = self.shapeDict[self.Shape]['shapeCount']
            self.Attributes['useOutlinerColor'] = {'value': 0,
                                                   'locked': False,
                                                   'hidden': True,
                                                   'input': None,
                                                   'output': None}
            self.Attributes['outlinerColorR'] = {'value': 0,
                                                 'locked': False,
                                                 'hidden': True,
                                                 'input': None,
                                                 'output': None}
            self.Attributes['outlinerColorG'] = {'value': 0,
                                                 'locked': False,
                                                 'hidden': True,
                                                 'input': None,
                                                 'output': None}
            self.Attributes['outlinerColorB'] = {'value': 0,
                                                 'locked': False,
                                                 'hidden': True,
                                                 'input': None,
                                                 'output': None}
            self.Attributes['overrideEnabled'] = {'value': 0,
                                                  'locked': False,
                                                  'hidden': True,
                                                  'input': None,
                                                  'output': None}
            self.Attributes['overrideColor'] = {'value': 0,
                                                'locked': False,
                                                'hidden': True,
                                                'input': None,
                                                'output': None}
            self.Attributes['overrideDisplayType'] = {'value': 0,
                                                      'locked': False,
                                                      'hidden': True,
                                                      'input': None,
                                                      'output': None}
        else:
            self.__dict__ = data
    def manageShapeCVData(self, **kwargs):
        """
        Reads or writes/overwrites self.ShapeCVDict data. This dictionary stores linear degree ctrl shape cv positional
        data
            kwargs:
                mode: str(), Read ('r') or write ('w'), if None defaults to read
        """
        mode = kwargs.get('mode', 'r')
        for s in range(0, self.ShapeCount+1):
            cvDict = self.ShapeCVDict[s]
            cvCount = len([key for key in cvDict.keys() if key != 'degree'])
            if mode == 'w':
                for i in range(1, cvCount+1):
                    cv = '{0}Shape{1}.cv[{2}]'.format(self.Name, s, i)
                    position = [round(x, 6) for x in cmds.xform(cv, q=True, t=True, ws=True)]
                    cvDict[i] = position
                self.ShapeCVDict[s] = cvDict
            if mode == 'r':
                for i in range(0, cvCount+1):
                    cv = '{0}Shape{1}.cv[{2}]'.format(self.Name, s, i)
                    position = cvDict[i]
                    cmds.xform(cv, t=position, ws=True)
            
    def create(self, **kwargs):
        """
        Creates the associated Maya object control and any control groups
            kwargs:
                snapTo: str(), a target to snap the new control or control groups to
                position:   tuple(), a target position to set the new control or control groups to
                orientation: tuple(), a target orientation to set the new control or control groups to
        """
        snapTo = kwargs.get('snapTo', None)
        position = kwargs.get('position', None)
        orientation = kwargs.get('orientation', None)
        if self.Shape == 'master':
            ctrl = cmds.spaceLocator(n=self.Name)[0]
        else:
            ctrl = cmds.group(n=self.Name, em=True)
            for s in range(0, self.ShapeCount):
                degree = self.ShapeCVDict[s]['degree']
                cvCount = len(self.ShapeCVDict[s].keys())
                shapeName = '{0}Shape{1}'.format(self.Name, s)
                tempCrv = cmds.curve(n='temp', d=degree, p=[self.ShapeCVDict[s][cv] for cv in range(0, cvCount-1)])
                tempShape = cmds.listRelatives(tempCrv)[0]
                cmds.rename(tempShape, shapeName)
                cmds.parent(shapeName, ctrl, s=True, r=True)
                cmds.delete(tempCrv)
            if self.Buffer is not None:
                suffix = 'Grp'
                self.groupSpecial(suffix=suffix)
                grpInstance = RigTransform(name=ctrl+suffix)
                if self.Parent is not None:
                    cmds.parent(grpInstance.Name, self.Parent)
                grpInstance.RefreshParent()
                grpInstance.RefreshAttributeValues()
                self.Buffer = grpInstance.__dict__
                self.Parent = grpInstance.Name
                if snapTo is not None:
                    cmds.delete(cmds.parentConstraint(snapTo, grpInstance.Name))
                if position is not None:
                    cmds.xform(grpInstance.Name, t=position, ws=True)
                if orientation is not None:
                    cmds.xform(grpInstance.Name, ro=orientation, ws=True)
            if self.InputLayerCount is not 0 and self.Buffer is not None:
                for i in range(0, self.InputLayerCount):
                    suffix = 'Input{0}'.format(i)
                    self.groupSpecial(suffix=suffix)
                    grpInstance = RigTransform(name=ctrl+suffix)
                    if self.Parent is not None:
                        cmds.parent(grpInstance.Name, self.Parent)
                    grpInstance.RefreshParent()
                    grpInstance.RefreshAttributeValues()
                    self.InputLayerDict[i] = grpInstance.__dict__
                    self.Parent = grpInstance.Name
                    if snapTo is not None:
                        cmds.delete(cmds.parentConstraint(snapTo, grpInstance.Name))
                    if position is not None:
                        cmds.xform(grpInstance.Name, t=position, ws=True)
                    if orientation is not None:
                        cmds.xform(grpInstance.Name, ro=orientation, ws=True)
            cmds.bakePartialHistory(ctrl, ppt=True)
        if self.Color is not None:
            if self.Shape == 'master':
                for item in cmds.listRelatives(ctrl):
                    cmds.setAttr('{0}.overrideEnabled'.format(item), 1)
                    cmds.setAttr('{0}.overrideColor'.format(item), self.Color)
            else:
                cmds.setAttr('{0}.overrideEnabled'.format(ctrl), 1)
                cmds.setAttr('{0}.overrideColor'.format(ctrl), self.Color)
    
        cmds.setAttr('{0}.visibility'.format(ctrl), l=True, k=False)
        
    def groupSpecial(self, suffix):
        """
        Creates a group over the associated Maya object and matches it's position and orientation
            args:
                suffix: The suffix to append to the end of the new group's name
        """
        grpParent = cmds.listRelatives(self.Name, p=True)
        grp = cmds.group(n=self.Name + suffix, em=True)
        cmds.delete(cmds.parentConstraint(self.Name, grp))
        if grpParent is not None:
            cmds.parent(grp, grpParent)
        cmds.parent(self.Name, grp)
class RigMaster(RigControl):
    """
    Extended RigControl. Contains additional Attributes entries
    """
    def __init__(self, name, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            args:
                name: str(), this object's name
            kwargs:
                data: dict(), existing data input
                (other): additional kwargs are passed to the parent instance
        """
        RigControl.__init__(self, name, **kwargs)
        data = kwargs.get('data', None)
        if not data:
            self.Type = 'master'
            self.Shape = 'master'
            self.Attributes['built'] = {'value': False,
                                        'locked': False,
                                        'hidden': False,
                                        'input': None,
                                        'output': None}
            self.Attributes['moduleType'] = {'value': None,
                                             'locked': False,
                                             'hidden': False,
                                             'input': None,
                                             'output': None}
        else:
            self.__dict__ = data
class DefaultHierarchy:
    """
    A class for constructing default hierarchy information
    """
    def __init__(self, **kwargs):
        """
        Sources data for self.__dict__ from given data or, if None, from kwargs
            kwargs:
                name: str(), the rig's name
                data: dict(), existing data input
        """
        data = kwargs.get('data', None)
        name = kwargs.get('name', None)
        if not data:
            self.World = RigTransform(name='{0}_N_01_World'.format(name),
                                      parent=None).__dict__
            self.PlacementCtrl = RigControl(name='{0}_N_01_PlacementCtrl'.format(name),
                                            parent='{0}_N_01_Controls'.format(name)).__dict__
            self.GeometryGroup = RigTransform(name='{0}_N_01_Geometry'.format(name),
                                              parent='{0}_N_01_World'.format(name)).__dict__
            self.KinematicsGroup = RigTransform(name='{0}_N_01_Kinematics'.format(name),
                                                parent='{0}_N_01_World'.format(name)).__dict__
            self.ControlsGroup = RigTransform(name='{0}_N_01_Controls'.format(name),
                                              parent='{0}_N_01_World'.format(name)).__dict__
            self.JointsGroup = RigTransform(name='{0}_N_01_Joints'.format(name),
                                            parent='{0}_N_01_World'.format(name)).__dict__
            self.LayoutGroup = RigTransform(name='{0}_N_01_Layout'.format(name),
                                            parent='{0}_N_01_World'.format(name)).__dict__
            self.BonesGroup = RigTransform(name='{0}_N_01_Bones'.format(name),
                                           parent='{0}_N_01_World'.format(name)).__dict__
            self.JointsRoot = RigKinematicJoint(name='{0}Root_N_01_Kn'.format(name),
                                                parent='{0}_N_01_Joints'.format(name)).__dict__
            self.LayoutRoot = RigLayoutJoint(name='{0}Root_N_01_LayoutJnt'.format(name),
                                             parent='{0}_N_01_Layout'.format(name)).__dict__
            self.BonesRoot = RigBindJoint(name='{0}Root_N_01_Bn'.format(name),
                                          parent='{0}_N_01_Bones'.format(name)).__dict__
        else:
            self.__dict__ = data
    def create(self):
        """
        Creates the associated Maya objects
        """
        hierarchyItems = [x for x in self.__dict__.keys()]
        while len(hierarchyItems) > 0:
            item = hierarchyItems[0]
            name = self.__dict__[item]['Name']
            parent = self.__dict__[item]['Parent']
            typ = self.__dict__[item]['Type']
            cmds.select(cl=True)
            if parent is None:
                if typ == 'transform':
                    cmds.group(n=name, em=True)
                    if parent is not None:
                        cmds.parent(name, parent)
                if typ == 'joint':
                    cmds.joint(n=name)
                    cmds.parent(name, parent)
                if typ == 'control':
                    shape = self.__dict__[item]['Shape']
                    color = self.__dict__[item]['Color']
                    (ctrl, ctrlGrp) = Ctrl_Utils.createCtrl(name=name, shape=shape, color=color)
                    cmds.parent(ctrlGrp, parent)
                hierarchyItems.pop(0)
            elif cmds.objExists(parent):
                if typ == 'transform':
                    cmds.group(n=name, em=True)
                    cmds.parent(name, parent)
                if typ == 'joint':
                    cmds.joint(n=name)
                    cmds.parent(name, parent)
                if typ == 'control':
                    shape = self.__dict__[item]['Shape']
                    color = self.__dict__[item]['Color']
                    (ctrl, ctrlGrp) = Ctrl_Utils.createCtrl(name=name, shape=shape, color=color)
                    cmds.parent(ctrlGrp, parent)
                hierarchyItems.pop(0)
            else:
                hierarchyItems.pop(0)
                hierarchyItems.insert(-1, item)