# Understanding Evaluation Order

52 views

### Eric Bates

Feb 14, 2021, 5:49:56 PMFeb 14
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.

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.

Eric

### Marcus Ottosson

Feb 15, 2021, 2:42:49 AMFeb 15

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)
``````

--
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.

### vince touache

Feb 15, 2021, 7:25:57 AMFeb 15
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

Feb 15, 2021, 9:31:20 AMFeb 15
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.

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!

```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.

### Eric Bates

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

### Marcus Ottosson

Feb 15, 2021, 9:37:03 AMFeb 15

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

Feb 15, 2021, 10:13:41 AMFeb 15
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.

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.