5

I have an armature where Automatic IK is enabled. I want to move my bone in pose mode using python instead of grabbing it using mouse. I tried the below but it didn't work as it says tail location is a read only. How I can move the bone's tail to a certain location in the scene without stretching the bone and in pose mode not edit mode?

import bpy

ob = bpy.data.objects['Armature']

ob.pose.bones['Bone.002'].tail = bpy.context.scene.cursor_location

I've also tried selecting the bone in Pose Mode then using ops.transform.translate but it didn't move the bone to the cursor:

bpy.ops.transform.translate(value=bpy.context.scene.cursor_location, constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)

enter image description here

As shown below I can do this manually by selecting the bone and grabbing it to the correct position while auto-ik is enabled without resizing or stretching the bones:

enter image description here

batFINGER
  • 84,216
  • 10
  • 108
  • 233
Tak
  • 6,293
  • 7
  • 43
  • 85
  • As stated in Automatic IK it calculates the Inverse Kinematic for you. AFAIK there no link between scripting and translation in auto-IK mode. ( also suggest making your sample images landscape ) – batFINGER May 10 '17 at 12:02
  • @batFINGER thanks for your comment. I just mentioned auto-IK so that the bone doesn't stretch or get resized. The main question is how I can use python move the tail of the bone to a certain point in pose mode, or at least as close as possible if the point is far? – Tak May 10 '17 at 12:09
  • Rotate it to align with vector made by subtracting base from cursor (in same space ofcourse) . Here is how to do so for object. https://blender.stackexchange.com/questions/19533/align-object-to-vector-using-python – batFINGER May 10 '17 at 12:17
  • @batFINGER is it possible to post an answer whenever you have time? As I'm not sure how this can be done in pose mode with bones. – Tak May 10 '17 at 12:22
  • The solution is not trivial for this one. Either you can calculate your own IK chain and rotate bones to follow it, or you could add some helper empty at cursor location and temporary IK constraint for the last bone of chain (target: empty), followed by bpy.ops.pose.visual_transform_apply(), then delete empty and constraint. – Jaroslav Jerryno Novotny May 10 '17 at 13:58

2 Answers2

6

Here is a solution using a temporary IK constraint:

enter image description here

The principle is to:

  • Set an empty at cursor position
  • Add a IK constraint to the bone targeting the empty
  • Get the resulting transformation
  • Remove the constraint
  • Assign the previously obtained transformations

The commented code:

import bpy

#Bones and its parents
def BoneAndParents( bone ):
    yield bone
    while bone.parent:
        bone = bone.parent
        yield bone

arm = bpy.data.objects['Armature']

bpy.ops.object.mode_set(mode='OBJECT')

#Add a temporary empty
bpy.ops.object.add()
empty = bpy.context.scene.objects.active

bpy.context.scene.objects.active = arm

bpy.ops.object.mode_set(mode='POSE')

bone = arm.pose.bones['Bone.003']

#Add IK constraint and update
constraint = bone.constraints.new( 'IK' )
constraint.target = empty
bone.constraints.update()
bpy.context.scene.update() #Important: update the scene

#Get the bones and transformation due to the constraint
bones = [b for b in BoneAndParents( bone )]
transformations = [b.matrix.copy() for b in bones]

#Get rid of the temporary constraint and update
bone.constraints.remove( constraint )
bone.constraints.update()

#Assign transformation
for b, t in zip( bones, transformations ):
    b.matrix = t

#Remove temporary empty
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.scene.objects.active = empty
bpy.ops.object.delete()

#Back to pose
bpy.context.scene.objects.active = arm
bpy.ops.object.mode_set(mode='POSE')
lemon
  • 60,295
  • 3
  • 66
  • 136
  • I don't know why you have to assign the transformation matrices after the updates. – robit Mar 06 '19 at 09:29
  • If you remove the empty, the constraint is also removed right ? Or your assign transformation will help there ? I haven't checked that case. – Rohit Davas Oct 06 '21 at 05:08
1

Writing an updated answer for above answer ( @lemon 's answer )

# set the armature object as active object and switch to pose mode
meshname = "mesh_1"
bpy.context.view_layer.objects.active = bpy.data.objects[meshname] 
print(f"active object name = {bpy.context.active_object.name}") 
obj =bpy.context.active_object
bpy.ops.object.mode_set(mode="POSE")

---------

access location of bone that you want to change

use this location to decide the empty object's location

bone = obj.pose.bones['index_03_r'] # pose bone location_local = bone.head.to_4d() # homogenous coordinates

change from local coordinate to global coordinate

M_arm_to_global = obj.matrix_world.to_4x4() location_global = M_arm_to_global @ location_local location_global = location_global.to_3d()

modify global location as per needed for setting the empty object

location_global[0] -= 0.1 location_global[1] -= 0.1 location_global[2] -= 0.1

above answer has used a recursive yield.

i have blender's own recursive call for the parent info

you don't need this function .

added this for the info reason.

def BoneAndParents(bone): return bone.parent_recursive

add an empty object

bpy.ops.object.mode_set(mode="OBJECT") bpy.ops.object.empty_add(type='SINGLE_ARROW', align='WORLD', location=location_global, scale=(1, 1, 1)) empty = bpy.context.active_object

set the mesh as active again

bpy.context.view_layer.objects.active = bpy.data.objects[meshname]

clear the parent of the bone after which you do not want IK to impact

bone so that arm does not move.

you are breaking the hierarchy of the transformations

or using / giving less flexibilty to IK

I set parent to None of Hand

bpy.ops.object.mode_set(mode="EDIT") bpy.context.active_object.data.edit_bones['hand_r'].parent = None

go to pose mode

bpy.ops.object.mode_set(mode="POSE")

move bone index_03_r to empty as target via IK

bone = obj.pose.bones['index_03_r'] constraint = bone.constraints.new("IK") constraint.target = empty bone.constraints.update()

update API as per blender 2.82 and above

bpy.context.view_layer.update()

# the other lines used by lemon are not needed.

NOT NEEDED - FOLLOWING LINES

ADDED FOR DISCUSSION PURPOSE AND AS ASKED BY @ROBIT in comment.

#Get the bones and transformation due to the constraint

bones = [b for b in BoneAndParents( bone )]

transformations = [b.matrix.copy() for b in bones]

# Assign transformation

for b, t in zip( bones, transformations ):

# b.matrix = t