Moving Pose Bone to world coordinates

Hi all,
I have this problem where I’m trying to move a pose bone to the location of an object.

Here’s a quick script to start off

import bpy

# add first sphere
bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=(0, 2, 0))
sphere_1 = bpy.context.object
bpy.context.object.name = "Sphere_1"

# add second sphere
bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=(0, 6, 0))
sphere_2 = bpy.context.object
bpy.context.object.name = "Sphere_2"

# add armature
bpy.ops.object.armature_add(enter_editmode=False, location=(0, 2, 0))

# parent sphere 1 to armature
sphere_1.select_set(True)
bpy.ops.object.parent_set(type='BONE', keep_transform=True)

So now I want to move sphere 1 to the location of sphere 2 using the pose bone.

The problem, as you may have guessed, is that pose bones don’t follow world coordinates so I can’t just do something like posebone.location = sphere_2.location

I’ve found a couple things online about inverting the world matrix (or something) but I couldn’t get it working, perhaps because I’m not really understanding it. Amending somebody else’s solution I ended up with something along the lines of

armature = bpy.data.objects['Armature']
pose_bone = armature.pose.bones['Bone']

pose_bone.location = armature.matrix_world.inverted() @ pose_bone.bone.matrix_local.inverted() @ sphere_2.location

But this didn’t work.
Any advice is greatly appreciated!

Okay I managed to figure out why this wasn’t working

The reason the end part of the script wasn’t working, i.e.

armature = bpy.data.objects['Armature']
pose_bone = armature.pose.bones['Bone']

pose_bone.location = armature.matrix_world.inverted() @ pose_bone.bone.matrix_local.inverted() @ sphere_2.location

is that the armature wasn’t added at the world origin.
A workaround was found by either adding the armature at the origin and translating the edit bones (messy)

bpy.ops.object.armature_add(enter_editmode=False, location=(0, 0, 0))

bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.armature.select_all(action='SELECT')

bpy.ops.transform.translate(value=(0, 2, 0))
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

or by applying the location on the armature

bpy.ops.object.armature_add(enter_editmode=False, location=(0, 2, 0))
bpy.ops.object.transform_apply(location=True, rotation=False, scale=False)

I’m sure there’s a more elegant solution that avoids ops which would be great to know, but good to know why the original script wasn’t working.

Hi, Does this help?

Just run your first script like this:

import bpy

# add first sphere
bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=(0, 2, 0))
sphere_1 = bpy.context.object
bpy.context.object.name = "Sphere_1"

# add second sphere
bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=(0, 6, 0))
sphere_2 = bpy.context.object
bpy.context.object.name = "Sphere_2"

# add armature
bpy.ops.object.armature_add(enter_editmode=False, location=(0, 2, 0))

# parent sphere 1 to armature
sphere_1.select_set(True)
bpy.ops.object.parent_set(type='BONE', keep_transform=True)


The none 0 origin one.
for that I wrote this:

import bpy


armature = bpy.data.objects['Armature']
pose_bone = armature.pose.bones['Bone']
sphere_2 = bpy.data.objects['Sphere_2']
taxi = sphere_2.location - armature.location

pose_bone.location = pose_bone.bone.matrix_local.inverted() @ taxi

Why not parent the bone to the object first then unparent while keeping the transform?

Both are these are very good solutions, thank you!

@Weekend I don’t understand why that works so I guess I need to read up on matrices but it’s perfect

@kkar There is no reason to not do that, it just hadn’t occurred to me!

In any case, once I transferred this to my original project, on which the armature is already added to the scene, I just needed to make sure the transforms were properly applied manually for it to work and so managed to get away with my own solution while avoiding ugly code. The solutions suggested will definitely be committed to memory though.

Well I thought you want to keep the armature location as is, so I set the armature as ground zero for object b. you just need to subtract object a from the object b location matrix. (basically the distance of the two)

Cool, that should keep your code smaller as well. See the docs

https://docs.blender.org/api/2.80/bpy.ops.object.html#bpy.ops.object.parent_clear