0
$\begingroup$

The answer in this question is great : How to rotate a bone using python

I use it to rotate bones in Pose mode but it adds the current angle to the new angle instead of setting the new angle regardless of what the current angle is.

Example:

I run the code to set the rotation of a bone to be 10 deg.

I run it again to set a new rotation of 20 deg. But I get 30 deg instead. I get the summation or the effect of the two codes. I need to get the last code's effect only. How can I do that?

$\endgroup$
2
$\begingroup$

If the rotation is directly assigned to bone.rotation_euler then the it will not accumulate, unlike when you're using bone.rotation_euler.rotate_axis().

import bpy
import math
import mathutils


def rotate_bone(armature, bone, rotation_euler):
    # Remember active object and its mode
    old_active_object = bpy.context.view_layer.objects.active
    old_mode = old_active_object.mode

    # Set the armature as active object and switch to pose mode
    armature = bpy.data.objects[armature]
    bpy.context.view_layer.objects.active = armature
    bpy.ops.object.mode_set(mode='POSE')

    # Set the rotation mode to use euler angles and adjust the 
    # rotation of the bone
    bone = armature.pose.bones[bone]
    bone.rotation_mode = 'XYZ'
    bone.rotation_euler = rotation_euler

    # Restore previously active object and its mode
    bpy.context.view_layer.objects.active = old_active_object
    bpy.ops.object.mode_set(mode=old_mode)

if __name__ == "__main__":
    rot_x = math.radians(10.0)
    rot_y = math.radians(0.0)
    rot_z = math.radians(0.0)
    rotation_euler = mathutils.Euler((rot_x, rot_y, rot_z), 'XYZ')
    rotate_bone('Armature', 'Bone', rotation_euler)
| improve this answer | |
$\endgroup$
2
$\begingroup$

Relative vs Absolute.

The "accumulative" nature is the nature of a 3D transform, ie rotate the bone from where it is by this much about an axis.

Whereas you wish to set the absolute rotation.

As well as using method in accepted answer, contend you could also pre-set the rotation to zero before applying any transform, for example if the rotation mode is euler

posebone.rotation_euler = (0, 0, 0)

Zeroing before applying a rotation will result in an absolute value. Could say, absolute is relative from zero.

Matrices

The following will zero rotation of a pose bone, regardless of its rotation mode, Euler, Quaternion or Angle Axis.

Using matrices can zero the rotation by finding the inverse of the rotation part of the matrix and pre or post multiplying. The inverse of a rotation matrix is the relative rotation to apply to rotate back to zero.

The top left 3x3 submatrix of a 4x4 transform matrix is the rotation part. A rotation matrix is orthogonal, hence the transpose is also the inverse (a cheaper calculaton)

>>> pb = C.object.pose.bones['Bone']
>>> M = pb.matrix_basis.to_3x3().transposed().to_4x4()
>>> pb.matrix_basis @= M

To set the rotation from whatever to 22 degrees about Z in whatever rotation mode

>>> R = Matrix.Rotation(radians(22), 4, 'Z')
>>> M = pb.matrix_basis.to_3x3().transposed().to_4x4()
>>> pb.matrix_basis = R @ M @ pb.matrix_basis

or similarly using below as rotation matrix, will set a pose bone of any rotation mode to the euler equivalent without the need to set the mode.

>>> R = Euler((0, 0, radians(22)), 'XYZ').to_matrix().to_4x4()

Why? what if I don't want all the bones changed to 'XYZ' Euler, and instead want to keep all as assigned, using above can keyframe with something like

pb.matrix_basis = R @ M @ pb.matrix_basis

if pb.rotation_mode == 'AXIS_ANGLE':
    pb.keyframe_insert("rotation_axis_angle")
elif pb.rotation_mode == 'QUATERNION':
    pb.keyframe_insert("rotation_quaternion")
else:
    pb.keyframe_insert("rotation_euler")
| improve this answer | |
$\endgroup$

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.