Skip to content

Commit

Permalink
matrix functions snippets and anim
Browse files Browse the repository at this point in the history
  • Loading branch information
Pullusb committed Feb 15, 2025
1 parent 9045476 commit 5b6bb1c
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 0 deletions.
17 changes: 17 additions & 0 deletions snippets/bpy/get_position_and_axis_direction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Get object or posebone world position + 3 axis direction vectors

def get_position_and_axis_direction(target):
'''Get world position and local direction axis of object or bone
return 4 vector: loc, x, y, z (1 position, 3 for axis directions)'''
if isinstance(target, bpy.types.PoseBone):
armature = target.id_data
matrix = armature.matrix_world @ target.matrix
else:
matrix = target.matrix_world
x = Vector((1,0,0))
y = Vector((0,1,0))
z = Vector((0,0,1))
x.rotate(matrix)
y.rotate(matrix)
z.rotate(matrix)
return matrix.to_translation(), (x, y, z)
49 changes: 49 additions & 0 deletions snippets/bpy/set_keyframe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Insert keyframe on object or posebone, automatically determine rotation data_path with axis choice

# Mapping of rotation modes to data_path
ROTATION_MODE_MAP = {
'ZYX': 'rotation_euler',
'ZXY': 'rotation_euler',
'YZX': 'rotation_euler',
'YXZ': 'rotation_euler',
'XZY': 'rotation_euler',
'XYZ': 'rotation_euler',
'QUATERNION': 'rotation_quaternion',
'AXIS_ANGLE': 'rotation_axis_angle'
}

def set_keyframe_on_axis(target, transform, frame, use_x=True, use_y=True, use_z=True):
'''simple keyframe insertion, pass object or bone, a transform channel name and a frame number
transform (str): 'location', 'rotation' or 'scale'
frame: frame number where to add the keyframes
use_x, use_y, use_z (bool): enable keyframe insertion per axis (always takes all for quaternion and axis-angle rotation)
'''

## Define axis to key (always all 4 in quaternion and axis_angle mode)
use_axes = [use_x, use_y, use_z]
if all(use_axes) or (transform == 'rotation' and target.rotation_mode in ('QUATERNION', 'AXIS_ANGLE')):
axis_indices = [-1]
else:
# Get the indices of enabled axes
axis_indices = [i for i, use_axis in enumerate(use_axes) if use_axis]

## define data_path to keyframe (adjust data_path for rotation)
data_path = ROTATION_MODE_MAP[target.rotation_mode] if transform == 'rotation' else transform

# if isinstance(target, bpy.types.PoseBone):
for axis_index in axis_indices:
## set group and options ? (probably not needed)
target.keyframe_insert(data_path, index=axis_index, frame=frame) # , keytype='KEYFRAME'

## Find and return newly created keyframes
## (/!\ Using other Ops within operator sometimes void reference to keyframes)
# path_prefix = ''
# if isinstance(target, bpy.types.PoseBone):
# path_prefix = f'pose.bones["{target.name}"].'
# return [k for fc in target.id_data.animation_data.action.fcurves
# if fc.data_path == f'{path_prefix}{data_path}' and (axis_indices[0] == -1 or fc.array_index in axis_indices)
# for k in fc.keyframe_points if k.co.x == frame]


## usage: key rotation on active object at frame 10
set_keyframe_on_axis(bpy.context.object, 'rotation', 10)
21 changes: 21 additions & 0 deletions snippets/math/add_translation_vector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Translate object or bone in world space by a given vector

def add_translation_vector(target, translation_vector):
"""Apply world space translation vector to an object or a bone
Usage:
Add 1 unit up:
add_translation_vector(bpy.context.object, Vector((0,0,1)))
"""
if isinstance(target, bpy.types.PoseBone):
armature = target.id_data # Get the armature object
# Switch to armature space
current_world_position = (armature.matrix_world @ target.matrix).to_translation()
new_world_position = current_world_position + translation_vector
new_local_position = armature.matrix_world.inverted() @ new_world_position
target.matrix.translation = new_local_position
# return new_local_position
return

## Add directly to object matrix
target.matrix_world.translation += translation_vector
# return target.matrix_world.to_translation()
44 changes: 44 additions & 0 deletions snippets/math/apply_quaterion_to_object_or_posebone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Apply quaternion rotation on object or pose bone

from mathutils import Matrix

def apply_quaternion_to_pose_bone(posebone, quaternion):
"""Rotate pose bone in world space by given quaternion"""
# Create rotation matrix from quaternion
rot_mat = quaternion.to_matrix().to_4x4()

# Get bone current world matrix
armature = posebone.id_data
world_mat = (armature.matrix_world @ posebone.matrix).copy()

# Get world pivot point
world_pivot = armature.matrix_world @ posebone.head

# Apply the rotation on the copy of world matrix
# remove pivot vector, apply rotation, re-apply pivot
new_world_mat = Matrix.Translation(world_pivot) @ \
rot_mat @ \
Matrix.Translation(-world_pivot) @ \
world_mat

## Assign matrix to posebone in armature space
posebone.matrix = armature.matrix_world.inverted() @ new_world_mat

def apply_quaternion_to_object(obj, quaternion):
"""Rotate pose bone in world space by given quaternion"""
# Create rotation matrix from quaternion
rotation_matrix = quaternion.to_matrix().to_4x4()

# Get object's current world matrix
world_matrix = obj.matrix_world

# Get object's pivot point (origin) in world space
pivot = world_matrix.to_translation()

# Create translation matrices
to_pivot = mathutils.Matrix.Translation(-pivot)
from_pivot = mathutils.Matrix.Translation(pivot)

# Apply rotation around pivot:
# 1. Move to center, 2. Apply rotation, 3. Move back to initail position
obj.matrix_world = from_pivot @ rotation_matrix @ to_pivot @ world_matrix
35 changes: 35 additions & 0 deletions snippets/math/object_world_rotation_using_euler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Apply a Euler to rotate an object in worldspace (not much use case)

import bpy
from mathutils import Matrix, Euler
from math import radians

def apply_world_euler_rotation_object(obj, rotation_euler):
# Get current world matrix
world_matrix = obj.matrix_world

# Get pivot point
pivot = world_matrix.to_translation()

# Create rotation matrix from Euler
rotation_matrix = rotation_euler.to_matrix().to_4x4()

# Create translation matrices
to_pivot = Matrix.Translation(-pivot)
from_pivot = Matrix.Translation(pivot)

# Apply rotation around pivot
new_matrix = from_pivot @ rotation_matrix @ to_pivot @ world_matrix

# Extract Euler angles from the new matrix while maintaining rotation mode
rot_mode = obj.rotation_mode
new_euler = new_matrix.to_euler(rot_mode)

# Make compatible with previous rotation to avoid discontinuities
new_euler.make_compatible(obj.rotation_euler)

# Update object's rotation
obj.rotation_euler = new_euler

# Example, rotate active object by 45 degrees on World X axis
apply_world_euler_rotation_object(bpy.context.object, Euler((radians(45),0,0)))

0 comments on commit 5b6bb1c

Please sign in to comment.