I want to influence the rotation of parents to their children

As you can see in the picture, I want to affect the rotation of the parent to the child by making the sphere Empty the parent and one side the child.
If I release the parent-child relationship to rotate the next face after rotating one face, the parent-child relationship is also released to the previous animation, and the animation switches to one in which only Empty the sphere is rotated.

Does this design not work? Also, is there a better way?

def rotation(frame,axis,position,angle):
    
    bpy.data.scenes['Scene'].frame_current = frame
    bpy.ops.object.select_all(action = 'DESELECT')
    
    for object in bpy.data.objects:
        if object.location[axis] == position:
            object.select_set(True)
            bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
    
    bpy.data.objects['Empty'].rotation_euler[axis] = numpy.radians(angle)
    bpy.ops.object.select_all(action = 'SELECT')
    bpy.ops.anim.keyframe_insert_menu(type='Rotation')
    
    #This one sentence eliminates the animation of the photo.
   # bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')



# 作成した関数を用いてそれぞれ変数に代入
rotation(40,0,1,90)

  1. Create 9 helpers. 3 for each axis.

  2. Create a Root empty at the center and parent the helpers to it.
    This makes it possible to move and rotate the whole cube, even while animating.

  3. Write some functions for dynamic parenting. This is probably the most time-consuming part. In the example below, only a single helper runs per animation, but as long as the helpers are parallel to eachother, it should work.

It’s easy to mess up the keyframes and cause the cubes to become misaligned.

rubiks.blend (143.1 KB)

The script:

import bpy
from math import pi, isclose

EPSILON = 0.1
halfpi = pi / 2
halfpi_inv = 1 / halfpi
store = {"last_parent": None}

children = set(bpy.data.collections["Children"].objects)
helpers = set(bpy.data.collections["Helpers"].objects)
root = bpy.data.objects["Root"]

def snap_axis(euler_rot):
    euler_rot[:] = [(round(e * halfpi_inv) % 4) * halfpi for e in euler_rot]

def snap_and_link_root(helper):
    snap_axis(helper.rotation_euler)
    for c in helper.children:
        mat = c.matrix_world.copy()
        c.matrix_parent_inverse.identity()
        c.parent = root
        c.matrix_world = mat
        snap_axis(c.rotation_euler)
        c.location[:] = [round(e) for e in c.location]

def get_axis_index(helper) -> int:
    x, z = helper.rotation_euler[:][0:3:2]
    if isclose(z, halfpi, rel_tol=EPSILON) and isclose(x, 0, rel_tol=EPSILON):
        return 0
    if isclose(x + z, 0, rel_tol=EPSILON):
        return 1
    return 2

def link_by_local_axis(helper):
    axis_index = get_axis_index(helper)
    co1 = (helper.matrix_basis).translation[axis_index]
    link_count = 0
    for o in children:
        co2 = (root.matrix_world.inverted() @ o.matrix_world).translation[axis_index]
        if isclose(co2, co1, abs_tol=EPSILON):
            mat = o.matrix_world.copy()
            o.parent = helper
            o.matrix_parent_inverse = helper.matrix_world.inverted()
            o.matrix_world = mat
            link_count += 1
    assert link_count >= 8, f"Not all children were linked ({link_count})"

def on_frame_change(scene):
    frame = scene.frame_current
    for h in helpers:
        data = h.animation_data
        if data is None:
            continue
        frame_range = range(*[int(r) for r in data.action.frame_range])
        if frame in frame_range:
            last = store["last_parent"]
            if last != h:
                store["last_parent"] = h
                if last is not None:
                    snap_and_link_root(last)
                link_by_local_axis(h)
            return

def main():
    enabled = False
    for func in bpy.app.handlers.frame_change_pre:
        if func.__name__ == 'on_frame_change':
            enabled = True
            bpy.app.handlers.frame_change_pre.remove(func)
            print("off")
            break
    if not enabled:
        print("on")
        bpy.app.handlers.frame_change_pre.append(on_frame_change)

if __name__ == "__main__":
    main()

Edit: Oh, remember to run the script if you load the blend file.

1 Like

I watched the video. It looks like the various objects are changing each helper into a parent, does this not cut or build a parent-child relationship?

Yes, the script performs dynamic parenting.

You can download the blend file I included in the post above and try it out yourself.

When a helper is rotated, the script finds and parents objects based on the helper’s location.

When another helper starts to rotate, the previous helper will snap its transformation and unparent its children. The children’s transformation is also snapped because rounding errors may accumulate over time which would cause misalignment.

When a helper releases its children, they become reparented to Root again.