2026-02-16

This commit is contained in:
2026-03-17 15:25:32 -06:00
parent d5dd373de0
commit 60100fbab2
560 changed files with 33397 additions and 20776 deletions
+289 -189
View File
@@ -8,6 +8,7 @@ from mathutils import Vector
# import math
from math import pi
from . import emp
from . import TempCtrls
# import time
from bpy.app.handlers import persistent
from bpy_extras import anim_utils
@@ -68,17 +69,21 @@ def draw_text_callback_px(self, context):
font_id = 0 # XXX, need to find out how best to get this.
#fade in
factor = 0.5
if not self.fade_out_start:
alpha = (self.timer.time_duration / self.fade_duration) * factor if not self.no_timer else factor
else:
timer = self.timer.time_duration - self.fade_out_start
alpha = (1 * factor) - ((timer / self.fade_duration ) * factor)
try:
if not self.fade_out_start:
alpha = (self.timer.time_duration / self.fade_duration) * factor if not self.no_timer else factor
else:
timer = self.timer.time_duration - self.fade_out_start
alpha = (1 * factor) - ((timer / self.fade_duration ) * factor)
except ReferenceError:
return
as_height = asset_shelf_height(context)
# draw some text
blf.position(font_id, 70, 45 + as_height, 0)
blf.position(font_id, 70, 50 + as_height, 0)
blf.size(font_id, self.size)
blf.color(0, 1, 1, 0, alpha)
blf.draw(font_id, self.text)
class Markers_Retimer(bpy.types.Operator):
@@ -188,8 +193,8 @@ class Markers_Retimer(bpy.types.Operator):
if strip.mute:
continue
# actions.add(strip.action)
all_fcurves.add(get_fcurves_channelbag(obj, strip.action))
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
all_fcurves.add(get_fcurves(obj, strip.action))
fcurves = get_fcurves(obj, obj.animation_data.action)
if fcurves not in all_fcurves:
all_fcurves.add(fcurves)
# actions.add(obj.animation_data.action)
@@ -352,19 +357,23 @@ class Markers_Retimer(bpy.types.Operator):
self.report({'ERROR'}, str(e) + '. Quitting RetimerMarkers')
return {'CANCELLED'}
def init_frame_start_marker(self, scene, name = "Frame_Start"):
def init_frame_start_marker(self, scene, name = "Frame Start"):
'''Create the start frame marker'''
self.frame_start_marker = scene.timeline_markers.new(name, frame = self.frame_start)
self.frame_start_marker[ "Frame_Start"] = True
self.frame_start_marker.select = False
self.frame_start_marker_name = name
if name in scene.timeline_markers:
self.frame_start_marker = scene.timeline_markers[name]
else:
self.frame_start_marker = scene.timeline_markers.new(name, frame = self.frame_start)
self.frame_start_marker.select = False
def init_frame_end_marker(self, scene, name = "Frame_End"):
def init_frame_end_marker(self, scene, name = "Frame End"):
'''Create the start frame marker'''
self.frame_end_marker = scene.timeline_markers.new(name, frame = self.frame_end)
self.frame_end_marker["Frame_End"] = True
self.frame_end_marker.select = False
self.frame_end_marker_name = name
if name in scene.timeline_markers:
self.frame_end_marker = scene.timeline_markers[name]
else:
self.frame_end_marker = scene.timeline_markers.new(name, frame = self.frame_end)
self.frame_end_marker.select = False
def check_removed_markers(self, scene):
#check if markers are gone in case of undo or if removed
@@ -507,7 +516,7 @@ class Markers_BakeRange(bpy.types.Operator):
atb.bake_frame_range = False
return {'CANCELLED'}
scene.animtoolbox.bake_frame_range = True
atb.bake_frame_range = True
self.frame_start = atb.bake_frame_start
self.frame_end = atb.bake_frame_end
@@ -640,15 +649,23 @@ def update_cursor_matrix(context):
item_matrix = item.id_data.matrix_world @ item.matrix
else:
item_matrix = item.matrix_world
#apply the new cursor position based on previous cursor
relative_matrix = Matrix(item['relative_cursor'])
cursor.matrix = item_matrix @ relative_matrix.inverted()
if bpy.app.version < (5, 0, 0):
relative_matrix = Matrix(item['relative_cursor'])
else:
# Since Blender 5 I need to reshape the array into a matrix using numpy
matrix_4x4 = np.array(item['relative_cursor']).reshape(4, 4).T
relative_matrix = Matrix(matrix_4x4)
cursor.matrix = item_matrix @ relative_matrix.inverted()
cursor_matrix = cursor.matrix
def assign_relative_matrix(context, items):
def assign_relative_matrix(context, items, prop = 'relative_cursor'):
'''get the cursor relative matrices either from object or bones'''
global last_matrices
if 'last_matrices' not in globals():
last_matrices = []
cursor_matrix = context.scene.cursor.matrix
matrix_avg = get_matrix_avg(items)
@@ -664,9 +681,9 @@ def assign_relative_matrix(context, items):
item_matrix = matrix_avg if len(items) > 1 else item_matrix
matrix_dist = cursor_matrix.inverted() @ item_matrix
item['relative_cursor'] = matrix_dist
item[prop] = matrix_dist
return last_matrices
return matrix_dist
def assign_relative_cursor_prop(context):
'''Updating the matrix property of the cursor distance on the bone'''
@@ -762,6 +779,7 @@ class Keyframe_Offset(bpy.types.Operator):
def invoke(self, context, event):
self.mouse_x = event.mouse_x
self.initial_offset = context.scene.animtoolbox.keyframes_offset
# self.mouse_y = event.mouse_y
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
@@ -779,8 +797,11 @@ class Keyframe_Offset(bpy.types.Operator):
self.mouse_x = event.mouse_x
scene.animtoolbox.keyframes_offset += value*0.01
if event.type in {'ESC'} or event.value == 'RELEASE': # Cancel
if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'RELEASE': # Cancel
scene.animtoolbox.keyframes_offset = self.initial_offset
return {'CANCELLED'}
if event.value == 'RELEASE' and event.type == 'LEFTMOUSE':
self.execute(context)
return {'FINISHED'}
return {'RUNNING_MODAL'}
@@ -838,7 +859,7 @@ def remove_offset_property(context):
def add_offset(obj, offset, prev_offset, bone_id = None):
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
if bone_id is not None:
@@ -876,83 +897,71 @@ def add_offset(obj, offset, prev_offset, bone_id = None):
fcu.keyframe_points.foreach_set('handle_left', updated_left_handle)
fcu.keyframe_points.foreach_set('handle_right', updated_right_handle)
fcu.update()
def add_keyframe(bone):
def get_locked_fcus(obj, locked_fcus = None):
# Locked fcus might have been already created, if it's not None it was already not checked
if locked_fcus is not None:
return locked_fcus
locked_fcus = set()
if not obj.animation_data:
return locked_fcus
if not obj.animation_data.action:
return locked_fcus
fcurves = get_fcurves(obj, obj.animation_data.action)
locked_fcus = {(fcu.data_path, fcu.array_index) for fcu in fcurves if fcu.lock}
return locked_fcus
def add_keyframe(bone, locked_fcus = None):
obj = bone.id_data
if bone.rotation_mode == 'QUATERNION':
rotation = 'rotation_quaternion'
elif bone.rotation_mode == 'AXIS_ANGLE':
rotation = 'rotation_axis_angle'
else:
rotation = 'rotation_euler'
rotation = 'rotation_euler' if len(bone.rotation_mode) == 3 else 'rotation_' + bone.rotation_mode.lower()
# rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
transforms = ['location', rotation, 'scale']
path = bone.path_from_id() if obj.mode == 'POSE' else ''
groups = dict()
#find fcurves that are locked to exclude them
excluded_fcus = {}
if obj.animation_data:
if obj.animation_data.action:
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
for fcu in fcurves:
if path not in fcu.data_path or not fcu.lock:
continue
groups.update({(fcu.data_path, fcu.array_index ): fcu.group.name})
if fcu.data_path in excluded_fcus:
excluded_fcus[fcu.data_path].append(fcu.array_index)
else:
excluded_fcus.update({fcu.data_path : [fcu.array_index]})
path = bone.path_from_id() + '.' if obj.mode == 'POSE' else ''
locked_fcus = get_locked_fcus(obj, locked_fcus)
#insert keyframe only to channels that are not excluded
for transform in transforms:
#path = bone.path_from_id() + '.' + transform
path = bone.path_from_id() + '.' + transform if obj.mode == 'POSE' else transform
# if path not in excluded_fcus:
# bone.keyframe_insert(transform)
# continue
data_path = path + transform
length = len(getattr(bone, transform))
for i in range(length):
if path in excluded_fcus:
if i in excluded_fcus[path]:
continue
if (path, i) in groups:
group_name = groups[(path, i)]
else:
group_name = bone.name
bone.keyframe_insert(transform, index = i, group = group_name, frame = bpy.context.scene.frame_current_final)
def get_fcu_inbetweens(bone, frame, fcu_inbetweens):
for i in range(length):
# Check if this channel being filtered
if TempCtrls.filter_transform(transform, i):
continue
# Skip if the fcurve was locked
if (data_path, i) in locked_fcus:
continue
bone.keyframe_insert(transform, index = i, group = bone.name, frame = bpy.context.scene.frame_current_final)
def get_fcu_inbetweens(bone, frame, fcu_inbetweens, inbetweens):
obj = bone.id_data
if bone.rotation_mode == 'QUATERNION':
rotation = 'rotation_quaternion'
elif bone.rotation_mode == 'AXIS_ANGLE':
rotation = 'rotation_axis_angle'
else:
rotation = 'rotation_euler'
rot_channel = rot_mode_to_channel(bone.rotation_mode)
# rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
transforms = ['location', rotation, 'scale']
# fcurves = obj.animation_data.action.fcurves
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
transforms = ['location', rot_channel, 'scale']
fcurves = get_fcurves(obj, obj.animation_data.action)
#insert keyframe only to channels that are not excluded
location, rotation, scale = bone.matrix_basis.decompose()
for transform in transforms:
path = bone.path_from_id() + '.' + transform if obj.mode == 'POSE' else transform
length = len(getattr(bone, transform))
for i in range(length):
fcu = fcurves.find(data_path = path, index = i)
if not fcu:
continue
if fcu.lock:
continue
if not inbetweens:
# If there is no inbetweens then we just want to store the fcurves for auto handles
fcu_inbetweens[fcu] = None
continue
if 'rotation' in transform:
if transform == 'rotation_euler':
coords = bone.matrix_basis.to_euler(bone.rotation_mode)
@@ -960,7 +969,7 @@ def get_fcu_inbetweens(bone, frame, fcu_inbetweens):
coords = rotation
else:
coords = locals()[transform]
if fcu in fcu_inbetweens:
fcu_inbetweens[fcu].update({frame : coords[i]})
else:
@@ -987,8 +996,10 @@ def add_inbetween_key(self, context):
#strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
#frame = round(bake_ops.frame_evaluation(context.scene.frame_current, strip), 3)
frame = context.scene.frame_current
paths = [bone.path_from_id() for bone in context.selected_pose_bones]
fcurves = get_fcurves_channelbag(obj, anim_data.action)
if obj.mode == 'POSE':
paths = [bone.path_from_id() for bone in context.selected_pose_bones]
fcurves = get_fcurves(obj, anim_data.action)
for fcu in fcurves:
#filter selected bones
if obj.mode == 'POSE': #apply only to selected bones
@@ -1059,10 +1070,8 @@ def add_inbetween_worldmatrix(self, context):
if action is None:
self['inbetween_worldmatrix'] = 0.0
return
# frame = round(context.scene.frame_current, 2)
if not self.inbetween_worldmatrix:
# scene.frame_set(frame)
return
if obj.mode == 'POSE' and not context.selected_pose_bones:
@@ -1073,37 +1082,38 @@ def add_inbetween_worldmatrix(self, context):
def get_matrix_other(self, context):
'''Get the matrix from the other frame, either previous or the next'''
#get all the list of the frames from the different channels for each bone
#rest of the code is only for posebones
#creating a new dict to avoid frame_set at the same frame multiple times. It will get the matrix of multiple bones from each frame
#This is done just to avoid extra scene evaluation using frame_set
#adding the bone to the new flipped dict key using the frame number
scene = context.scene
prev_frame_bones = dict()
next_frame_bones = dict()
for item, frames in self.items_frames.items():
#get the next and previous frames
next_frames = [frame for frame in frames if frame > self.frame]
if next_frames:
next_frame = next_frames[0]
next_frame = next_frames[0]
#the bones that are on the next frame
if next_frame in next_frame_bones:
next_frame_bones[next_frame].append(item)
else:
next_frame_bones.update({next_frame : [item]})
prev_frames = [frame for frame in frames if frame < self.frame]
if prev_frames:
prev_frame = prev_frames[-1]
if not prev_frame and not next_frame:
prev_frame = prev_frames[-1]
if prev_frame in prev_frame_bones:
prev_frame_bones[prev_frame].append(item)
else:
prev_frame_bones.update({prev_frame : [item]})
if not prev_frames and not next_frames:
continue
#get all the list of the frames from the different channels for each bone
#rest of the code is only for posebones
#creating a new dict to avoid frame_set at the same frame multiple times. It will get the matrix of multiple bones from each frame
#This is done just to avoid extra scene evaluation using frame_set
#adding the bone to the new flipped dict key using the frame number
prev_frame_bones = dict()
if prev_frame in prev_frame_bones:
prev_frame_bones[prev_frame].append(item)
else:
prev_frame_bones.update({prev_frame : [item]})
#the bones that are on the next frame
next_frame_bones = dict()
if next_frame in next_frame_bones:
next_frame_bones[next_frame].append(item)
else:
next_frame_bones.update({next_frame : [item]})
#if item.id_data.mode == 'POSE':
@@ -1154,7 +1164,7 @@ class InbetweenWorldMatrix(bpy.types.Operator):
bone = None
fcurves = get_fcurves_channelbag(obj, action)
fcurves = get_fcurves(obj, action)
for fcu in fcurves:
#filter selected bones
if obj.mode == 'POSE': #apply only to selected bones
@@ -1188,7 +1198,6 @@ class InbetweenWorldMatrix(bpy.types.Operator):
self.items_frames.update({bone : frames})
get_matrix_other(self, context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
@@ -1205,6 +1214,20 @@ class InbetweenWorldMatrix(bpy.types.Operator):
#modal is being cancelled because of undo issue with the modal running through the property
return {'FINISHED'}
if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'RELEASE': # Cancel
# Revert to the original matrix
for bone, matrix in self.items_matrix_org.items():
if bone.id_data.mode == 'POSE':
bone.matrix = matrix
else:
bone.matrix_world = matrix
# if scene.tool_settings.use_keyframe_insert_auto:
# add_keyframe(bone)
context.window_manager.atb_ui['is_dragging'] = False
self.atb['inbetween_worldmatrix'] = 0
return {'CANCELLED'}
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
self.stop = True
@@ -1227,8 +1250,8 @@ class InbetweenWorldMatrix(bpy.types.Operator):
else:
bone.matrix_world = matrix
if scene.tool_settings.use_keyframe_insert_auto:
add_keyframe(bone)
# if scene.tool_settings.use_keyframe_insert_auto:
# add_keyframe(bone)
return {'PASS_THROUGH'}
@@ -1296,18 +1319,19 @@ class BlendToMirroModal(bpy.types.Operator):
if self.stop:
context.window_manager.atb_ui['is_dragging'] = False
for bone in context.selected_pose_bones:
if scene.tool_settings.use_keyframe_insert_auto:
if scene.tool_settings.use_keyframe_insert_auto and self.finish == {'FINISHED'}:
add_keyframe(bone)
if 'matrix' in bone:
del bone['matrix']
self.ui['blend_mirror'] = 0
redraw_areas(['PROPERTIES'])
#modal is being cancelled because of undo issue with the modal running through the property
return {'FINISHED'}
return self.finish
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
self.stop = True
self.finish = {'CANCELLED'} if event.type in {'ESC', 'RIGHTMOUSE'} else {'FINISHED'}
for bone in context.selected_pose_bones:
mirror_bone = find_mirror_bone(bone)
if mirror_bone is None:
@@ -1315,7 +1339,10 @@ class BlendToMirroModal(bpy.types.Operator):
mirror_bone_matrix = mirror_bone.matrix_basis.copy()
mirror_plane = Matrix.Scale(-1, 4, (1, 0, 0))
mirrored_matrix = mirror_plane @ mirror_bone_matrix @ mirror_plane
matrix = Matrix(bone['matrix']).lerp(mirrored_matrix, self.ui.blend_mirror)
bone_matrix = bone['matrix'] if bpy.app.version < (5, 0, 0) else np.array(bone['matrix']).reshape(4, 4).T
matrix = Matrix(bone_matrix).lerp(mirrored_matrix, self.ui.blend_mirror)
matrix = filter_matrix_properties(context, bone.matrix_basis, matrix)
bone.matrix_basis = matrix
@@ -1357,7 +1384,7 @@ class SelectKeyframesOffset(bpy.types.Operator):
if 'keyframes_offset' in bone:
#check if there are bones with an offset
posemode = True
obj.data.bones[bone.name].select = True
TempCtrls.posebone_select(bone, True)
if posemode and obj.mode != 'POSE':
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode = 'POSE')
@@ -1365,6 +1392,25 @@ class SelectKeyframesOffset(bpy.types.Operator):
return {'FINISHED'}
def get_frame_range(context, obj):
def selected_frame_range(frame_range):
smartframes = set()
if obj.mode == 'POSE':
bones_paths = [bone.path_from_id() for bone in context.selected_pose_bones if bone.name in obj.pose.bones]
fcurves = get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
path = fcu.data_path.split('"].')[0] + '"]'
if obj.mode == 'POSE' and path not in bones_paths:
continue
for keyframe in fcu.keyframe_points:
smartframes.add(round(keyframe.co[0], 2))
if keyframe.select_control_point:
frame_range.append(round(keyframe.co[0], 2))
return smartframes
#get the frame range
frame_range = []
inbetweens = []
@@ -1377,27 +1423,21 @@ def get_frame_range(context, obj):
if atb.bake_frame_start > atb.bake_frame_end:
atb.bake_frame_start = atb.bake_frame_end
frame_range = list(range(atb.bake_frame_start, atb.bake_frame_end+1))
elif atb.range_type == 'SELECTED':
if obj.mode == 'POSE':
bones_paths = [bone.path_from_id() for bone in context.selected_pose_bones]
if obj.animation_data is None:
return frame_range
if obj.animation_data.action is None:
return frame_range
smartframes = set()
elif obj.animation_data is None:
return frame_range, inbetweens
elif obj.animation_data.action is None:
return frame_range, inbetweens
elif atb.range_type == 'SELECTED_RANGE':
# Create a list of a keyframes between the first selected keyframes and the last selected one
selected_frame_range(frame_range)
if frame_range:
frame_range = list(range(int(min(frame_range)), int(max(frame_range))+1))
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
for fcu in fcurves:
path = fcu.data_path.split('"].')[0] + '"]'
if obj.mode == 'POSE' and path not in bones_paths:
continue
for keyframe in fcu.keyframe_points:
smartframes.add(round(keyframe.co[0], 2))
if keyframe.select_control_point:
frame_range.append(round(keyframe.co[0], 2))
elif atb.range_type == 'SELECTED':
smartframes = selected_frame_range(frame_range)
smartframes = sorted(smartframes)
frame_range = sorted(set(frame_range))
@@ -1422,6 +1462,7 @@ def get_frame_range(context, obj):
def notification_invoke(self, context, text = 'Copy Matrix', size = 20.0):
'''get ready and lauch the notification'''
wm = context.window_manager
self.timer = wm.event_timer_add(0.1, window=context.window)
self.no_timer = False
@@ -1432,16 +1473,21 @@ def notification_invoke(self, context, text = 'Copy Matrix', size = 20.0):
self.fade_out_start = 0
self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(draw_text_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
redraw_areas(['VIEW_3D'])
def update_notification(self, context, event, esc = False, fade_out = True):
'''notify with a modal operator that the matrix was being copied'''
if not hasattr(self, 'draw_handle'):
return {'FINISHED'}
#Quit from the notification
if event.type in {'ESC'} or esc:
bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, 'WINDOW')
context.area.tag_redraw()
if context.area is not None:
context.area.tag_redraw()
elif hasattr(self, 'area'):
self.area.tag_redraw()
context.window_manager.event_timer_remove(self.timer)
del self.draw_handle
return {'FINISHED'}
@@ -1470,6 +1516,14 @@ def update_notification(self, context, event, esc = False, fade_out = True):
return {'PASS_THROUGH'}
def compatible_rotation(bone, bones_prevrot):
'''Make sure the bone is not flipping and is compatible with previous frame rotation'''
rotation_channel = rot_mode_to_channel(bone.rotation_mode)
if bone in bones_prevrot:
rot = getattr(bone, rotation_channel)
rot.make_compatible(bones_prevrot[bone])
bones_prevrot[bone] = getattr(bone, rotation_channel).copy()
class CopyMatrix(bpy.types.Operator):
"""Copy the matrix of the selection and store it"""
bl_idname = "anim.copy_matrix"
@@ -1546,6 +1600,7 @@ class PasteMatrix(bpy.types.Operator):
frame_current_final = scene.frame_current_final
bones_matrices = dict()
bones_prevrot = dict()
constrained = set()
#constraint evaluation is
# con.target.matrix_world.copy() @ con.inverse_matrix @ basis
@@ -1564,6 +1619,11 @@ class PasteMatrix(bpy.types.Operator):
for obj in context.selected_objects:
frame_range, inbetweens = get_frame_range(context, obj)
fcu_inbetweens = dict()
# Checking for locked fcurves before iterating
locked_fcus = get_locked_fcus(obj)
for frame in sorted(frame_range + inbetweens):
scene.frame_set(int(frame), subframe = frame % 1)
if obj.mode == 'POSE':
@@ -1588,7 +1648,7 @@ class PasteMatrix(bpy.types.Operator):
#adding the keyframes
for bone in bones_matrices.keys():
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens)
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
else:
target = obj
@@ -1603,10 +1663,13 @@ class PasteMatrix(bpy.types.Operator):
matrix_copied = reverse_constraint_offset(target.matrix_world, matrix_copied)
target.matrix_world = filter_matrix_properties(context, target.matrix_world, matrix_copied)
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens)
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
if inbetweens and len(frame_range) > 1:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
if len(frame_range) > 1:
if inbetweens:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
else:
set_auto_handles(fcu_inbetweens.keys(), frame_range)
if context.scene.frame_current_final != frame_current_final:
scene.frame_current = int(frame_current_final)
@@ -1678,7 +1741,8 @@ class CopyRelativeMatrix(bpy.types.Operator):
if obj.type == 'ARMATURE':
#Get the distance from the selected bones
for bone_relative in obj.pose.bones:
if not bone_relative.bone.select:
selected = bone_relative.bone.select if bpy.app.version < (5, 0, 0) else bone_relative.select
if not selected:
continue
if bone_relative == source_active:
continue
@@ -1704,14 +1768,18 @@ class CopyRelativeMatrix(bpy.types.Operator):
return {'FINISHED'}
def paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens):
def paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot):
#Make sure the rotation is compatible with the previous rotation
compatible_rotation(target, bones_prevrot)
#if autokey is turned on then add a keyframe
if (scene.tool_settings.use_keyframe_insert_auto or scene.animtoolbox.range_type != 'CURRENT') and 'target' in locals():
if frame not in inbetweens:
add_keyframe(target)
add_keyframe(target, locked_fcus)
#store the inbetween values
elif len(frame_range) > 1:
fcu_inbetweens = get_fcu_inbetweens(target, frame, fcu_inbetweens)
if len(frame_range) > 1:
fcu_inbetweens = get_fcu_inbetweens(target, frame, fcu_inbetweens, inbetweens)
def reverse_bone_constraints(context, bone, matrix_copied):
@@ -1865,6 +1933,8 @@ class PasteRelativeMatrix(bpy.types.Operator):
source_bone = None
source_obj = None
bones_matrices = dict()
# Store previous rotations to be compared and avoid flips
bones_prevrot = dict()
constrained = set()
#get the source matrix
if 'source_rig_name' in globals():
@@ -1890,6 +1960,8 @@ class PasteRelativeMatrix(bpy.types.Operator):
frame_range, inbetweens = get_frame_range(context, obj)
fcu_inbetweens = dict()
# Checking for locked fcurves before iterating
locked_fcus = get_locked_fcus(obj)
for frame in sorted(frame_range+inbetweens):
scene.frame_set(int(frame))
@@ -1925,7 +1997,7 @@ class PasteRelativeMatrix(bpy.types.Operator):
paste_bones_matrices(bones_matrices, constrained)
for bone in bones_matrices.keys():
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens)
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
else:
target = obj
@@ -1950,11 +2022,14 @@ class PasteRelativeMatrix(bpy.types.Operator):
matrix_new = reverse_constraint_offset(target.matrix_world, matrix_new)
target.matrix_world = filter_matrix_properties(context, target.matrix_world, matrix_new)
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens)
if inbetweens and len(frame_range) > 1:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
if len(frame_range) > 1:
if inbetweens:
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
else:
set_auto_handles(fcu_inbetweens.keys(), frame_range)
#return to the original frame in case of using frame range
if context.scene.frame_current != frame_current:
scene.frame_current = frame_current
@@ -2005,8 +2080,7 @@ def sharekeys_add_missing_fcurves(obj, attr_index, fcurves):
action = obj.id_data.animation_data.action
#Get the container, either action of channelbag because of adding groups
fcurves_container = get_fcurves_container(obj, action)
channelbag = get_channelbag(obj.id_data, action)
if 'rotation' in attr:
#converting the rotation depending on the rotation mode
mode = 'euler' if len(obj.rotation_mode) == 3 else obj.rotation_mode.lower()
@@ -2019,14 +2093,14 @@ def sharekeys_add_missing_fcurves(obj, attr_index, fcurves):
continue
elif rot != 'rotation_euler' and (path, 3) not in datapaths_arrays:
#adding an extra curve for quaternion or axis_angle
extra_fcu = fcurves_container.fcurves.new(data_path = path, index = 3, action_group = group)
extra_fcu = channelbag.fcurves.new(data_path = path, index = 3, action_group = group)
fcurves.append(extra_fcu)
datapaths_arrays.add((extra_fcu.data_path, extra_fcu.array_index))
if (path, index) in datapaths_arrays:
continue
fcu = fcurves_container.fcurves.new(data_path = path, index = index)
fcu = channelbag.fcurves.new(data_path = path, index = index)
add_group_to_fcurve(obj.id_data, fcu, group)
fcurves.append(fcu)
datapaths_arrays.add((fcu.data_path, fcu.array_index))
@@ -2102,7 +2176,7 @@ class ShareKeys(bpy.types.Operator):
def get_fcurves_set(self, obj_actions):
fcurves = set()
for obj, action in obj_actions.items():
fcurves = fcurves.union(set(get_fcurves_channelbag(obj, action)))
fcurves = fcurves.union(set(get_fcurves(obj, action)))
return fcurves
def add_get_action(self, obj):
@@ -2141,7 +2215,7 @@ class ShareKeys(bpy.types.Operator):
continue
#if there is no animation data or an action then create it
action = self.add_get_action(obj)
fcurves = get_fcurves_channelbag(obj, action)
fcurves = get_fcurves(obj, action)
frames, attr_index_obj = get_fcurves_frames([obj], fcurves, all_fcurves, frames, attr_index_obj)
if obj.data.animation_data is None:
@@ -2150,7 +2224,7 @@ class ShareKeys(bpy.types.Operator):
continue
#add the type of object if the data is also animated
actions_data_types.add(obj.type)
fcurves = get_fcurves_channelbag(obj.data, obj.data.animation_data.action)
fcurves = get_fcurves(obj.data, obj.data.animation_data.action)
frames, attr_index_obj = get_fcurves_frames([obj], fcurves, all_fcurves, frames, attr_index_obj)
#Getting all the available transforms and array index
@@ -2333,6 +2407,15 @@ def add_inbetweens(smartframes):
# all_frames = sorted(self.frames + self.inbetweens)
return inbetweens
def set_auto_handles(fcurves, frame_range):
for fcu in fcurves:
for keyframe in fcu.keyframe_points:
if keyframe.co[0] in frame_range:
keyframe.handle_right_type = 'AUTO'
keyframe.handle_left_type = 'AUTO'
fcu.update()
def add_interpolations(fcurves, fcu_inbetweens, frames = None):
inbetweens = sorted(set([frame for inbetweens in fcu_inbetweens.values() for frame in inbetweens.keys()]))
@@ -2419,7 +2502,7 @@ class FindRotationMode(bpy.types.Operator):
rot_mode = posebone.rotation_mode
obj = posebone.id_data
# fcurves = obj.animation_data.action.fcurves
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = get_fcurves(obj, obj.animation_data.action)
new_rot_eulers = []
#get the values from the original rotation
@@ -2533,7 +2616,7 @@ class ConvertRotationMode(bpy.types.Operator):
def switch_rot_mode_keyframes(self, obj, posebone):
# Switching any rotation mode keyframes to the new rotation mode value using to_rot_mode_index
bone_path = posebone.path_from_id() + '.' if type(posebone) == bpy.types.PoseBone else ''
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = get_fcurves(obj, obj.animation_data.action)
fcu_rotation_mode = fcurves.find(data_path = bone_path + 'rotation_mode', index = 0)
if fcu_rotation_mode:
for keyframe in fcu_rotation_mode.keyframe_points:
@@ -2556,7 +2639,7 @@ class ConvertRotationMode(bpy.types.Operator):
obj = posebone.id_data
# fcurves = obj.animation_data.action.fcurves
action = obj.animation_data.action
fcurves = get_fcurves_channelbag(obj, action)
fcurves = get_fcurves(obj, action)
fcu_inbetweens = dict()
fcu_keyframes = dict()
#get the transform from the original rot mode
@@ -2858,6 +2941,10 @@ def get_obj_slot(obj, action):
if obj in slot.users():
return slot
# If no slot with the users was found then get the next available slot
if obj.animation_data.action_suitable_slots:
return next(iter(obj.animation_data.action_suitable_slots))
return None
def get_all_fcurves(action):
@@ -2868,21 +2955,48 @@ def get_all_fcurves(action):
for strip in layer.strips:
for channelbag in strip.channelbags:
yield from channelbag.fcurves
def get_fcurves(obj, action: bpy.types.Action):
if hasattr(action, 'layers'):
channelbag = get_channelbag(obj, action)
return channelbag.fcurves
# action.fcurves not available anymore from Blender 5.0
if hasattr(action, 'fcurves'):
return action.fcurves
def get_fcurves_channelbag(obj, action: bpy.types.Action):
return []
def get_channelbag(obj, action: bpy.types.Action):
'''Getting the container of the fcurves, either the action or channelbag
Using this when adding a new group to the action'''
if hasattr(action, 'layers'):
slot = get_obj_slot(obj, action)
if not slot:
return action.fcurves
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
if channelbag:
return channelbag.fcurves
return action.fcurves
channelbag = None
if slot:
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
else:
# If a signed slot was not found then add a new one
slot = add_action_slot(obj, action)
obj.animation_data.action_slot = slot
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
if channelbag is None:
# action_ensure_channelbag_for_slot works only from Blender 5
if hasattr(anim_utils, 'action_ensure_channelbag_for_slot'):
channelbag = anim_utils.action_ensure_channelbag_for_slot(action, slot)
else:
channelbag = add_channelbag(obj, action)
return channelbag
else:
return action
def add_channelbag(obj, action):
'''Old might need to remove'''
if not hasattr(action, 'layers'):
return
slot = get_obj_slot(obj, action)
@@ -2904,26 +3018,12 @@ def add_channelbag(obj, action):
return channelbag
def get_fcurves_container(obj: bpy.types.Object, action: bpy.types.Action):
'''Getting the container of the fcurves, either the action or channelbag
Using this when adding a new group to the action'''
if hasattr(action, 'layers'):
slot = get_obj_slot(obj, action)
if not slot:
return action
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
return channelbag
else:
return action
def add_group_to_fcurve(obj, fcu, groupname):
'''Add an fcurve group based on the fcurve container, either action or channelbag'''
action = fcu.id_data
#get the container which is either a channelbag or a group
fcu_container = get_fcurves_container(obj, action)
fcu_container = get_channelbag(obj, action)
group = fcu_container.groups.get(groupname)
if group is None:
group = fcu_container.groups.new(groupname)