How to sort objects from a collection using python? So I'm trying to animate a rubiks cube via a python script using the scripting capabilities given with Blender.

My current cube looks like this:


It consists of 27 separate objects (cubelets) that are organized in a collection like this:


Initially the locations of the individual cubelets were not applied and so I could select all cubelets of a given side by sorting the objects of the collection by the corresponding location and reversing the resulting list if necessary:

def cubeletsBySide(cube, side):
    dimensionsBySide = {'U': 2, 'D': 2, 'F': 1, 'B': 1, 'R': 0, 'L': 0}
    revertBySide = {'U': True, 'D': False, 'F': True, 'B': False, 'R': False, 'L': True}
    dimension = dimensionsBySide[side]
    return sorted(
        list(cube.all_objects.values()),
        key=lambda c: c.location[dimension],
        reverse=revertBySide[side])[:9]


After some initial trouble with animating the cube I found a tutorial that helped me in animating a cube by hand. However it seems the right thing to do to apply the locations and set the origins of the individual cubelets to the same common point (the 3D cursor in this case).

While this works when animating the cube manually I can now no longer use the location to reliably sort cubelets. I pondered the idea of attaching custom properties to these objects, but that would imply that I also need to update this data whenever rotating some cubelets. To my mind there is a certain beauty in not needing to keep track of redundant data here and just selecting the desired objects at will. I also looked trough other object properties in the hope that I could find something to sort the cubelets by but had no luck with that.

Simply stated: I desire some sort of property or mechanism to compare objects in a given world location independently of their origins so that I can find the required cubelets independently of previously executed rotations.

For completion here's the code I currently use to rotate a side of the cube that makes use of the cubeletsBySide function:

def rotateSide(cube, side, angle):
    axisBySide = {'U': 'Z', 'D': 'Z', 'F': 'Y', 'B': 'Y', 'R': 'X', 'L': 'X'}
    override = bpy.context.copy()
    override['selected_objects'] = cubeletsBySide(cube, side)
    bpy.ops.transform.rotate(override, value=angle, orient_axis=axisBySide[side])


I'd be grateful for any pointers on this .
Alright so what worked for me is this:

For every cubelet the cubelet.data.vertices are all in the same grid cell so to speak. That means it's not important which one of them I take to compare against other cubelets. If it was I could still iterate them and find a min/max/average to my desire.

So using cubelet.data.vertices[0].co works for selecting initial, unrotated cubelets, but it breaks once a cubelet is rotated. This makes sense as the rotation is part of the cubelet.matrix_world rather than the individual vertices being updated.

I've added an exampleVector function encapsulate finding an arbitrary example vector for a cubelet for comparison:

def exampleVector(cubelet):
    return cubelet.matrix_world @ cubelet.data.vertices[0].co


With this my cubeletsBySide function now looks like this:

def cubeletsBySide(cube, side):
    dimensionsBySide = {'U': 2, 'D': 2, 'F': 1, 'B': 1, 'R': 0, 'L': 0}
    revertBySide = {'U': True, 'D': False,
                    'F': True, 'B': False, 'R': False, 'L': True}
    dimension = dimensionsBySide[side]
    return sorted(
        list(cube.all_objects.values()),
        key=lambda c: exampleVector(c)[dimension],
        reverse=revertBySide[side])[:9]


Together with the rest of my code I can now perform the superflip again, which I use to verify that both side selection and rotations are working correctly:

superflip = ["R", "L", "U", "U", "F", "U'", "D", "F", "F", "R", "R", "B", "B", "L", "U", "U", "F'", "B'", "U", "R", "R", "D", "F", "F", "U", "R", "R", "U"]

currentCube = newCube('cube', (0, 0, 0))
shuffle(currentCube, superflip)