How to Add a Constraint to a Bone

Hi,

I’m trying to add the COPY_TRANSFORMS constraint to specific bones using python because doing the exact same process manually for each and every armature is very time consuming.

I learned from a tutorial that the “info” panel includes all the python codes that you need to repeat the exact same process. and I also noticed that some codes cannot be registered properly if you, for example, used the eyedropper in target and sub-target fields. so I tried to pick the bone names manually and the code is almost ready:

import bpy

bpy.ops.pose.constraint_add(type='COPY_TRANSFORMS')

bpy.context.object.pose.bones["foot_ik_r"].constraints["Copy Transforms"].target = bpy.data.objects["armature"]

bpy.context.object.pose.bones["foot_ik_r"].constraints["Copy Transforms"].subtarget = "foot_r"

bpy.ops.pose.constraint_add(type='COPY_TRANSFORMS')

bpy.context.object.pose.bones["foot_ik_l"].constraints["Copy Transforms"].target = bpy.data.objects["armature"]

bpy.context.object.pose.bones["foot_ik_l"].constraints["Copy Transforms"].subtarget = "foot_l"

except I get the following error:
KeyError: 'bpy_prop_collection[key]: key "Copy Transforms" not found'

which I guess it’s because the name of each bone has not been specified for:
bpy.ops.pose.constraint_add(type='COPY_TRANSFORMS')

I’m saying it because first of all it only applies to the active bone and second when I add the copy transforms constraint to all bones manually, then the code works fine.

I don’t have coding skills but I can read them to some extend and I guess it’s the same condition as using the eyedropper and there has to be a way to apply a constraint to a bone so that the bone name can also be registered with it.

The only option that I’m aware of adding a constraint is either through the drop-down menu or using the Shift + Ctrl + C shortcut in which case results in a slightly different string (but still no bone name included):
bpy.ops.pose.constraint_add_with_targets(type='COPY_TRANSFORMS')

Any idea?

I think it’s a selection issue.

Operators will always work on what is currently selected, and even though the context.object is the armature, once you get into Pose Mode you need to make sure that you have the right bone selected.

Every time you run bpy.ops.pose.constraint_add it adds a constraint to the currently active bone.

The first time you run it, it will add one called ‘Copy Transforms’, the second time it will add one called ‘Copy Transforms.001’ to the same bone as the first, and so on with ‘Copy Transforms.002’

Before you call constraint_add, first deselect all pose bones, then select just the one you want.

(Which actually turns out to be a more confusing task than I thought).

Here is a script which will do what you’re looking for:

import bpy

bones = bpy.context.object.pose.bones

for bone in bones:
    bpy.context.object.data.bones.active = bone.bone
    bone.bone.select = True
    bpy.ops.pose.constraint_add(type='COPY_TRANSFORMS')

The secret sauce here is that active and selected are actually two different things in Blender, and to assign a constraint the object needs to be active not just selected, and since you’re dealing with bpy.context.object.pose.bones you probably didn’t realize that you can’t access the active attribute through pose bones, you have to go through object.context.data.bones

I love Blender, but this is obtuse and I don’t blame you for having issues with it.

I found the data.bones vs pose.bones tip in this post: https://blender.stackexchange.com/questions/134250/set-active-bone-in-pose-mode-from-python-script if you want any more details.

You should avoid using ops commands whenever possible (As it makes the code slower and it is more unpredictable to work with). There is a solution that doesn’t require it.

# Select the bone you want to work with
bone = bpy.context.object.pose.bones.get("foot_ik_r")

# Creates the constraint itself
constraint = bone.constraints.add("COPY_TRANSFORMS")

# Modifies its data right away
constraint.target = bpy.data.object.get("armature")
constraint.subtarget = "foot_r"
import bpy

bones = bpy.context.object.pose.bones

for bone in bones:
    bpy.context.object.data.bones.active = bone.bone
    bone.bone.select = True
    bpy.ops.pose.constraint_add(type='COPY_TRANSFORMS')

Almost there!
It added the Copy Transforms constraint to all bones (ignoring the selected ones).

# Select the bone you want to work with
bone = bpy.context.object.pose.bones.get("foot_ik_r")

# Creates the constraint itself
constraint = bone.constraints.add("COPY_TRANSFORMS")

# Modifies its data right away
constraint.target = bpy.data.object.get("armature")
constraint.subtarget = "foot_r"

Thanks for the advice. of course the the more optimized the code the better and I think I should start learning Python.

Anyways, I got the following error for your code:
AttributeError: bpy_prop_collection: attribute "add" not found

I also need to emphasize that I need the Copy Transform constraint* to be added to all selected bones and your code seems to be applying it only to one (active) bone.

I was going to use the combination of both codes but unfortunately none of them worked (properly).

Whoops! I am sorry, i did not double check the proper command to create a new constraint. The command is new instead of add, apologies.

As for wanting to add the constraints for all selected bones, you can use a for loop, however, actually assigning specific bone targets may require a bit more work if the name of the target bones aren’t similar.

In this case, assuming the only difference between them is having “ik” or not, i used the replace command to remove that part in the bone name so the target of “foot_ik_r” would be “foot_r”.

import bpy

# Saves all selected bones in a variable so it can be used in a loop later
selected_bones = bpy.context.selected_pose_bones

# For each bone in the variable
for bone in selected_bones:
    # Creates the constraint itself
    constraint = bone.constraints.new("COPY_TRANSFORMS")

    # Modifies its data right away
    constraint.target = bpy.data.objects.get("armature")

    # Removes the "ik_" part of the current bone name
    # in a new variable. Keep in mind this doesn't actually
    # change the name of the current bone
    target_name = bone.name.replace("ik_", "")

    constraint.subtarget = target_name

Let me know if it works!

1 Like

Thank you so much Haggets,

For a couple of minutes I had no idea what was happening and now I know exactly what you did. you removed the IK in between and used it as the subtarget. it was a great solution but since I have so many armatures with completely different bone names (that I’m not allowed to change) I was a little bit disappointed at first.

But all of a sudden I was like. oh wait a minute. you managed to solve the mystery! :

selected_bones = bpy.context.selected_pose_bones

# For each bone in the variable
for bone in selected_bones:
    # Creates the constraint itself
    constraint = bone.constraints.new("COPY_TRANSFORMS")

Now that the Copy Transform constraint can be easily added to selected bones I simply replaced the rest with blender console codes and here is the final result that worked perfectly:

# Saves all selected bones in a variable so it can be used in a loop later
selected_bones = bpy.context.selected_pose_bones

# For each bone in the variable
for bone in selected_bones:
    # Creates the constraint itself
    constraint = bone.constraints.new("COPY_TRANSFORMS")

bpy.context.object.pose.bones["foot_ik_r"].constraints["Copy Transforms"].target = bpy.data.objects["armature"]

bpy.context.object.pose.bones["foot_ik_r"].constraints["Copy Transforms"].subtarget = "foot_r"

bpy.context.object.pose.bones["foot_ik_l"].constraints["Copy Transforms"].target = bpy.data.objects["armature"]

bpy.context.object.pose.bones["foot_ik_l"].constraints["Copy Transforms"].subtarget = "foot_l"

Now I can replace the names and save them as new scripts to use them for different armatures with different names.

I also tried to replace the console codes with the second part of your first code (in your first post) …

# Modifies its data right away
constraint.target = bpy.data.object.get("armature")
constraint.subtarget = "foot_r"

… to make the codes shorter for a better optimization (as you said earlier) and of course it wouldn’t work because as far as I can see “foot_ik_l” and “foot_ik_r” are not specified for the target and subtarget.

If there is a way to make them a little bit shorter, it would be great to mark your optimized version as the final solution. otherwise I’m already more than happy with the results.

Thanks again Haggets. I really appreciate it.

1 Like