1
$\begingroup$

I have 2 different hand objects in separate files that have bones identically named. I want to set the angles between the bones of hand 2 same as the angles in hand 1 so that they have the same posture.

I have tried getting the bones' positions from hand 1 as normalized vectors, then do the same for hand 2 and find the angles between two vectors along X, Y and Z axes. Then, rotate the bones with those angles. However, the result is not at all what I want. The rotate_euler.rotate_axis() function rotates the bone over its local axes, not the global ones.

How should I approach this problem? Any help would be appreciated.

Here are some screenshots for better visualizing the situation.

Bone 1

[![enter image description here][1]][1]

Bone 2

[![enter image description here][2]][2]

Script that saves normalized vectors from Bone 1

import bpy
import json
from math import *
from mathutils import *
import numpy as np

obj = bpy.data.objects["Armature"]
bones = obj.pose.bones

with open("vectors.json","w") as outfile:
    outfile.write("[")
    i = 0
    for bone in bones:
        u = (bone.tail - bone.head).normalized()
        outfile.write(json.dumps({bone.name:list(u)},indent = 2))
        if(i != len(bones.keys())-1):
            outfile.write(",\n")
            i += 1
    outfile.write("]")

Script that finds the angles and rotates the bones of Hand 2

import json
import numpy as np
import bpy
from math import *
from mathutils import *

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

with open("vectors.json","r") as infile:
    vectors = json.loads(infile.read())

counter = 1
for vector in vectors:
    v1 = vector
    key = list(v1.keys())[0]
    print(key)
    v1 = Vector(vector[key])
    bone = arm.pose.bones[key]
    v2 = bone.tail - bone.head
    v2 = v2.normalized()
    
    angleyz = acos(np.dot(v1.yz,v2.yz))
    anglexy = acos(np.dot(v1.xy,v2.xy))
    anglexz = acos(np.dot(v1.xz,v2.xz))
    
    bone.rotation_mode = 'XYZ'
    
    bone.rotation_euler.rotate_axis("X", angleyz)
    bone.rotation_euler.rotate_axis("Y", anglexz) 
    bone.rotation_euler.rotate_axis("Z", anglexy)

Result [![enter image description here][3]][3]

The Files I am working on

https://drive.google.com/drive/folders/1d4J1O7GuPSU6UeEd-YdZ0M4EN0be1p9v?usp=sharing

I am trying to make the hand in the abnormal.blend to have the same pose as the hand in neutral.blend [1]: https://i.stack.imgur.com/BxpqC.png [2]: https://i.stack.imgur.com/Q0ctQ.png [3]: https://i.stack.imgur.com/G4yjy.png

$\endgroup$

2 Answers 2

3
$\begingroup$

Use matrices.

enter image description here

In Python, How to get pose bone to rotate with another pose bone from a different armature? Finding Rotational difference have shown how to snap a bone to another using world space matrices of each. The example snaps one bone to the world space rotation of another.

Similarly here could look at local space. Have simply converted the local space matrix back to pose and applied to bone. Bit hard to test without two rigs, but as shown in linked answer, and Python How to make pose bone rotation toward a point? can make matrix to adjust the rotation of the target pose bone to the source. Basically the rotation part of a pose matrix is pointing the Y axis of the bone (head - tail). If a bone is connected it will ignore the location part of its matrix

import bpy
from bpy import context
from mathutils import Matrix
import json

# WRITE

def pose2local(pb):
    M = pb.id_data.convert_space(
        pose_bone=pb,
        matrix=pb.matrix,
        from_space='POSE',
        to_space='LOCAL'
        )
    return [row[:] for row in M]

scene = context.scene
rig1 = scene.objects.get("Armature")

output = {pb.name : pose2local(pb)
        for pb in rig1.pose.bones}

with open("matrices.json", "w") as fp:
    json.dump(output, fp)      


# READ

rig2 = scene.objects.get("Armature.001")

with open("matrices.json", "r") as fp:
    data = json.load(fp)

def local2pose(pb, matrix):
    return pb.id_data.convert_space(
        pose_bone=pb,
        matrix=Matrix(matrix),
        from_space='LOCAL',
        to_space='POSE'
        )


for name, matrix in data.items():
    pb = rig2.pose.bones.get(name)
    if pb:
        pb.matrix = local2pose(pb, matrix)
$\endgroup$
2
  • $\begingroup$ Thanks for the comprehensive answer. This script applies the transform changes of one hand to other; however, it does not change the original position of hand 2. I am adding the links of my blend files to original post if you want to look $\endgroup$ Mar 21, 2021 at 10:47
  • $\begingroup$ As in the linked answer can decompose & recompose a matrix to keep any or all components of the source -> to make the matrix of the transform target . Was unsure, as if the bones are connected AFAIK the location doesn't matter and gets dealt with for us. The linked example only changes the rotation of matrix world of target. $\endgroup$
    – batFINGER
    Mar 21, 2021 at 11:58
0
$\begingroup$

The simplest way would probably be to create a Pose Library for the source file and save the pose to it, then later append that Pose library to the destination file, then link to it, and then assign the pose to the new hand.

Select your source hand armature in Object Mode, then go into Pose Mode and select every bone of the hand. The reason for selecting all bones is that poses are saved on a selected bone basis. Any bone not selected won't have its position saved to the pose. Click the Object Data tab in the Properties Panel (Skeleton Icon) and expand the section titled Pose Library. Click New to create a new library. Give it a name. Now click the "+" icon to add a new pose, based on your current selection, and then name it also.

Save your file.

Now open the file with the destination hand. Select its armature in Object Mode, go into Pose Mode, select all the bones.

Now, go to the topmost file menu and choose File > Append. It will bring up a list of folders which constitute parts of the source file. You are looking for the folder called Action. Open it, and look for the name of your pose library. Select it, and press the Append button at the bottom right corner of the window.

Now all that is left is to link the pose library to your armature. In your destination file, you should still see the contents of the Object Data tab in the Properties Panel, including the Pose Library section. Look for the link button to the left of the Pose Library name field and click on it to link a new pose library. You should see your pose library listed, with a zero to its left, signifying that it has no users currently. Choose it, and your library will be linked to your object. All your destination armature's bones should still be selected. If they aren't, select them all. With the pose in the library currently selected (it will be selected if it's the only one present, but if you have extra poses, selecting specific ones becomes a relevant step) press the magnifying glass icon to apply the selected pose from the library to the selected bones in your 3D Viewport.

$\endgroup$
3
  • $\begingroup$ Thanks for the clean explanation. I have done what you have said, yet, none of the bones have moved. The pose library is shown under armature object in scene collection hierarchy. Might be unnecessary to ask but does having different length bones in these 2 hands would affect anything? $\endgroup$ Mar 19, 2021 at 12:23
  • $\begingroup$ My guess is that the length won't affect anything, since I believe what gets copied are joint orientations. If you share your files, I'll take a look at them. blend-exchange.com $\endgroup$
    – R-800
    Mar 20, 2021 at 10:28
  • $\begingroup$ I am adding the link for them in the original post $\endgroup$ Mar 21, 2021 at 10:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.