0
$\begingroup$

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: The current rubiks cube in the scene

It consists of 27 separate objects (cubelets) that are organized in a collection like this: Outliner view of the rubiks cube

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

$\endgroup$
  • $\begingroup$ Hm - I had some further look and it seems that for an object o I might be able to use the o.data.vertices for this. The MeshVertex.co property seems to hold 3 coordinates, and since cubelets form quite the grid I could maybe just sort using the first of them not worrying about looping trough all or computing in a more complex fashion. $\endgroup$ – Jakob Runge May 21 at 21:04
  • $\begingroup$ Back in the day to solve a Rubik's cube would pull it apart and put it back together. AFAIC the gimbal joint in the middle is the key here. It's our three axes. Three 3x3 matrices describe the cube. $\endgroup$ – batFINGER May 21 at 22:09
0
$\begingroup$

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)

the currentCube after shuffling with the superflip

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.