Home Website Youtube GitHub

How to use the stepDict to access ALL the things

Hey everyone,

@Justin_Pedersen and Jascha from Triggerfish helped me figure out how to use the stepDict. I only really knew how to get the model with
oModel = stepDict['mgearRun'].model

But they helped me figure out how to crack open the whole dictionary of components! So we’ve been digging around, and this is what we’ve found so far. There is a ton of information you can access in your build scripts. I’ve always just been calling objects by name in PyMEL. But this is potentially much more reliable and modular.

I don’t think there is much documentation on the stepDict, so I’ve written a Github Gist with lots of comments and examples. I’ll (probably) continue to update as I discover more, or if Miquel has any extra suggestions. But hopefully this helps some people!

You can also find these class object properties in this file:
mgear_3.X.X\scripts\mgear\shifter\component\__init__.py

5 Likes

@chrislesage Thanks for documenting this!

I will try to reply to some of the TODO notes questions:

print('setupWS', component.setupWS) # TODO: returns the "setup" group, but no idea what that is for.
image
is the setup transform. This is used to organize parts of the rig that are not moving. Like local facial or some part of leg_3jnt

print('size', component.size) # TODO: Not sure what this refers to
is the self size of the component. Mainly use to calculate the size of some controls

print('negate', component.negate) # TODO: returns True or False
is the inverted side. for example Left the negate is Right. Some components need to invert some axis and values to achieve the desired behavior

print('subGroups', component.subGroups) #TODO: No idea. Always empty in my tests.
is this. By default is empty
image

print('relatives', component.relatives) #TODO. eg. {'elbow': nt.Transform(u'arm_L0_div5_loc'), 'wrist': nt.Transform(u'arm_L0_div10_loc')}

Is the relation between the guide locators and the corresponding objects in the final rig

print('aliasRelatives', component.aliasRelatives) #TODO: arm is {'eff': 'hand'} or control_01 is {'root': 'ctl'}

is the name alias to be used in the ik space reference. So instead of using the guide locator name, will use a more descriptive name when the rig is build

print('jnt_pos', component.jnt_pos) # TODO A list of lists [[transform, 0], [transform, 1], [transform, 2], [transform, 'end']]
this is the list to configure each deformation joint at the end of the build. this is the methor that is using it later: is in components/init.py

print('transform2Lock', component.transform2Lock) #TODO: I guess a list of nodes that will get locked
to track the controls parent to be locked. This was a request from animators. o avoid keyframe by mistake internal rig parts. NOTE: We have set a custom pickwalk using control tag node so is not easy to pickwalk by mistake the parent of a control and keyframe it.

print('stepMethods', component.stepMethods) #TODO: Unsure yet.
is the list of steps that will be executed at build time. this are the default steps not the custom steps
image

I hope this helps :slight_smile:

Cheers,
Miquel

3 Likes

Yes, very cool. Thanks for the details!

Thanks for the shout out Chris! also huge thanks to Miquel for the full explanation :slight_smile:

Thanks for the shout-out @chrislesage and thank you @Miquel for pointing out some extra things.

@Miquel, is stepDict meant to be available only in POST steps? When I try to access it in a PRE step, it’s empty.
I was about to symmetrically duplicate limbs guides but got stuck.

@Lirinis think its only available as a post step. Its empty when printed out at build?

Best guess for mirroring all your left components would be something like this:

import pymel.core as pm
from mgear.shifter import guide_manager


myGuide = pm.PyNode("guide")

# Looping over all the children of the root
for child in pm.listRelatives(myGuide, allDescendents=True):
    # If it has the comp side attr, it must be a component root
    if pm.attributeQuery("comp_side", node=child, ex=True):
        # If its on the Left its safe to mirror
        if child.comp_side == "L":
            pm.select(child, replace=True)
            guide_manager.duplicate(True)

Runtime might suck a little though :sweat:

I can’t imagine this being safe though, since, you would only want to mirror the top-most items of a hierarchy. Otherwise you’ll end up duplicating the children multiple times.

arm -> elbow -> hand
       elbow -> hand
             -> hand

Another tip though. You can search ls() by attributes, instead of listRelatives and attributeQuery. But I don’t know how to get the object from an attribute, so I split the name in the 3rd line.

# Find all right side components to delete.
for each in pm.ls('*.comp_side'):
    if each.get() == 'R':
        print pm.PyNode(each.split('.')[0])

And then finally, I wrote a little test. I find all of the left components. And then test to see if it has any parents that are also left components. If it does, then ignore those.

import pymel.core as pm
from mgear.shifter import guide_manager

#1. Delete all the right sides first.
allRight = [
        pm.PyNode(each.split('.')[0])
        for each in pm.ls('*.comp_side')
        if each.get() == 'R'
        ]
pm.delete(allRight)

#2. Get all the left side components.
allLefts = [
        pm.PyNode(each.split('.')[0])
        for each in pm.ls('*.comp_side')
        if each.get() == 'L'
        ]

#3. Find only the left side components who are not children of other left side components ###
# Test if the left guide has another parent who is also left.
# If so, append it to childrenLeft, and do not duplicate those.
childrenLeft = []
for each in allLefts:
    for testParent in allLefts:
        if each.hasParent(testParent):
            childrenLeft.append(each)
allParentLefts = [each for each in allLefts if each not in childrenLeft]

#4. These are the left components you would want to mirror
for each in allParentLefts:
    pm.select(each)
    guide_manager.duplicate(True)
1 Like

Ahh Chris you’re 100% right! Can’t believe I let that bit slip my mind. Thanks you the tip bits on pm.ls, will definitely be making use of that one :slight_smile:

Thank you, Justin and Chris.
This is the code I ended up with.

import pymel.core as pm
from mgear.shifter import guide_manager

def cleanup(secondary_side='L'):
    secondary_side_components = [comp_side_attr.plugNode()
                                 for comp_side_attr in pm.ls('*.comp_side')
                                 if comp_side_attr.get() == secondary_side]
    pm.delete(secondary_side_components)


def get_components(primary_side='R'):
    primary_side_components = [comp_side_attr.plugNode()
                               for comp_side_attr in pm.ls('*.comp_side')
                               if comp_side_attr.get() == primary_side]
    #sort by hierarchy level
    return sorted(primary_side_components, key=lambda elem: elem.longName().count('|'))


def leave_only_top_level(all_components):
    top_components = dict()
    for component in all_components:
        top = True
        for parent in pm.listRelatives(component, allParents=True):
            if parent in top_components:
                top = False
                break
        if top:
            top_components.update({component: None})
    return top_components.keys()


def main():
    primary_side = 'R'
    secondary_side = 'L'
    cleanup(secondary_side)
    components_to_mirror = leave_only_top_level(get_components(primary_side))
    for component in components_to_mirror:
        pm.select(component)
        guide_manager.duplicate(True)       


main()
2 Likes

I tried that “allParents” flag in listRelatives, and it only ever gave me a single parent… Even the docs said, “Normally, this command only returns the parent corresponding to the first instance of the object”

So… I would bug test the heck out of it… But if it works, cool!

You’re right. Had to fix it.

def leave_only_top_level(all_components):
    top_components = dict()
    for component in all_components:
        top = True
        for parent in [pm.PyNode(name) for name in component.longName().split('|')[1:-1]]:
            if parent in top_components:
                top = False
                break
        if top:
            top_components.update({component: None})
    return top_components.keys()
1 Like