Sorting objects in a list by heirarchy

Hello! I have a list of joint controls that I want to sort by hierarchy.

My current list prints as:

[u’wrist’, u’shoulder’, u’forearm’, u’elbow’]

I would like to sort my list by hierarchy…

[u’shoulder’, u’elbow’, u’forearm’, u’wrist’]

Is there a way to do this?

Hi @hanaharu,

Easiest way - you could get the hierarchy then reference into it:

hierarchy = cmds.listRelatives(root, allDescendents=True, type="joint")

filtered_list = filter(lambda n: n in my_list, hierarchy)      

If not you’ll need to re-arrange the list by parent/child relatives.

1 Like

In most cases the following should pull you through:

def get_node_depth(node):
    """
    Returns scene depth of a node.
    """
    depth = 0

    if node in (None, ''):
        return depth

    while True:
        depth += 1
        node = cmds.listRelatives(node, parent=True)
        if node is None:
            break

    return depth

sorted_nodes = sorted(nodes, key=get_node_depth)
2 Likes

The classic cheat is

 hierarchy = sorted(cmds.ls(my_list_of_things, long=True))

because the long flag will prepend the whole path to each item. Then sorting the list alphabetically gives you the proper hierarchy. It’s good practice to use long names by default anyway, since it gets you out of trouble if you ever have leaf nodes with the same name.

6 Likes

If you need to do arbitrary sorting, i.e. you’re dictating the order yourself -1 being no parent:

import operator

names = ["chest", "hips", "spine", "rightArm", "leftShoulder", "rightShoulder", "leftArm", "pelvis"]
indices = [2, -1, 1, 5, 0, 0, 4, -1]  # The order that names are parented in -1 being no parent.

counts = []

for i in indices:

  idx = i
  count = 0

  while idx != -1:

    count += 1
    idx = indices[idx]

  counts.append(count)


count_pairs = dict(zip(names, counts))

sorted_count_pairs = sorted(
  count_pairs.items(), key=operator.itemgetter(1))

order = []

for item in sorted_count_pairs:

  if item[1] != 0:

    parent = names[indices[names.index(item[0])]]
    order.insert(order.index(parent)+1, item[0])

  else:
    order.append(item[0])

print order

# >> ['pelvis', 'hips', 'spine', 'chest', 'rightShoulder', 'rightArm', 'leftShoulder', 'leftArm']

Thanks to @Klaudikus for the while loop idea. :+1:

2 Likes

You can also sort the list by counting how many ‘|’ there are in your strings, as this is the hierarchy sign in long names. Pretty usefull if your list already exists or if you want to reverse the order:

list0 = [u'|group6',
 u'|group6|group4',
 u'|group6|group4|group3|group2|group1|null1',
 u'|group6|group4|group3',
 u'|group6|group4|group3|group2',
 u'|group6|group4|group3|group2|group1',
 u'|group6|group5',
 u'|group6|group5|null2',
 u'|group6|null3']

list0 = sorted(list0, key=lambda x: x.count('|'), reverse=1)

result = [u'|group6|group4|group3|group2|group1|null1',
 u'|group6|group4|group3|group2|group1',
 u'|group6|group4|group3|group2',
 u'|group6|group4|group3',
 u'|group6|group5|null2',
 u'|group6|group4',
 u'|group6|group5',
 u'|group6|null3',
 u'|group6']
1 Like

You can write this as

  [v for k, v in sorted(zip(indices, names))]

However I like @BenWall’s idea of using long names and sorting on pipes which takes the possiblity of index errors out of the picture.

1 Like