Set inverse Child OF constraints via python

Hi there !
A few years ago I used to be using a little script found here in order to reset every “Stretch to” constraints of an armature, even on unselected/offlayered bones :

 import bpy
    for b in bpy.context.active_object.pose.bones:
        for c in b.constraints: 
            if c.type == "STRETCH_TO":
                c.rest_length = 0

It has been very useful, especially when you drastically edit the bones positions of an already finished rig (i.e. when you reuse the same rig for similar but different models).

I would like a very similar script, but with “set inverse” for CHILD_OF constraint.
But I barely know Blender’s API and every try haven’t got anything working nor going the right direction.

My last attempt looked like that:

import bpy

for b in bpy.context.active_object.pose.bones:
    for c in b.constraints: 
        if c.type == "CHILD_OF":
            context_py = bpy.context.copy()
            context_py["constraint"] = constraint
            bpy.ops.constraint.childof_set_inverse(context_py, constraint="Child Of")

And got this error:

Traceback (most recent call last):
  File "\Text", line 7, in <module>
NameError: name 'constraint' is not defined

If someone could help me, that would be kind.

I got this proposition from another guy:

import bpy

for b in bpy.context.active_object.pose.bones:
    for c in b.constraints: 
        if c.type == "CHILD_OF" and c.target is not None:
            c.inverse_matrix = c.target.matrix_world.inverted()

No errors returned, it executes propperly, but simply doesn’t affects the rig.

Thanks to this topic, now i have this :

import bpy

for b in bpy.context.active_object.pose.bones:
    for c in b.constraints: 
        if c.type == "CHILD_OF":
            context_py = bpy.context.copy()
            context_py["constraint"] = c
            bpy.ops.constraint.childof_set_inverse(context_py, constraint="Child Of", owner='BONE')

And it works, but only on the active bone. How can I make it work on the whole armature ? Even on offlayered bones ?

1 Like

The reason your first error (post#1) occurs is because you set context_py[‘constraint’] to “constraint” instead of “c”…because you never defined “constraint”.


The second version works when you have use_location and use_rotation enabled.
I don’t know what you’re targeting which is what the “c.target” is looking at.
I use empty objects that I created during the op.
If you are targeting “bones”, then you have to use the subtarget.

target = c.target
subtarget = c.subtarget
if target and target.pose is not None and subtarget:
    subtarget = c.target.pose.bones.get(subtarget, None)
if target is None:
    continue
if subtarget:
    # target.matrix_world is used for when the object itself is tranformed
    # note the inverted() function may be wrong, for this combination
        # but the point is not
    c.inverse_matrix = target.matrix_world() * subtarget.matrix.inverted()
else:
    c.inverse_matrix = target.matrix_world.inverted()

You probably haven’t ran into an error on your 3rd version but for the specific reason that all your constraints have the same name as the active one’s.
What this operator does is basically click the set_inverse button on the active bone/object

What you have to do is set the active object/bone BEFORE clicking set inverse:

    active = b.id_data.data.bones.active
    b.id_data.data.bones.active = src.bone
        # b.id_data = pose_bone's object
        # I prefer to not lock code to active anything, only selected
        # plus would transfer easier to multi-objects usage
    ...
        # inverse constraints actions
    ...
    b.id_data.data.bones.active = active  # don't change active bone


The set_inverse operator is slower but I don’t know the math to set it correctly for the second version.

By that, I mean if you enable location and disable rotation, the current inverse math value is incorrect, but the set_inverse() op sets it correctly.
(note: when the location is actually being used, which it isn’t when a bone is connected to another)

I really do want this to work on bones only, and in pose mode. Ideally on every bones of the armature, even on disabled layers (but if it’s really too complicated, working on all the selected bones is enough).

I tried to integrate your suggestions into my script. But I did not have convincing results. My poor python skills really struggle to follow :’)

One of the attempts:

import bpy

for b in bpy.context.active_object.pose.bones:
    for c in b.constraints: 
        if c.type == "CHILD_OF" and c.target is not None:
            target = c.target
            subtarget = c.subtarget
            if target and target.pose is not None and subtarget:
                subtarget = c.target.pose.bones.get(subtarget, None)
            if target is None:
                continue
            if subtarget:
                # target.matrix_world is used for when the object itself is tranformed
                # note the inverted() function may be wrong, for this combination
                    # but the point is not
                c.inverse_matrix = target.matrix_world() * subtarget.matrix.inverted()
            else:
                c.inverse_matrix = target.matrix_world.inverted()``` 

It gives that error:

Traceback (most recent call last):
  File "C:\Users\3D3\Desktop\child_of2.blend\set_inverse_chil_of.py", line 16, in <module>
TypeError: 'Matrix' object is not callable

A big thank to Konybravo for this piece of code:

import bpy

ob = bpy.context.active_object

# Take a copy of current layers 
org_layers = ob.data.layers[:]

# Show all layers
for i in range(len(org_layers)):
    ob.data.layers[i] = True

for b in ob.pose.bones:
    for c in b.constraints: 
        if c.type == "CHILD_OF":
            context_py = bpy.context.copy()
            context_py["constraint"] = c
            ob.data.bones.active = b.bone
            bpy.ops.constraint.childof_set_inverse(context_py, constraint="Child Of", owner='BONE')
            
# Reset back to orginal layer state    
for i in range(len(org_layers)):
    ob.data.layers[i] = org_layers[i]

Working straight away, even on bones hidden in a layer. Yay \o/

Your script worked for me on multiple objects then it didn’t…

It works on the active objects but the other selected ones aren’t affected.
The weird thing is the original object i copied the constraint from is always affected even if it’s not active.

At some point it works on all selected objects but when i tried to change the name of the constraint to affect second Child Of constraint, it again only worked on the active one.

I don’t know how to script :(but i might have to learn, i really need to Set inverse often.

Here’s another example, There you can see the last created bone never works unless it is active…
Notice also that as soon as theres a Child Of constraint with another name, the script stops working.

ChildOf_002.blend (823.4 KB)

Well one problem was that i was running 2.92.
Another was the script was set to constrain=“Child of”,. Changing it to constraint=c.name, worked.
That made it for me but it Set Inverse all the Child of constraints.

It would’ve been nice to only affect selected bones and to be able to specify the constraints affected.
I tried changing the name of the constraint but it doesnt really works.

Edit: It doesnt works in 2.91 either

Hi, I tried to see how to fix this. And I also removed the part where it showed all bone layers back and forth, and made it work on selection only.
I can’t find how to make it work when the constraint’s name isn’t “Child Of”, will check this later.

But can you try this version at least? I made it work on 2.91.

import bpy

ob = bpy.context.active_object
sb = bpy.context.selected_pose_bones

for b in sb:
    for c in b.constraints: 
        if c.type == "CHILD_OF":
#            if sb.constraints.name != "Child_Of":
#                continue
            context_py = bpy.context.copy()
            context_py["constraint"] = c
            ob.data.bones.active = b.bone
            bpy.ops.constraint.childof_set_inverse(context_py, constraint="Child Of", owner='BONE')
``

It works perfectly in 2.83!
But as soon as there is a Child Of constraint with a different name in the selection the script fails.

I think there is something broken in 2.91, or maybe in that version they changed how Python works idk…
I have inconsistent results in 2.91: