• White LinkedIn Icon
  • White Instagram Icon
  • artstationLogo
  • email_logo
M_header_simple.png
  • DEMO REEL

  • PROJECTS

    • Bartok
    • Tunies
    • Hex Limit
    • Bearly
    • Mech Triceratops
  • TOOLS

    • Github
    • Rigamajig
    • Shot Sculptor
  • ABOUT

  • CONTACT

  • BLOG

  • More

    Use tab to navigate through the menu items.
    • White LinkedIn Icon
    • White Instagram Icon
    • email_logo
    • github_lgoo
    • All Posts
    • Rigamajig2
    • Rigging Tips
    Search
    • Mason Smigel

    Building a rig

    Updated: Mar 23

    Building a rig requires more than just the components, it also requires inputs like the model, skeleton, and post-build operations like constructing deformations, pose readers, and final publishing steps.


    For this rigamajig2 uses the builder, a system of short scripts to wrap up all the required methods to build a rig.



    Managing data

    Rigs contain a lot of data. A key design choice of rigamajig2 was that almost all data is saved outside of Maya so the entire rig can be rebuilt. or this, the builder relies on an external folder structure to manage the data.



    The builder uses a .rig file to store relative paths to data needed in the build. The build has the functionality to both load and save the data for the rig.

    {
        "type": "AbstractData", 
        "user": "masonsmigel", 
        "time": "2021-11-22 23:12:13", 
        "data": {
            "model_file": null, 
            "skeleton_file": "skeleton.ma", 
            "skeleton_pos": "skeleton_pos.json", 
            "pre_scripts": [], 
            "post_scripts": [], 
            "pub_scripts": [], 
            "control_shapes" : "controlShapes.json", 
            "guides": "guides.json",
            "components": "components.json",
            "psd": "psd.json",
            "output_file": "../out/biped_rig.ma"
        }
    
    

    An example .rig file



    Using the builder


    The builder class is a system of methods to sequentially run code. Under the hood the code looks like this:


    import rigamajig2.maya.rig.builder as builder
    
    # initialize the builder 
    path = "/Users/masonsmigel/Documents/dev/maya/rigamajig2/archetypes/biped/biped.rig"
    b = builder.Builder(rigFile=path)
    
    # artist can manually run each step... 
    b.load_required_plugins()
    b.pre_script()
    b.import_model()
    b.import_skeleton()
    b.load_joint_positions()
    b.load_components()
    b.initalize()
    b.build()
    b.connect()
    b.finalize()
    b.load_controlShapes()
    b.load_deform_data()
    b.post_script()
    b.optimize()
    b.pub_script()
    b.publish()
    
    # ... or use the run() method to run all the above code at once. 
    b.run()

    Because rigamajig2 relies entirely on external data the final rig can be built without any manual input. The primary method for publishing rigs to the end-user (aka animator) is to use the command line since it seems a bit silly to wait for Maya to boot up just to run a script that doesn't require the rigger.


    Using the command line allows the rigger to rebuild multiple rigs at once. As an example use case, if a bug is found in the rig and is present across all characters in a production a TD could adjust the code in the component and rebuild all rigs to push the change to the animators.


    However, the command line is only useful during the publishing phase of building a rig, while data is still being created the builder UI allows users to more intuitively edit and walk through rig construction step by step.

    Between steps, the rigger has time to author new data within Maya. This can range from positioning joints and guides to editing control shapes to skinning and blendshapes. All this data is pushed back to external files in the rig environment and referenced the next time the rig is built.


    • Rigamajig2
    • Mason Smigel

    Components

    Updated: Mar 23

    The foundation of the rig is the components. While the system utilizes other scripts and procedures to create the character, components create all behaviors of the rig.


    What is a component

    Within rigamajig2 components have two contexts: On the TD end (Python) they define code structure and execution and on the rigging end (Maya) they control rig behaviors.


    On the TD side components are python classes that follow specific rules about the code to set up and run procedures in a logical order. Because they use classes, components can be sub-classed for more control and proceduralism when creating new components.


    For the rigger, components can define any setup within a rig, ranging from an ikfk limb with soft ik, to a single control. However, they must be contained within a single system (space switches are an exception). The Maya rig component is the output of the code.



    Component Code structure

    Components are designed to be simple to understand and edit, allowing anyone with technical knowledge to jump in and write their own components.


    input

    While components can be expanded on within the subclass, there are several parameters all components expect.


    • name: Name of the component is required to build a component. This is also where the user can define a side by adding a side token (_l, _left, _lf, _r, etc...).

    • input: All components require some input node, most often this is a list of joints.

    • The specifics depend on the developer, for example my arm component requires a clavicle, shoulder, elbow, and wrist joint, while the chain component only requires a start and end joint.

    • size: (Optional) Default size of the controls. All built in control shapes are use a size to make sense with cm. However control shapes are almost always customized.

    • rigParent: (Optional) Node the component should attach to in the rig. For example the legs should connect to the bottom of the spine.


    execution

    During execution, components are managed through the builder. The builder uses a list of components, as python objects, to step through component construction.


    The builder creates components in 5 steps.

    1. initialize_cmpt: create necessary containers (discussed below), parameters to control the component, and intractable guides

    2. build_cmpt: the main workhorse of the component, creates behaviors of the component

    3. connect_cmpt: connect the component to other input sources

    4. finalize_cmpt: publish controls and cleanup hierarchy

    5. optimize_cmpt: run performance optimizations that make the rig less friendly to a rigger. Includes locking/ moving attributes.

    These 5 steps are called from softly protected methods that call the functionality of other methods reimplemented in subclasses to control the behavior of new components. This creates clear rules for developers so they can primarily focus on the behavior of the component not repetitive tasks like publishing controllers, wrangling containers, or proper order of executing steps.


    def _build_cmpt(self):
        """ build the rig """
        self._load_meta_to_component()
    
        if not self.getStep() >= 2:
    
            # anything that manages or creates nodes should set the active container
            with rigamajig2.maya.container.ActiveContainer(self.container):
                self.initalHierachy()
                self.preRigSetup()
                self.rigSetup()
                self.postRigSetup()
                self.setupAnimAttrs()
            self.setStep(2)
        else:
            logger.debug('component {} already built.'.format(self.name))
    
    

    As an example, the _build_cmpt() method calls the initalHeirarchy, preRigSetup, rigSetup, postRigSetup, and setupAnimAttrs (each of these methods have specific guidelines for construction). However, it also checks the current build step to ensure we aren't trying to build a component that is already built and uses a with-statement to add any created nodes to the active container.




    Containers

    I've mentioned containers in several places throughout the post because they are an essential part of rigamajig2 components. Containers are Maya nodes that create relationships between the container and node agnostic of the DAG or DG. I derived my philosophy on containers from Raffaele Fragapane and his ideas on the cult of rig streams.


    Within rigamajig2 they serve several purposes:

    • interface for user input

    • store component metadata

    • keep a list of all nodes in a component


    Interface

    The container serves as the main interface for adjusting the component settings. It stores settings in attributes allowing the user to edit them directly within Maya (in the future this might be expanded into a UI). The settings here are re-collected from the Maya node and updated within the python object before each step.


    rig in initialize step.


    MEtaData

    The container settings double as metadata for the component. From the container node, rigamajig2 gathers all the settings to save and load the component with JSON.


    Aside from component settings rigamajig2 also uses a tagging system to manage other metadata. After doing performance tests between controller tags, message connections, and querying for nodes with an attribute I was surprised to find that querying attributes were the fastest solution to return lists of nodes.


    The tagging system adds a private attribute to the given nodes and can be returned by searching for nodes with that attribute:


    def tag(nodes, tag, type=None):
        """
        Tag the specified nodes with the proper type
        :param nodes: nodes to add the tag to
        :type nodes: str | list
        :param tag: tag to add
        :type tag: str
        :param type: type of tag
        :type type: str
        """
        nodes = common.toList(nodes)
        for node in nodes:
            if cmds.objExists(node):
                if not cmds.objExists("{}.__{}__".format(node, tag)):
                    cmds.addAttr(node, ln='__{}__'.format(tag), at='message')
                if type:
                    if not cmds.objExists("{}.__{}_{}__".format(node, type, tag)):
                        cmds.addAttr(node, ln='__{}_{}__'.format(type, tag), at='message')
    
    def getTagged(tag, namespace=None):
        """
        Get a list of all the objects with a tag in a scene.
        :param tag: tag to get
        :type tag: str
        :param namespace: Get controls found within a specific namespace
        :type namespace: str
        :return:
        """
        if not namespace:
            return [s.split(".")[0] for s in cmds.ls("*.__{}__".format(tag))]
        else:
            return [s.split(".")[0] for s in cmds.ls("{}:*.__{}__".format(namespace, tag))]
    
    


    rebuilding components

    Keeping a list of all related notes becomes important for rebuilding only specified components. This keeps RnD intuitive and iterative so the rigger can test out different setups without waiting for the entire rig to rebuild. The code below is used to delete a rig setup:


    def deleteSetup(self):
        """ delete the rig setup"""
        logger.info("deleting component {}".format(self.name))
        cmds.select(self.container, r=True)
        mel.eval("doDelete;")
    
        for input in self.input:
            rigamajig2.maya.attr.unlock(input, rigamajig2.maya.attr.TRANSFORMS)
            


    Maya Structure

    Components also have specific rules to how they should be structured within Maya.


    • Must be completely self contained. Aside from rigParent and space switches

    • Component must be scalable

    • Parameters exist on a separate node (for optimization purposes)

    • No joints bound to the skin should live in the component

    • Anything added to the component should live under the component root (ikfk, space switches, bend, etc... )



    Base Class

    All components are subclasses of the base component. Below is a slightly simplified version of the base class showing only the initialize and build steps. But the idea extends to later steps as well.


    class Base(object):
    
        def __init__(self, name, input=[], size=1, rigParent=str()):
            """
            :param name: name of the components
            :type name: str
            :param input: list of input joints.
            :type input: list
            :param size: default size of the controls:
            :param rigParent: node to parent to connect the component to in the heirarchy
            :type size: float
            """
            self.name = name
            self.cmpt_type = ".".join([self.__module__.split('cmpts.')[-1], self.__class__.__name__])
            self.input = input
            self.container = self.name + '_container'
            self.metaNode = None
    
            # element lists
            self.joints = list()
            self.controlers = list()
    
            # node metaData
            self.cmptData = OrderedDict()
            self.cmptData['name'] = self.name
            self.cmptData['type'] = self.cmpt_type
            self.cmptData['input'] = self.input
            # node cmpt settings
            self.cmptSettings = OrderedDict(size=size, rigParent=rigParent)
    
        def _intialize_cmpt(self):
            """
            setup all intialize functions for the component
            """
            if not self.getStep() >= 1:
                # fullDict = dict(self.metaData, **self.cmptSettings)
                self.setInitalData()
                self.createContainer()
    
                # Store to component node
                self.metaNode = rigamajig2.maya.meta.MetaNode(self.container)
                self.metaNode.setDataDict(data=self.cmptData, hide=True, lock=True)
                self.metaNode.setDataDict(data=self.cmptSettings, hide=True)
    
                # anything that manages or creates nodes should set the active container
                with rigamajig2.maya.container.ActiveContainer(self.container):
                    self.preScript()  # run any pre-build scripts
                    self.createBuildGuides()
                self.setStep(1)
    
        def _build_cmpt(self):
            """
            build the rig
            """
            self._load_meta_to_component()
    
            if not self.getStep() >= 2:
    
                # anything that manages or creates nodes should set the active container
                with rigamajig2.maya.container.ActiveContainer(self.container):
                    self.initalHierachy()
                    self.preRigSetup()
                    self.rigSetup()
                    self.postRigSetup()
                    self.setupAnimAttrs()
                self.setStep(2)
           
        # -----------------------------------------------------------------
        # functions
        # -----------------------------------------------------------------
        def createBuildGuides(self):
            """Add additional guides"""
            pass
    
        def setInitalData(self):
            """ Set inital component data. """
            pass
    
        def createContainer(self, data={}):
            """Create a Container for the component"""
            if not cmds.objExists(self.container):
                self.container = rigamajig2.maya.container.create(self.container)
                rigamajig2.maya.meta.tag(self.container, 'component')
    
        def preScript(self):
            pass
    
        def setupAnimAttrs(self):
            """Setup animation attributes. implement in subclass"""
            pass
    
        def initalHierachy(self):
            """Setup the inital Hirarchy. implement in subclass"""
            pass
    
        def preRigSetup(self):
            """Pre rig setup. implement in subclass"""
            pass
    
        def rigSetup(self):
            """Add the rig setup. implement in subclass"""
            pass
    
        def postRigSetup(self):
            """Add the post setup. implement in subclass"""
            pass
    
    


    • Rigamajig2
    • Mason Smigel

    Project structure

    Updated: Mar 22

    Setting up an organized project structure is essential for maintaining and collaborating on large-scale projects. RIGAMJIG2 is designed to be simple, flexible, and logical so it makes sense to not only me but other TDs.


    Maya environment

    I used Maya's Module system to keep track of data within Maya's environment variables. This allows me to use a single .mod file to add all the icons, scripts, and plugins into Maya's environment variables on startup.


    Using modules also makes it simple to install rigamajig2. Using the techniques from Christian Akesson's tutorial I wrote a simple drag and drop installer to create the .mod file and load in the scripts and plugins.


    """ Drag and Drop installer for rigamajig2"""
    import os
    import sys
    
    import pymel.util
    import maya.cmds as cmds
    
    def onMayaDroppedPythonFile(*args):
        # Get the location of the installer
        installer_path = __file__.replace('\\', '/')
    
        # from the installer path build other important paths.
        module_root = os.path.dirname(installer_path)
        python_path = os.path.join(module_root, 'scripts')
        plugin_path = os.path.join(module_root, 'plug-ins')
        lib_path = os.path.join(module_root, 'scripts', 'lib')
    
        # Check if the modules directory exists in the user preference directory (if it doesn't, create it)
        maya_moddir_path = '{}/modules'.format(pymel.util.getEnv('MAYA_APP_DIR'))
        if not os.path.exists(maya_moddir_path):
            os.makedirs(maya_moddir_path)
    
        # Define the module file path
        maya_mod_file = '{}/rigamajig2.mod'.format(maya_moddir_path)
    
        # Write our module file
        with open(maya_mod_file, 'w') as moduleFile:
    
            output = '+ rigamajig2 1.0 {}'.format(module_root)
            output += '\r\nPYTHONPATH += {}'.format(lib_path)
            # Add the path to plugin path on first use
            if plugin_path not in pymel.util.getEnv("MAYA_PLUG_IN_PATH"):
                pymel.util.putEnv("MAYA_PLUG_IN_PATH", [pymel.util.getEnv("MAYA_PLUG_IN_PATH"), plugin_path])
    
            moduleFile.write(output)
    
        # add the python path on first use
        if python_path not in sys.path:
            sys.path.append(python_path)
        if lib_path not in sys.path:
            sys.path.append(lib_path)

    Project structure

    rigamajig2/
    |-- archetypes/
    |   |-- base/
    |   |-- biped/
    |   |-- quadruped/
    |-- bin/
    |-- icons/
    |-- misc/
    |-- plug-ins/
    |-- scripts/
    |   |-- lib/
    |   |-- rigamajig2/
    |   |   |-- maya/
    |   |   |   |-- anim/
    |   |   |   |-- cmpts/
    |   |   |   |-- data/
    |   |   |   |-- rig/
    |   |   |   |-- test/
    |   |   |-- shared/
    |   |   |-- ui/
    |-- tests/
    |-- .gitignore
    |-- drag_into_maya.py
    |-- README.md

    archetypes: rig archetypes or presets. Each archetype contains data to act as a starting point when constructing a rig.


    bin: executable files. This should be added to a system path to run from the terminal.


    icons: icons used within UI or shelves. This is added to the XBMLANGPATH environment variable.


    misc: random assets or files. (ex. poly modeled axis marker file)


    plug-ins: plugins. This is added to the MAYA_PLUG_IN_PATH environment variable.


    scripts: python and Mel scripts. This is added to the PYTHONPATH and MAYA_SCRIPT_PATH environment variables.


    lib: external python libraries. For this project we only need NumPy.


    rigamajig2: python module for rigamajig2


    maya: scripts for Maya. At the root level, the scripts should be largely independent of other modules and are combined together later.

    (ex. matrix constrain, match transform, create nodes, cleanup mesh).


    anim: scripts for animation. (ex. mirror pose, ikfk matching)


    cmpts: components used in rig build. Components are python classes with specific methods and parameters used in the rig builder.

    (ex. arm, leg, cog, lookAt)


    data: scripts to manage saving and loading data from Maya to JSON.

    (ex. hierarchy data, skin data, joint data, blendshape data, curve data)


    rig: scripts for building rigs, but not whole components. (ex. controls, rig builder, ikfk setups, ik spline setup, space switching)


    test: contains Maya unit test runner


    shared: scripts not related to Maya or another DCC.


    ui: python scripts for UIs and custom PySide widgets


    tests: python files for unit testing. All files here are run when using the 'runmayaunittest' executable.


    .gitignore: git file to specify files not under VCS


    drag_into_maya.py: drag and drop installer


    README.md: markdown document containing basic documentation about rigamajig2

    • Rigamajig2
    1
    2
    M_logo_3x.png

    © 2023 by Mason Smigel.