Using bone.translate() to move a bone?

Hi All,

I am trying to move a bone using the translate() function described here:

Here is the code I have, but the bone never moves.


import bpy
import mathutils


ob = bpy.data.objects["Armature"]
arm = ob.data
bone_root = arm.bones["Bone"]
new_bone_loc = (1.0,2.0,3.0)
vec = mathutils.Vector(new_bone_loc)
bone_root.translate(vec)

Does anyone know how to use this function?

Or any other way to move a bone without using bpy.ops?

Thanks

Hmmm…


...
>>> bb=arm.edit_bones['Bone']
>>> bb.tail=(3,3,3)
>>> bb.head=(5,5,5)

Oh, looks like same goes for pose, e.g. arm.pose.bones[]

And both require to be in appropriate mode too!

So how do you actually get a pose bone?

pb = arm.pose.bones["Bone"]

Seems to be invalid syntax?

Here’s a slightly amended script snippet from the 2.57 Scripting PDF, just for a context example:


    # Create armature and object    
    bpy.ops.object.armature_add()
    ob = bpy.context.object
    amt = ob.data
    
    # Set object location in object mode
    ob.location=origin
    bpy.ops.transform.rotate(value=(ang,), axis=(0.0, 0.0, 1.0))    #0.7855
 
    # Rename first bone
    bpy.ops.object.mode_set(mode='EDIT')
    
    base = amt.edit_bones['Bone']
    base.tail = (2,0,0)
    base.name = 'Base'
    
    # create second bone
    tip = amt.edit_bones.new('Tip') #   name=tip
    tip.head = (2,0,0)
    tip.tail = (1,0,0)
    tip.parent = base
    tip.use_connect = True

    # Set rotation mode to Euler ZYX
    bpy.ops.object.mode_set(mode='POSE')    
    pbase = ob.pose.bones['Base']
    ptip = ob.pose.bones['Tip']

It could be chapter 3 ???
http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Cookbook/Code_snippets

any other way to move a bone without using bpy.ops?
Yeah, but that won’t work in a frameChange event.

I need a solution that directly manipulates bpy.data, NO bpy.ops allowed, it will just error out on render.

I’m still new at this Blender scripting thing…

But if you ran a script like this which followed by inserting key frames, all before render then it’d fine whilst rendering 'cos that’s what i’m doing.

Or, is your script being called on each frame of a render? If so pass me an example script / setup to do it and i’ll give it another go…

Here is an example frame change that changes the head and tail of a bone in the default armature.

While the code executes without errors, the bone never updates or changes it’s position.

Attachments

25_frame_change_bone_move.blend (462 KB)

So I am still trying to move a bone with code.

I tried rarebits code but all I get are errors. Still clues have lead me to this…


import bpy
import mathutils

ob = bpy.data.objects["Armature"]
arm = ob.data

bpy.ops.object.posemode_toggle()
bpy.ops.object.editmode_toggle()
bone_root = arm.edit_bones['Bone']
new_bone_loc = (0.0,0.0,1.0)
vec = mathutils.Vector(new_bone_loc)
bone_root.translate(vec)

This will move an edit bone. But I need to move a pose bone. Does anyone have any working example code that shows how to do this?

Thanks

tail and head are readonly in pose-mode,
moving a bone is only possible with its location, matrix…

in python-console, just try something like:
bone.matrix = bone.matrix_basis
and the bone will rotate…

btw. i was thinking about writing down a function to set the bone in pose-mode
to a global location/rotation …
something like:
def set_posebone_location(…global_vector)

there was already a posting about such a thing
> http://blenderartists.org/forum/archive/index.php/t-197056.html
(but the sample-code needs re-checked… with/without object-space…)

Ok,

I brought in the code from that link you posted but I am not sure if I restored the indentation correctly.
Does this look right?


import bpy

def poseArmature(rig, locations, rotations):
    bpy.context.scene.object.active = rig

    bpy.ops.object.mode_set(mode='EDIT')
    headRest = {}
    matrixRest = {}
    inverseRest = {}
    for eb in rig.data.edit_bones[]
        name = eb.name
        headRest[name] = eb.head
        matrixRest[name] = eb.matrix.rotation_part() 
        inverseRest[name] = matrixRest[name].copy().invert()

    bpy.ops.object.mode_set(mode='POSE')
    nFrames = len(locations)
    for pb in rig.pose.bones:
        name = pb.name
        for frame in range(nFrames): 
            pb.location = inverseRest[name] * (locations[frame] - headRest[name])
            for n in range(3): 
                pb.keyframe_insert('location', index=n, frame=frame, group=name)    

            mat = inverseRest[name] * rotations[frame] * matrixRest[name]
            pb.rotation_quaternion = mat.to_quat()
            for n in range(4): 
                pb.keyframe_insert('rotation_quaternion', index=n, frame=frame, group=name)

So the next question is how do I call the DEF to make it mode a pose bone?
And how would I target an individual bone for movement?

I tried your other suggestion, but I just get an error “Bone object has no attribute matrix_basis”.


import bpy
import mathutils

ob = bpy.data.objects["Armature"]
arm = ob.data
bone_root = arm.bones["Bone"]


#bpy.ops.object.editmode_toggle() 
#bpy.ops.object.posemode_toggle()

bone_root.matrix = bone_root.matrix_basis

I tried running the code with both versions of toggles.

I found some more test-dr code (thanks!) and was able to piece something together that actually works.


import bpy
from mathutils import *

ob_active = bpy.context.scene.objects.active    # Save the active object. 

arm = bpy.data.objects["Armature"]              # Get Armature
bpy.context.scene.objects.active = arm          # make it the active object for manipulation
bpy.ops.object.mode_set(mode="POSE")            # need POSE mode to set armature bones
pb = arm.pose.bones["Bone"]
pb.location[2] = 1.0 
bpy.ops.object.mode_set(mode="POSE")            # need POSE mode to set armature bones

bpy.context.scene.objects.active = ob_active    # Restore the active object.

You can change the location of a pose bone just like any other object if the armature is in the correct state. I have not tried this with a “skinned” rig, however, only the default single bone armature from the SHIFT-A menu.

I don’t really like using this toggle system. The entire code is based upon the assumption that the rig is in the non-pose state. What if the end-user puts the rig into pose mode and the code is run? All of a sudden our toggles will be backwards and the code will error out…

I am attaching a BLEND file that moves the bone over time based upon the frame number using a ModalTimer. It seems to work.

Attachments

25_fcmt_move_bone_1a.blend (361 KB)

ok, so you tried …
but i dont need to switch the modes (pose/edit).

This works for me (blender-2.58 rxxxx)
and is still far away from full usage:
If the bone has a parent, this is still wrong!!
And i did not test it full for an animated armature-object, my object-location
was at the center …
If the bone has a parent and is NOT connected (free translation), then
it should be possible to set it to a global position to, but what bone-space
to calculate in? Its parent-rotation, parent-tail as offset too …??


import bpy
from mathutils import Vector, Matrix

def set_bonelocation(armaturename="Armature.001", bonename="Bone", newlocation=Vector((0,0,0))):
    print("=========", armaturename, bonename, "========")
    a = bpy.data.objects[armaturename]
    bpose = a.pose.bones[bonename]
    bdata = a.data.bones[bonename]
    hasParent = False
    if bdata.use_local_location == False:
        print("bone used NOT in local location ... what now?")
        return
    if bdata.parent != None :
        #has a parent-bone
        parentname = bdata.parent.name
        hasParent = True
        if bdata.use_connect : #is True, we cannot move the bone
            print("bone has parent:", parentname, " and is connected!")
            return
        parent_tail = a.data.bones[parentname].tail
        parent_mat = a.data.bones[parentname].matrix.copy()
        parent_mat_inv = parent_mat.inverted()
    print(bpose.matrix)        #the world-matrix that is with armature-object-matrix
    print(bpose.matrix_basis)  #the bone-matrix before any ?constraints, etc.
    b_head  = a.data.bones[bonename].head
    b_rotmat= a.data.bones[bonename].matrix
    b_mat = b_rotmat.copy()
    b_mat_inv = b_mat.inverted()
    print(bonename, bpose.location, b_head)
    print(b_mat)
    print(b_mat_inv)
    print( b_mat * b_mat_inv)
    print( b_head * b_mat )
    print( b_head * b_mat_inv)
    print( "to newlocation..")
    # a bone without parent, subtract its armature-loc and its bone-loc, than use translation in bone-space 
    location = (newlocation - a.location - b_head ) * b_mat_inv
    if hasParent:
        location = (newlocation - a.location - b_head - parent_tail) * b_mat_inv * parent_mat_inv
    print(location)
    bpose.location = location
        
set_bonelocation("Armature.001") #test with simple one bone
#set_bonelocation("Armature", "hips") #this is for the biped ...

and what to do if there is NOT activated “use_local_location” for a bone?
I never used this. Could this be used for easy setting the location in global-world-coords?

Thanks for the code. I am new to armature and all that math. But it does work to move bones in an existing RIG. (Tested with FatBastard from Blendswap) Also, your code does not reference the context or use bpy.ops which is important for a frame change based solution.

I guess the next question is how do I rotate a bone so I can automate things like hips and arm swing during a walk?

it is the same like the “translation” problem.
As soon you have a working setup for the translation,
this works the same for rotation. It is only a question
about when/what coord-space is used.

For example, you know the influence of the object-space.
If the object is already translated (has a location != (0,0,0) )
then you have to take this into account (cause you can only
modify the bone-location in its coord-space).
Next to the object-space (its matrix) there is the bone-space
(if it is not a child-bone of another bone).
If it is a child-bone, then the coord-space of the parent-bone
is necessary too.

Those coord-system-transformations are … brain-cooking(for me) …
thats why i cannot quick create a solution, i have to do it step by step
slowly.
For one bone it is more easy to use an Empty and modify its location and
rotation and use a constraint for the bone to force it to the location and
rotation of the empty-object.

ok, for any tests
here a more quick function (quick = did only first tests)

(shin.L is a bone from the default biped-rig)
(and i still dont know whats the meaning of “use_local” in the armature-bone)


import bpy
from mathutils import Vector, Matrix

def set_bonelocation(armaturename="Armature", bonename="Bone", newmatrix=Matrix()):
    """ set armature bone to new world-matrix in its local-space
        connected bones can only be rotated
        """
    print("=========", armaturename, bonename, "========")
    a = bpy.data.objects[armaturename]
    # if we are in edit-mode, some settings may not up-to-date, leave edit-mode!
    if bpy.context.active_object == a:
        print("using active_object")
        if bpy.context.mode == "EDIT_ARMATURE":
            print("we are in EDIT mode - leave to free read of properties")
            bpy.ops.object.mode_set(mode="POSE")
    bpose = a.pose.bones[bonename]
    bdata = a.data.bones[bonename]
    hasParent = False
    isConnected = False
    if bdata.use_local_location == False:
        print("bone used NOT in local location ... what now?")
        return
    b_localmat= a.data.bones[bonename].matrix_local.copy()
    b_localmat_inv = b_localmat.inverted()
    if bdata.parent != None :
        #has a parent-bone
        parentname = bdata.parent.name
        hasParent = True
        print("use_connect:", bdata.use_connect)
        if bdata.use_connect : #is True, we cannot move the bone
            print("bone has parent:", parentname, " and is connected!")
            #maybe i should only set the rotation now
            isConnected = True
#            return
        parent_localmat = a.data.bones[parentname].matrix_local.copy()
        parent_localmat_inv = parent_localmat.inverted()
        parent_pose_mat = a.pose.bones[parentname].matrix.copy()
        #this calculates for a child-bone the world-pos(without constraints)
        # using the current parent-pose, parent-local and child-bone-local
        # if the child is not moved, 
        #  parent-pose-matrix  inverted-parent-edit  no:1-matrix   bone-edit-matrix
        # this calculation should be the same like bones posematrix (without constraints)
#        m = parent_pose_mat * parent_localmat_inv  * Matrix()             * b_localmat
#        print("---m:",m)
#        print("pose:",bpose.matrix)
#    print( b_localmat * b_localmat_inv)
    if hasParent:   #use the parent-space too
        set_mat =  parent_localmat * parent_pose_mat.inverted() * newmatrix * b_localmat_inv
        print("has parent, should set to mat:", set_mat, isConnected)
        rotation = set_mat.to_3x3()
        location = set_mat.to_translation()
    else: #can anyone easy explain why this is so different to the has-parent-case?
        set_mat = newmatrix * b_localmat_inv
        rotation = set_mat.to_3x3()
        location = (newmatrix.to_translation() - b_localmat.to_translation()) * b_localmat_inv.to_3x3()
    if isConnected:
        print("connected_bone, use only rotation")
        bpose.rotation_quaternion = rotation.to_quaternion()
    else:
        print("use location and rotation")
        bpose.location = location
        bpose.rotation_quaternion = rotation.to_quaternion()
        
#set_bonelocation(bonename="Bone.001")
set_bonelocation("Armature", "shin.L")