Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Applies parent inverse matrix to the child's local matrix.
import bpy
import mathutils
class Converter:
def __init__(self, ob, logging=False):
self.ob = ob
self.action = ob.animation_data.action
self.rot_mode = ob.rotation_mode
self.logging = logging
if self.logging:
print("\n"*2 + "-"*80)
# apply parent inverse to rest pose
# BUG - assignment to matrix_basis does not work correctly for AXIS_ANGLE
self.parent_inv = ob.matrix_parent_inverse.copy()
if self.rot_mode == "AXIS_ANGLE":
mat = self.build_matrix(ob.location, ob.rotation_axis_angle, ob.scale)
mat = ob.matrix_parent_inverse * mat
loc, rot, scale = mat.decompose()
rot = rot.to_axis_angle()
rot = (rot[1], rot[0][0], rot[0][1], rot[0][2])
ob.location = loc
ob.rotation_axis_angle = rot
ob.scale = scale
ob.matrix_basis = ob.matrix_parent_inverse * ob.matrix_basis
self.vec3_fs = "{: 8.4f} " * 3
self.vec4_fs = "{: 8.4f} " * 4
# gather fcurve channels
self.location = [None] * 3
self.rotation = [None] * (3 if self.rot_mode not in ("QUATERNION", "AXIS_ANGLE") else 4)
self.scale = [None] * 3
self.frames = set()
prop_rot = ("rotation_quaternion" if self.rot_mode == "QUATERNION" else
"rotation_axis_angle" if self.rot_mode == "AXIS_ANGLE" else
prop_to_attr = (
("location", self.location),
( prop_rot, self.rotation),
( "scale", self.scale)
for fcu in self.action.fcurves:
for prop, attr in prop_to_attr:
if fcu.data_path == prop:
attr[fcu.array_index] = fcu
for prop, attr in prop_to_attr:
for index, item in enumerate(attr):
# use constant float if no fcurve is present or fcurve is empty
if item is None or len(item.keyframe_points) == 0:
attr[index] = getattr(ob, prop)[index]
# timeline
for kfp in item.keyframe_points:
def eval_prop(self, prop, frame):
return tuple(elem.evaluate(frame) if isinstance(elem, bpy.types.FCurve) else
elem for elem in prop)
def build_matrix(self, loc, rot, scale):
rot = (mathutils.Quaternion(rot) if self.rot_mode == "QUATERNION" else
mathutils.Quaternion(rot[1:4], rot[0]) if self.rot_mode == "AXIS_ANGLE" else
mathutils.Euler(rot, self.rot_mode))
mat_loc = mathutils.Matrix.Translation(loc)
mat_rot = rot.to_matrix().to_4x4()
mat_sca = mathutils.Matrix.Identity(4)
for i in range(3):
mat_sca[i][i] = scale[i]
return mat_loc * mat_rot * mat_sca
def insert_keyframe(self, prop, frame, value):
for index, elem in enumerate(prop):
if isinstance(elem, bpy.types.FCurve):
elem.keyframe_points.insert(frame, value[index], {'REPLACE', 'FAST'})
def convert(self):
for frame in self.frames:
loc = self.eval_prop(self.location, frame)
rot = self.eval_prop(self.rotation, frame)
scale = self.eval_prop(self.scale, frame)
if self.logging:
rot_fs = self.vec3_fs if self.rot_mode not in ("QUATERNION", "AXIS_ANGLE") else self.vec4_fs
print("frame %03d" % frame)
print("location before:" + self.vec3_fs.format(*loc))
print("rotation before:" + rot_fs.format(*rot))
print("scale before:" + self.vec3_fs.format(*scale))
mat_basis = self.build_matrix(loc, rot, scale)
mat_basis = self.parent_inv * mat_basis
loc, rot, scale = mat_basis.decompose()
if self.rot_mode == "AXIS_ANGLE":
rot = rot.to_axis_angle()
rot = (rot[1], rot[0][0], rot[0][1], rot[0][2])
elif not self.rot_mode == "QUATERNION":
rot = rot.to_euler(self.rot_mode)
if self.logging:
print("location after :" + self.vec3_fs.format(*loc))
print("rotation after :" + rot_fs.format(*rot))
print("scale after :" + self.vec3_fs.format(*scale))
self.insert_keyframe(self.location, frame, loc)
self.insert_keyframe(self.rotation, frame, rot)
self.insert_keyframe(self.scale, frame, scale)
for ob in bpy.context.scene.objects:
if ob.parent and ob.animation_data:
converter = Converter(ob, logging=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.