1
$\begingroup$

I have just spent the better part of my day trying many different examples in an attempt to simply parent an object to a bone without the object moving (aka keep_transform). But no matter what I try it always offsets:

object.parent = armature
object.parent_bone = bone #object now in weird position
object.parent_type = 'BONE'

I also tried this as suggested here:

object.parent = armature
object.parent_bone = bone
bpy.context.active_pose_bone
bpy.ops.object.parent_set(type='BONE', keep_transform=True)

But get error "Error: No active bone"

I notice the object I am parenting to the bone in the armature is always moving to the bone's tail when I need it to be positioned at the head.

Is there anything I can do to programmatically affix the object to the bone's head? Any help her would be most encouraging, I just keep hitting walls with this.

One idea I had was to just offset the object by the head/tail delta, but because blender allows parenting-in-place I figured I should stick to convention.

Thanks.

$\endgroup$
2
$\begingroup$

When using bpy.ops.parent_set you want the type to be 'BONE_RELATIVE'.

Setting the active pose bone is a little un-intuitive, you need to set it in the armature object, not in the context.

import bpy

scn = bpy.context.scene
object = scn.objects['Cube']
armature = scn.objects['Armature']
arm_bones = bpy.data.armatures[armature.name].bones

object.select = True
armature.select = True
scn.objects.active = armature

arm_bones.active = arm_bones['Bone']

bpy.ops.object.parent_set(type='BONE_RELATIVE')
$\endgroup$
  • 1
    $\begingroup$ Recommend using arm_bones = armature.data.bones in case obj and obj.data names don't match. Going to be an edit-fest when 2.80 comes on-board, no scn.objects.active no obj.select $\endgroup$ – batFINGER Jul 13 '18 at 23:21
  • $\begingroup$ @sambler This worked very well! Thank you so very much!! $\endgroup$ – Logic1 Jul 14 '18 at 2:02
  • $\begingroup$ Also thank you @batFINGER for the suggestion, my armature object and data name were in fact different and this also worked very well!! $\endgroup$ – Logic1 Jul 14 '18 at 2:03
3
$\begingroup$

I have just tested batFINGER's code in Blender 2.79b, there is a bug in the formula.

When you multiply armature's world matrix with pose bone's object space matrix, you actually reached the bone head, not the bone tip, so you need to walk a bone's length to the bone tip, which is the origin of the pose bone.

Pose bone has a 'head' property and a 'tail' property, so we can calculate the bone vector, which equals to bone.tail minus bone.head.

I have verified the code in my project, it works correctly.

Here is the bug fixed version:

import bpy
context = bpy.context
from mathutils import Matrix
scene = context.scene
armature = scene.objects.get("Armature")
cube = scene.objects.get("Cube")

if cube and armature:
    cube.parent = armature
    bone = armature.pose.bones.get("Bone")
    cube.parent_bone = bone.name
    cube.parent_type = 'BONE'
    # keep current location
    cube.matrix_parent_inverse = (armature.matrix_world * Matrix.Translation(bone.tail - bone.head) * bone.matrix).inverted()

$\endgroup$
0
$\begingroup$

API method (no operators)

If you know the object and the bone, then often it's easier to parent directly using API methods, setting ob.parentto armature object, ob.parent_type to 'BONE' and ob.parent_bone to the name of the bone.

The global matrix of the bone can be calculatd using

bone_global_matrix = armature.matrix_world * bone.matrix

To have the cube stay in same location make the matrix parent inverse such that when multiplied by parent bone matrix world it remains the same, ie an identity matrix, the result being inverse of above..

To snap the cube to the bone,calc its matrix world such that when multiplied the inverse parent bone matrix you get identity.

import bpy
context = bpy.context
from mathutils import Matrix
scene = context.scene
armature = scene.objects.get("Armature")
cube = scene.objects.get("Cube")


if cube and armature:
    cube.parent = armature
    bone = armature.pose.bones.get("Bone")
    cube.parent_bone = bone.name
    cube.parent_type = 'BONE'
    # keep current location
    cube.matrix_parent_inverse = (armature.matrix_world * bone.matrix).inverted()
    # to snap to bone uncomment 2 lines below icomment line above
    #cube.matrix_world = (armature.matrix_world * bone.matrix)
    #cube.matrix_parent_inverse = Matrix()
$\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.