3
$\begingroup$

I am trying to move around objects by manipulating their transformation matrices with Python. If the objects are in a parent/child hierarchy you have to step down this hierarchy using the object's matrix_parent_inverse element. My code works fine with one exception: In case an object is a child of an armature's bone the child has a matrix_parent_inverse I do not understand.

Say, we have a cube as child of a cone as child of a cylinder, i. e. the hierarchy cube -> cone -> cylinder. bpy.data.objects["Cube"].matrix_parent_inverse gives me the inverse matrix of the cone at the time of parenting. Fine.

Now we have the same situation (at least in my opinion) when the cube is the child of a bone. The hierarchy is in this case cube -> bone -> armature. bpy.data.objects["Cube"].matrix_parent_inverse should give the same result as in the first situation but it does not. So my question is: What is the meaning of matrix_parent_inverse in this case or how can I get the correct one?

Sample code showing the different results (Blender 2.71):

import bpy

#------------------------------------------------------
# Make a parent hierarchy
# cube -> cone -> cylinder
# "->" means "is child of"
#------------------------------------------------------

# Delete the scene:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# Create a cylinder at location (2, 3, 4):
bpy.ops.mesh.primitive_cylinder_add(location=(2, 3, 4))

# Create a cone and make it the cylinder's child at the same position:
bpy.ops.mesh.primitive_cone_add(location=(2, 3, 4))
bpy.context.scene.objects.active = bpy.data.objects["Cylinder"]
bpy.ops.object.parent_set(type='OBJECT')

# Create a cube at (0, 0, 0) and make it the cone's child:
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
bpy.context.scene.objects.active = bpy.data.objects["Cone"]
bpy.ops.object.parent_set(type='OBJECT')

# Print the cube's parent inverse matrix:
print(bpy.data.objects["Cube"].matrix_parent_inverse)


#------------------------------------------------------
# Make a parent hierarchy
# cube -> bone -> armature
# "->" means "is child of"
#------------------------------------------------------

# Delete the scene:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# Create an armature at (2, 3, 4), a bone is created automatically as child:
bpy.ops.object.armature_add(location=(2, 3, 4))

# Create a cube at (0, 0, 0) and make it the bone's child:
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
bpy.context.scene.objects.active = bpy.data.objects["Armature"]
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.object.parent_set(type='BONE')

# Print the cube's parent inverse matrix:
print(bpy.data.objects["Cube"].matrix_parent_inverse)


# => output:
#<Matrix 4x4 (1.0000,  0.0000, 0.0000, -2.0000)
#            (0.0000,  1.0000, 0.0000, -3.0000)
#            (0.0000,  0.0000, 1.0000, -4.0000)
#            (0.0000,  0.0000, 0.0000,  1.0000)>
#
#<Matrix 4x4 (1.0000,  0.0000, 0.0000, -2.0000)
#            (0.0000,  0.0000, 1.0000, -5.0000)
#            (0.0000, -1.0000, 0.0000,  3.0000)
#            (0.0000,  0.0000, 0.0000,  1.0000)>
#
# Both matrices should be the same but aren't.
$\endgroup$
6
  • $\begingroup$ Parenting to bones uses bone’s tip, not its root. Think this explains the difference? $\endgroup$
    – mont29
    Commented Mar 6, 2015 at 13:50
  • $\begingroup$ No, I don't think so. This could have some effect in the matrix' last column (the translation part) only but not in the second and third column; there are some new rotation elements now ... $\endgroup$
    – frisee
    Commented Mar 6, 2015 at 16:14
  • $\begingroup$ No, default rotation of bones is not the same as the one of mere object (just add a new object, a new armature, and compare new object's and new bone's matrices…). $\endgroup$
    – mont29
    Commented Mar 6, 2015 at 20:18
  • $\begingroup$ Hm, I cannot confirm that: If I create an armature with bpy.ops.object.armature_add(location=(2, 3, 4)) as in my sample code above, switch into pose mode and print bpy.context.active_pose_bone.matrix_basis I get the identity matrix. There is no rotation component in the bone. The translation is in the armature's matrix of course as one should expect. $\endgroup$
    – frisee
    Commented Mar 9, 2015 at 14:46
  • $\begingroup$ Yes, but this is based on 'bone space' as defined by rest pose (aka edit mode), do this in Object space and you’ll see: C.object.data.bones[0].matrix $\endgroup$
    – mont29
    Commented Mar 9, 2015 at 18:33

1 Answer 1

2
$\begingroup$

Thanks to mont29's hint I have found a solution: A bone seems to have two representations:

  • bpy.context.active_pose_bone.matrix_basis
  • <armature>.data.bones[x].matrix_local

Dependant on the context (pose mode/object mode) one sees the one or the other. There is a rotation by pi/2 around the x-axis and an offset of 1 in the z-axis between them. Here is the sample code from above again, the correction to make the second matrix equal to the first is added at the end.

import bpy
import mathutils
import math

#------------------------------------------------------
# Make a parent hierarchy
# cube -> cone -> cylinder
# "->" means "is child of"
#------------------------------------------------------

# Delete the scene:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# Create a cylinder at location (2, 3, 4):
bpy.ops.mesh.primitive_cylinder_add(location=(2, 3, 4))

# Create a cone and make it the cylinder's child at the same position:
bpy.ops.mesh.primitive_cone_add(location=(2, 3, 4))
bpy.context.scene.objects.active = bpy.data.objects["Cylinder"]
bpy.ops.object.parent_set(type='OBJECT')

# Create a cube at (0, 0, 0) and make it the cone's child:
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
bpy.context.scene.objects.active = bpy.data.objects["Cone"]
bpy.ops.object.parent_set(type='OBJECT')

# Print the cube's parent inverse matrix:
cmpi=bpy.data.objects["Cube"].matrix_parent_inverse
print("cmpi:")
print(cmpi)

#------------------------------------------------------
# Make a parent hierarchy
# cube -> bone -> armature
# "->" means "is child of"
#------------------------------------------------------

# Delete the scene:
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

# Create an armature at (2, 3, 4), a bone is created automatically as child:
bpy.ops.object.armature_add(location=(2, 3, 4))

# Create a cube at (0, 0, 0) and make it the bone's child:
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
bpy.context.scene.objects.active = bpy.data.objects["Armature"]
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.object.parent_set(type='BONE')

# Print the cube's parent inverse matrix:
mpi=bpy.data.objects["Cube"].matrix_parent_inverse
print("mpi:")
print(mpi)

#------------------------------------------------------
# The correction to make the second matrix equal to
# the first
#------------------------------------------------------

ri=mathutils.Matrix.Rotation(math.pi/2, 4, 'X').inverted()
ti=mathutils.Matrix.Translation(mathutils.Vector((0,0,1))).inverted()
mpii=mpi.inverted()
mpiiriti=mpii*ri*ti
mpiiritii=mpiiriti.inverted()
print("mpiiritii:")
print(mpiiritii)

# Output (some formatting added by hand):
#
# cmpi:
# <Matrix 4x4 ( 1.0000,  0.0000,  0.0000, -2.0000)
#             ( 0.0000,  1.0000,  0.0000, -3.0000)
#             ( 0.0000,  0.0000,  1.0000, -4.0000)
#             ( 0.0000,  0.0000,  0.0000,  1.0000)>
# mpi:
# <Matrix 4x4 ( 1.0000,  0.0000,  0.0000, -2.0000)
#             ( 0.0000,  0.0000,  1.0000, -5.0000)
#             ( 0.0000, -1.0000,  0.0000,  3.0000)
#             ( 0.0000,  0.0000,  0.0000,  1.0000)>
# mpiiritii:
# <Matrix 4x4 ( 1.0000, -0.0000,  0.0000, -2.0000)
#             (-0.0000,  1.0000,  0.0000, -3.0000)
#             ( 0.0000, -0.0000,  1.0000, -4.0000)
#             (-0.0000,  0.0000, -0.0000,  1.0000)>
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .