I'm stuck, Trying to roll a pose bone while staying in the Head and Tail vector. When I use axis angle or any other rotation, it doesn't stay in place and it goes off its vector.
2 Answers
Rolling a pose bone.
Using python, how do you get the "roll" of a pose_bone?
IMO this is simply a case of rotating the bone around any pivot point that is on a line parallel to y axis vector passing thru points head and tail.
Matrix math to translate, rotate, scale with respect to a pivot point in Object mode
import bpy
from mathutils import Matrix
def rollposebone(pb, roll):
R = (
Matrix.Translation(pb.head) @
Matrix.Rotation(roll, 4, pb.y_axis) @
Matrix.Translation(-pb.head)
)
pb.matrix = R @ pb.matrix
# test call, roll 3 degrees.
from math import radians
context = bpy.context
rollposebone(context.active_pose_bone, radians(3))
Please also note that blender natively uses radians, as does math
and numpy
. Recommend using as above, ie if you wish to pass a value like 90 degrees pass via radians(90)
rather than converting to and fro in code. If making a UI give any angle property a `unit = 'ROTATION`` and it will be displayed in the chosen rotation unit, which is by default degrees.
import bpy
import math
import numpy as np
from mathutils import Vector
context = bpy.context
scene = context.scene
RobloxRig = bpy.context.scene.objects['__Rig']
UpperRightArm_bone = RobloxRig.pose.bones["RightUpperArm"]
def angle(v1, v2, acute):
# v1 is your first vector
# v2 is your second vector
angle = np.arccos(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)))
if (acute == True):
return angle
else:
return 2 * np.pi - angle
def RollBone(bone,Degrees):
vdir = Vector(bone.tail - bone.head)
BoneXVec = Vector((0,vdir.y,vdir.z))
Xangle = math.degrees(angle(Vector((0, 0, 1)), BoneXVec, True))
xAbs = vdir.y
xsign = 0
if xAbs > 0:
xsign = -math.radians(Xangle)
if xAbs < 0:
xsign = math.radians(Xangle)
#Center X
bpy.ops.transform.rotate(value= xsign, orient_axis='X', orient_type='GLOBAL')
bpy.context.view_layer.update()
vdir = Vector(bone.tail - bone.head)
BoneXVec = Vector((vdir.x,0,vdir.z))
Yangle = math.degrees(angle(Vector((1, 0, 0)), BoneXVec, True))
yAbs = vdir.z
ysign = 0
if yAbs > 0:
ysign = -math.radians(Yangle) + math.radians(90)
if yAbs < 0:
ysign = math.radians(Yangle) + math.radians(90)
#Center Y
bpy.ops.transform.rotate(value=ysign, orient_axis='Y', orient_type='GLOBAL')
bpy.context.view_layer.update()
#Roll on Z
bpy.ops.transform.rotate(value=math.radians(Degrees), orient_axis='Z', orient_type='GLOBAL')
bpy.context.view_layer.update()
#Return Rotation
bpy.ops.transform.rotate(value=-ysign, orient_axis='Y', orient_type='GLOBAL')
bpy.ops.transform.rotate(value= -xsign, orient_axis='X', orient_type='GLOBAL')
if __name__ == "__main__":
RollBone(UpperRightArm_bone,90)
I used bpy.ops.transform.rotate to rotate the bone to its normal x and y vector, then I rotate z to the desired angle. Lastly I use the inverse of x and y rotations to set it back to the space it was at.