Understanding Evaluation Order

90 views
Skip to first unread message

Eric Bates

unread,
Feb 14, 2021, 5:49:56 PM2/14/21
to Python Programming for Autodesk Maya
Hi all,

Really great group you guys have here! Some awesome stuff :)

I'm looking for some advice about a posing script I'm working on. Thanks for any ideas!

I have world space matrices that represent control positions on a rig, lots of them. My goal is to snap all of the rig controls to these points.

Unfortunately, since I don't know the evaluation order ahead of time, when the positions are aligned out of order, things get ugly!

This terrifying pose should look like a hand, the locators above show the "correct" points.

Screenshot 2021-02-14 234510.png
I was thinking that if there was a way to lookup the evaluation order of nodes, that took into account hierarchy, constraints, etc, it would solve the problem. But so far, I havent had much luck.

Of if there is another approach out there, Id love to hear.

Thanks for your thoughts everyone!

Eric

Marcus Ottosson

unread,
Feb 15, 2021, 2:42:49 AM2/15/21
to python_in...@googlegroups.com

If I understand you correctly:

  1. You’ve got a dictionary of worldMatrices associated to names of controls
  2. They are unordered, e.g. could be pinky and then shoulder.
  3. You’d like these assigned to their associated control

If so, then what you’re looking for isn’t evaluation order per-se, but hierarchical order. By assigning to shoulder first, you change where pinky is. So what you need to do is assign your worldMatrices in the order they are parented.

from maya import cmds
from maya.api import OpenMaya as om
import math  # degrees

# Make something to export and import, you'll already have these
shoulder, _ = cmds.polyCube()
shoulder = cmds.rename(shoulder, "shoulder")
cmds.move(1, 3, 6)
cmds.rotate(15)
pinky, _ = cmds.polyCube()
pinky = cmds.rename(pinky, "pinky")
cmds.parent(pinky, shoulder)
cmds.move(0, 5, 0)
cmds.rotate(45, 20)

# Next, serialise them. You'll have these too
matrices = {
    "pinky": cmds.getAttr("pinky.worldMatrix[0]"),
    "shoulder": cmds.getAttr("shoulder.worldMatrix[0]"),
}

# Now, anything below will restore the above to their original world matrix transformations
selected_root = cmds.ls(selection=True)[0]
hierarchy = cmds.listRelatives(selected_root, allDescendents=True)
hierarchy += [selected_root] # This one isn't included in the above

# allDescendents returns not only transforms, but shapes too. We don't need those
hierarchy = cmds.ls(hierarchy, type="transform")

# allDescendents returns the order reversed
hierarchy.reverse()

for control in hierarchy:
    matrix = matrices.get(control)

    # If there's something in the hierarchy not present in your list of matrices, that's fine
    if not matrix:
      continue

    # A world matrix can't be applied directly, because every control is relative their parent
    # So we'll convert world to local by multiplying it with the parent inverse matrix
    parent_inverse_matrix = cmds.getAttr(control + ".parentInverseMatrix[0]")

    # To multiply, we need to call on the API
    matrix = om.MMatrix(matrix)
    parent_inverse_matrix = om.MMatrix(parent_inverse_matrix)
    local_matrix = matrix * parent_inverse_matrix

    # Next we need translate/rotate/scale from this matrix
    local_matrix = om.MTransformationMatrix(local_matrix)
    translate = local_matrix.translation(om.MSpace.kTransform)
    rotate = local_matrix.rotation()
    scale = local_matrix.scale(om.MSpace.kTransform)

    # API rotations are in radians
    rotate = [math.degrees(r) for r in rotate]

    # Now we're got the local values ready to apply
    cmds.setAttr(control + ".translate", *translate)
    cmds.setAttr(control + ".rotate", *rotate)
    cmds.setAttr(control + ".scale", *scale)

applymatrices



--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/b8af2735-f81a-4991-af84-8b12448b06d8n%40googlegroups.com.

vince touache

unread,
Feb 15, 2021, 7:25:57 AM2/15/21
to Python Programming for Autodesk Maya
you can get rid of all the matrix part by setting directly the matrix in world space, using
cmds.xform(obj, matrix=your_mat, worldSpace=True)

Eric Bates

unread,
Feb 15, 2021, 9:31:20 AM2/15/21
to python_in...@googlegroups.com
Thanks so much Marcus for the really thoughtful and complete reply!

I managed to get it working through a combination of approaches.

I adapted your code Marcus to my script, and it nearly worked! There were a few controls that weren't hierarchically logical, meaning that some of the controls were structured through groups that were constrained. So there were some parallel hierarchy structures, that were actually functionally hierarchical (through constraints). So list relatives didn't give enough information here.

For the most part on this particular rig, "allDescendents" order was pretty close.
Screenshot 2021-02-15 140655.png

So I tried a brute force approach using the allDescendents order as a starting place and this worked. But I can't help but think there is a more elegant solution!
Screenshot 2021-02-15 135833.png



from maya import cmds
from maya.api import OpenMaya as om
from maya import OpenMaya as om1
import math  # degrees
import copy


# Make something to export and import, you'll already have these
shoulder, _ = cmds.polyCube()
shoulder = cmds.rename(shoulder, "shoulder")
cmds.move(1, 3, 6)
cmds.rotate(15)
pinky, _ = cmds.polyCube()
pinky = cmds.rename(pinky, "pinky")
cmds.parent(pinky, shoulder)
cmds.move(0, 5, 0)
cmds.rotate(45, 20)

# Next, serialise them. You'll have these too
matrices = {
    "pinky": cmds.getAttr("pinky.worldMatrix[0]"),
    "shoulder": cmds.getAttr("shoulder.worldMatrix[0]"),
}

# Now, anything below will restore the above to their original world matrix transformations
selection = pinky
full_path = cmds.listRelatives(selection, fullPath=True)
selected_root = filter(None, full_path[0].split('|'))[0] # get parent node from full path

hierarchy = cmds.listRelatives(selected_root, allDescendents=True)
hierarchy += [selected_root] # This one isn't included in the above

# allDescendents returns not only transforms, but shapes too. We don't need those
hierarchy = cmds.ls(hierarchy, type="transform")

# allDescendents returns the order reversed
hierarchy.reverse()




def is_object_matching_matrix(control, target_matrix_list):
    #
    current_matrix_list = cmds.xform(control, worldSpace=True, query=True, matrix=True)
    current_mMatrix = om1.MMatrix()
    om1.MScriptUtil.createMatrixFromList(current_matrix_list, current_mMatrix)

    # comparison matrix
    target_mMatrix = om1.MMatrix()
    om1.MScriptUtil.createMatrixFromList(target_matrix_list, target_mMatrix)

    # get diff and decompose
    diff_matrix = target_mMatrix * current_mMatrix.inverse()
    diff_MTMatrix = om1.MTransformationMatrix(diff_matrix)
    translate = diff_MTMatrix.translation(om.MSpace.kTransform)
    rotate = diff_MTMatrix.rotation()
    #scale = diff_MTMatrix.scale(om.MSpace.kTransform)


    # API rotations are in radians
    rotate = [math.degrees(r) for r in
 rotate]

    # sum errors
    translation_error = sum(translate)
    rotation_error = sum(rotate)

    # check error
    threshold_pos = .0001
    threshold_rot = .001
    if translation_error < threshold_pos or rotation_error < rotation_error:
        return True
    else:
        return False

# brute force
max_iterations = len(matrices)
previous_match_count = max_iterations
iteration_count = 0
hierarchy_copy = copy.deepcopy(hierarchy)

for i in range(max_iterations):
    # track iterations
    iteration_count += 1
    
    # initial matching
    for control in hierarchy_copy:
        matrix = matrices.get(control)

        
# If there's something in the hierarchy not present in your list of matrices, that's fine
        if not
 matrix:
            # do come cleanup along the way
            #hierarchy_exec.remove(control)
            continue

        # apply matrix directly
        print('applying matrix to ',control )
        cmds.xform(control, worldSpace=True, matrix=matrix)

    # check that the matching was successful
    for control in list(hierarchy_copy): # use a copy of list

        matrix = matrices.get(control)

        # If there's something in the hierarchy not present in your list of matrices, that's fine
        if not matrix:
          continue

        # check that the match is correct
        is_matching = is_object_matching_matrix(control, matrix)
        if is_matching:
            # remove if all good
            hierarchy_copy.remove(control)

    # if not getting any better, hit the TV with a broom to see if it works
    if not len(hierarchy) < previous_match_count:
        hierarchy_copy.reverse()

    # all matches correct, done
    if not len(hierarchy_copy):
        break

    # keep track of matching improvements per iteration
    previous_match_count = len(hierarchy_copy)

print('Matching Positions in {0} iterations'.format(iteration_count))

You received this message because you are subscribed to a topic in the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/python_inside_maya/7gqUkM2-IT8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/75933eb6-6bc5-4511-a09d-fe0e84fbfac0n%40googlegroups.com.

Eric Bates

unread,
Feb 15, 2021, 9:33:34 AM2/15/21
to Python Programming for Autodesk Maya
My code formatting didnt quite work, @marcus, you will have to share your secret :)

Marcus Ottosson

unread,
Feb 15, 2021, 9:37:03 AM2/15/21
to python_in...@googlegroups.com

For more elegance, have you considered storing the local matrix, instead of the world? Or better yet, store the translate/rotate values? Is it not safe to assume that wherever you export these from will have a similar - if not identical - hierarchy?

@marcus, you will have to share your secret :)

Hehehehe, sure. It’s called MarkdownHere


Eric Bates

unread,
Feb 15, 2021, 10:13:41 AM2/15/21
to python_in...@googlegroups.com
Cool, I'll definitely check it out.

The thing about this that makes it tricky, is that all of the positions are generated. This tool lets you mirror a pose around an arbitrary plane, so the driving factors are the world space points. So sadly there is no ground truth to begin with. On top of that, the user can choose to work with the full set of controls or any subset, so it's pretty open ended.

If I look for similar functionality in Maya there is:
Constraints
  • These are able to handle correctly aligning any number of controls irrespective of hierarchy. Although it's a lot of overhead to generate potentially hundreds of constraints.

Move Tool
  • I did notice that when using the move tool set to "World" everything moves correctly with no double transformations. So somehow it works internally here.

So maybe it's possible to create a node or tool context that could do this as well. I'm not really sure.

I was also starting to investigate the MItDependencyGraph (https://download.autodesk.com/us/maya/2011help/API/class_m_it_dependency_graph.html#e463c04e6c151635e509ad6152c1b85e) but didn't have much success. 

Well Im happy to have at least found a solution that is working, will call this done for now, unless anyone has another idea.

Thanks again for your help Marcus and Vince.

Marcus Ottosson

unread,
Feb 15, 2021, 10:41:15 AM2/15/21
to python_in...@googlegroups.com

Well Im happy to have at least found a solution that is working, will call this done for now, unless anyone has another idea.

The last idea you had of iterating over it multiple times seems fine to me. So long as it doesn’t bother you performance wise, it is very robust. I would probably do something similar. Down the road, you might be able to sort the initial list with the help of cmds.cycleCheck or other evaluation-related tools. Maya is able to generate this dependency hierarchy up-front for you via the Evaluation Toolkit. It is likely you could leverage that to get a near perfect sort here.


Reply all
Reply to author
Forward
0 new messages