5
$\begingroup$

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

$\endgroup$
5
  • $\begingroup$ 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 ) $\endgroup$
    – batFINGER
    Commented May 10, 2017 at 12:02
  • $\begingroup$ @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? $\endgroup$
    – Tak
    Commented May 10, 2017 at 12:09
  • $\begingroup$ Rotate it to align with vector made by subtracting base from cursor (in same space ofcourse) . Here is how to do so for object. blender.stackexchange.com/questions/19533/… $\endgroup$
    – batFINGER
    Commented May 10, 2017 at 12:17
  • $\begingroup$ @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. $\endgroup$
    – Tak
    Commented May 10, 2017 at 12:22
  • $\begingroup$ 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. $\endgroup$ Commented May 10, 2017 at 13:58

2 Answers 2

6
$\begingroup$

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')
$\endgroup$
2
  • $\begingroup$ I don't know why you have to assign the transformation matrices after the updates. $\endgroup$
    – robit
    Commented Mar 6, 2019 at 9:29
  • $\begingroup$ If you remove the empty, the constraint is also removed right ? Or your assign transformation will help there ? I haven't checked that case. $\endgroup$ Commented Oct 6, 2021 at 5:08
1
$\begingroup$

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
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .