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
+3 -1
View File
@@ -18,6 +18,7 @@
# ***** END GPL LICENCE BLOCK *****
import bpy
from . import Tools
class GizmoSizeUp(bpy.types.Operator):
"""Share keyframes between all the selected objects and bones"""
@@ -144,7 +145,8 @@ class SwitchBoneCollectionsVisibility(bpy.types.Operator):
start = 'pose.bones["'
end = '"]'
#get all the animated bones from the fcurves
for fcu in obj.animation_data.action.fcurves:
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
start_index = fcu.data_path.find(start)
end_index = fcu.data_path.find(end)
#if it's not a posebone fcurve then skip
File diff suppressed because it is too large Load Diff
+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)
+46 -5
View File
@@ -20,7 +20,7 @@
bl_info = {
"name": "AnimToolBox",
"author": "Tal Hershkovich",
"version" : (0, 0, 8),
"version" : (0, 2, 3),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "A set of animation tools",
@@ -119,6 +119,11 @@ class TempCtrlsSceneSettings(bpy.types.PropertyGroup):
add_ik_ctrl: bpy.props.BoolProperty(name = 'Add an Extra IK Ctrl Bone', description = "Adds an extra bone ctrl as the ik ctrl", default = False, update = TempCtrls.add_ik_prop)
pole_target: bpy.props.BoolProperty(name = 'Add Pole Target', description = "Adding Pole Target to the IK Chain", default = True, update = TempCtrls.pole_prop)
pole_offset: bpy.props.FloatProperty(name="Offset", description="Offset the bone in the axis direction", default=1.0, update = TempCtrls.pole_offset)
pole_axis: bpy.props.EnumProperty(name = 'Axis Direction', description="Which direction should the pole bone point", default = 4, update = TempCtrls.update_axis_prop,
items = [('+X', '+X','Target Bone the +X Axis', 0), ('+Y', '+Y', 'Target Bone the +Y Axis', 1),
('-X', '-X','Target Bone the -X Axis', 2), ('-Y', '-Y', 'Target Bone the -Y Axis', 3),
('AUTO', 'Auto','Select pole bone direction based on the current pose', 4)])
child: bpy.props.BoolProperty(name = 'Add extra child Ctrls', description = "Add an child control for an overlay control", default = False, update = TempCtrls.child_prop)
orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = True)
@@ -252,8 +257,9 @@ class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
#Copy/Pase Matrix
range_type: bpy.props.EnumProperty(name = 'Paste to Frames', description="Paste to current frame or a range of frames.", update = Tools.bake_range_type,
items = [('CURRENT', 'Current Frame','Paste Matrix to only current frame', 0),
('SELECTED', 'Selected Keyframe','Paste Matrix to only selected keyframes', 1),
('RANGE', 'Frame Range','Paste Matrix to a Frame Range', 2)])
('SELECTED', 'Selected Keyframes','Paste Matrix only to the selected keyframes', 1),
('RANGE', 'Frame Range','Paste Matrix to a Custom Frame Range', 2),
('SELECTED_RANGE', 'Selected Keyframes Range','Paste Matrix to every frame within a selected keyframe range', 3)])
bake_frame_start: bpy.props.IntProperty(name = "Bake Frame Start", description = "Define the start frame to paste the matrix", min = 0, update = Tools.bake_frame_start_limit)
bake_frame_end: bpy.props.IntProperty(name = "Bake Frame End", description = "Define the end frame to paste the matrix", min = 0, update = Tools.bake_frame_end_limit)
@@ -298,6 +304,11 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
# Motion Path brush colors
mp_brush_disabled: bpy.props.FloatVectorProperty(name="Disabled", subtype='COLOR', default=(0.7, 0.7, 0.9, 0.75), size=4, min=0.0, max=1.0, description="Brush is disabled and have no influence")
mp_brush_hover: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(0.8, 0.7, 0.2, 0.8), size=4, min=0.0, max=1.0, description="Brush is hovering and can be activated")
mp_brush_active: bpy.props.FloatVectorProperty(name="Active", subtype='COLOR', default=(1.0, 1.0, 1.0, 0.9), size=4, min=0.0, max=1.0, description="Brush is activated")
# addon updater preferences from `__init__`, be sure to copy all of them
auto_check_update: bpy.props.BoolProperty(
name = "Auto-check for Update",
@@ -371,6 +382,12 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
row.prop(self, 'mp_hover_color')
row.prop(self, 'mp_key_selection_color')
row.prop(self, 'mp_handle_selection_color')
col = box.row()
col.label(text = 'Brush Colors')
row = box.row()
row.prop(self, 'mp_brush_disabled')
row.prop(self, 'mp_brush_hover')
row.prop(self, 'mp_brush_active')
layout.separator()
col = layout.column()
@@ -392,6 +409,8 @@ def loadanimtoolbox_pre(self, context):
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
emp.remove_draw_circle()
bpy.context.scene.emp.selected_keyframes = '{}'
if 'markers_retimer_dh' in dns:
@@ -401,20 +420,38 @@ def loadanimtoolbox_pre(self, context):
#remove the motion path app handler if it's still inside
if emp.mp_value_update in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(emp.mp_value_update)
if emp.mp_frame_change in bpy.app.handlers.frame_change_post:
bpy.app.handlers.frame_change_post.remove(emp.mp_frame_change)
if emp.mp_undo_update in bpy.app.handlers.undo_pre:
bpy.app.handlers.undo_pre.remove(emp.mp_undo_update)
@persistent
def loadanimtoolbox_post(self, context):
scene = bpy.context.scene
def remove_markers(prop, marker_type, names):
'''Remove Frame Range or Bake Range Markers if they were turned on'''
if not getattr(prop, marker_type):
return
setattr(prop, marker_type, False)
for marker_name in names:
if marker_name in scene.timeline_markers:
scene.timeline_markers.remove(scene.timeline_markers[marker_name])
dns = bpy.app.driver_namespace
if scene.animtoolbox.isolate_pose_mode:
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
if scene.emp.motion_path:
# In case motion path was active in the last save, make sure atb mp name property
# Is not included inside the bones, for a proper cleanup
for obj in bpy.context.view_layer.objects:
if "atb_mp_name" in obj:
del obj["atb_mp_name"]
if obj.type == 'ARMATURE':
for pbone in obj.pose.bones:
if "atb_mp_name" in pbone:
del pbone["atb_mp_name"]
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
@@ -424,6 +461,10 @@ def loadanimtoolbox_post(self, context):
# Because it is used for undo
bpy.context.scene.emp.selected_keyframes = '{}'
remove_markers(scene.animtoolbox, 'marker_frame_range', {'Frame Start', 'Frame End'})
remove_markers(scene.animtoolbox, 'bake_frame_range', {'Bake Start', 'Bake End'})
remove_markers(scene.emp, 'marker_frame_range', {'MotionPath Start', 'MotionPath End'})
Tools.selection_order(self, context)
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
@@ -1,6 +1,6 @@
{
"last_check": "2025-09-23 10:58:29.201165",
"backup_date": "September-23-2025",
"last_check": "2026-02-09 15:54:54.419194",
"backup_date": "February-9-2026",
"update_ready": false,
"ignore": false,
"just_restored": false,
File diff suppressed because it is too large Load Diff
@@ -1803,7 +1803,7 @@ def reorder_bones_matrices(bones_matrices, constrained):
return re_bones_matrices
def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
def paste_bone_matrix(bone, matrix_copied, constrained, bones = {}, x_filter = True):
#running again separatly in case the bones are in a hierarchy and influencing each other
# for bone, matrix_copied in bones_matrices.items():
# Determine whether to use bone.matrix or bone.matrix_world
@@ -1818,10 +1818,14 @@ def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
if x_filter : matrix_copied = filter_matrix_properties(context, getattr(bone, matrix_attr), matrix_copied)
# bone.matrix = bone.id_data.matrix_world.inverted() @ matrix_copied
setattr(bone, matrix_attr, matrix_copied) # bone.id_data.matrix_world.inverted() @
children = set(bone.children_recursive).intersection(bones)
if children or bone in constrained:
# print(f'found children {[child.name for child in children]} in bone {bone.name}' )
context.view_layer.update()
#Check if the bone has constrainsts on it that need extra iteration
if bone not in constrained:
# filter_matrix_properties(context, bone.matrix, matrix_copied)
return
context.view_layer.update()
matrix_copied = reverse_bone_constraints(context, bone, matrix_copied)
if x_filter : matrix_copied = filter_matrix_properties(context, bone.matrix, matrix_copied)
@@ -1832,8 +1836,12 @@ def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
def paste_bones_matrices(bones_matrices, constrained, x_filter = True):
#running again separatly in case the bones are in a hierarchy and influencing each other
pasted_bones = set()
for bone, matrix_copied in bones_matrices.items():
paste_bone_matrix(bone, matrix_copied, constrained, x_filter)
#Get the rest of the bones to check if they are children of the current bone
bones = set(bones_matrices.keys()).difference(pasted_bones)
paste_bone_matrix(bone, matrix_copied, constrained, bones, x_filter)
pasted_bones.add(bone)
class PasteRelativeMatrix(bpy.types.Operator):
"""paste the relative matrix of the selection"""
@@ -2522,7 +2530,15 @@ class ConvertRotationMode(bpy.types.Operator):
# @classmethod
# def poll(cls, context):
# return context.object.type == 'ARMATURE'
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)
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:
keyframe.co[1] = self.to_rot_mode_index
def execute(self, context):
scene = context.scene
selected_bones = context.selected_pose_bones
@@ -2530,7 +2546,8 @@ class ConvertRotationMode(bpy.types.Operator):
to_rot_mode = scene.animtoolbox.rotation_mode
to_rot_mode_fcu = rot_mode_to_channel(to_rot_mode)
#Getting the index of the rotation mode we want to convert to
self.to_rot_mode_index = list(scene.animtoolbox.bl_rna.properties['rotation_mode'].enum_items.keys()).index(to_rot_mode)
#get the keyframes from the bones
for posebone in selected_bones:
@@ -2548,16 +2565,21 @@ class ConvertRotationMode(bpy.types.Operator):
keyframes = emp.get_bone_keyframes(posebone, transform)
#get all interpolations and handle types of the keyframes
handle_types = emp.get_bone_keyframes(posebone, transform, property = 'interpolation')
interpolations = handle_types[::3]
handle_left_type = handle_types[1::3]
handle_right_type = handle_types[2::3]
# rotation_mode_keyframes = emp.get_bone_keyframes(posebone, 'rotation_mode')
self.switch_rot_mode_keyframes(obj, posebone)
inbetweens = []
smartframes = sorted(set(map(lambda x: round(x, 2), keyframes[::2])))
inbetweens = add_inbetweens(smartframes)
all_frames = sorted(smartframes + inbetweens)
#get all interpolations and handle types of the keyframes
if len(smartframes) > 1:
handle_types = emp.get_bone_keyframes(posebone, transform, property = 'interpolation')
interpolations = handle_types[::3]
handle_left_type = handle_types[1::3]
handle_right_type = handle_types[2::3]
inbetweens = add_inbetweens(smartframes)
all_frames = sorted(smartframes + inbetweens)
new_path = posebone.path_from_id() + '.' + to_rot_mode_fcu
#define array length
@@ -2632,13 +2654,15 @@ class ConvertRotationMode(bpy.types.Operator):
keyframe = new_fcu.keyframe_points[-1]
keyframe.co = (frame, fcu_keyframes[new_fcu][frame])
keyframe.interpolation = interpolations[frame_index]
keyframe.handle_left_type = handle_left_type[frame_index]
keyframe.handle_right_type = handle_right_type[frame_index]
if inbetweens:
keyframe.interpolation = interpolations[frame_index]
keyframe.handle_left_type = handle_left_type[frame_index]
keyframe.handle_right_type = handle_right_type[frame_index]
new_fcu.update()
add_interpolations(new_fcurves, fcu_inbetweens)
if inbetweens:
add_interpolations(new_fcurves, fcu_inbetweens)
posebone.rotation_mode = to_rot_mode
@@ -20,7 +20,7 @@
bl_info = {
"name": "AnimToolBox",
"author": "Tal Hershkovich",
"version" : (0, 0, 7, 3),
"version" : (0, 0, 8),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "A set of animation tools",
@@ -133,15 +133,16 @@ class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 5),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 6),
('ROOT', 'Root', 'Root Ctrl for all the setups', 7),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10)])
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
#using org mostly to decide if it needs a custom shape
org: bpy.props.EnumProperty(name = 'Org Type', description="Describes what if the function of the bone", override = {'LIBRARY_OVERRIDABLE'},
@@ -152,21 +153,29 @@ class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
shape: bpy.props.BoolProperty(name = "Apply shape", description = "Mark if the bone needs a shape", default = False, override = {'LIBRARY_OVERRIDABLE'})
class TempCtrlsOrgIds(bpy.types.PropertyGroup):
# A collection of the org ids used in each setup. Org ID is the direct connection
# between original bones and ctrls
pass
class TempCtrlsObjectSetups(bpy.types.PropertyGroup):
#located at obj.animtoolbox.ctrl_setups
#name: using string of the id
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of",
items = [('NONE', 'No Setup','No Setup Applied', 0),
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 5),
('ROOT', 'Root', 'Root Ctrl for all the setups', 6),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10),
])
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
org_ids: bpy.props.CollectionProperty(type = TempCtrlsOrgIds)
class MultikeyProperties(bpy.types.PropertyGroup):
@@ -238,37 +247,6 @@ class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
#Blendings
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = -1, soft_max = 1, default=0.0, options = set(), update = Tools.add_inbetween_key, override = {'LIBRARY_OVERRIDABLE'})
motion_path: bpy.props.BoolProperty(name = "Motion Path", description = "Flag when Motion Path is on", default = False, override = {'LIBRARY_OVERRIDABLE'})
mp_settings: bpy.props.BoolProperty(name = "Motion Path Settings", description = "Open the settings Menu", default = False, override = {'LIBRARY_OVERRIDABLE'})
mp_keyframe_scale: bpy.props.FloatProperty(name = "Scale Selecgted Keyframes Bounding Box", description = "Change the scale of the bounding box around the selected keyframes ", default = 0.1, step = 0.1, precision = 3)
mp_color_before: bpy.props.FloatVectorProperty(name="Motion Path Before Color", subtype='COLOR', default=(1.0, 0.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
mp_color_after: bpy.props.FloatVectorProperty(name="Motion Path After Color", subtype='COLOR', default=(0.0, 1.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
mp_infront: bpy.props.BoolProperty(name = "Motion Path In Front", description = "Display motion path in front of all the objects", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_points: bpy.props.BoolProperty(name = "Motion Path Points", description = "Display motion path points", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_lines: bpy.props.BoolProperty(name = "Motion Path Lines", description = "Display motion path lines", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_handles: bpy.props.BoolProperty(name = "Motion Path Handles", description = "Display motion path handles on keyframe selection", default = True, override = {'LIBRARY_OVERRIDABLE'})
mp_display_frames: bpy.props.BoolProperty(name = "Frame Numbers", description = "Display frame numbers on all the keyframes", default = False, override = {'LIBRARY_OVERRIDABLE'})
mp_handle_types: bpy.props.EnumProperty(name = 'Set Keyframe Handle Type', description="Set handle type for selected keyframes", default = 'AUTO', update = emp.update_handle_type_prop,
items = [('FREE', 'Free', 'Free', 'HANDLE_FREE', 0),
('ALIGNED','Aligned', 'Aligned', 'HANDLE_ALIGNED', 1),
('VECTOR', 'Vector', 'Vector', 'HANDLE_VECTOR', 2),
('AUTO','Automatic', 'Automatic', 'HANDLE_AUTO', 3),
('AUTO_CLAMPED','Auto Clamped', 'Auto Clamped', 'HANDLE_AUTOCLAMPED', 4)])
mp_interpolation: bpy.props.EnumProperty(name = 'Set Interpolation', description="Set Keyframe Interpolation", default = 'BEZIER', update = emp.update_interpolation_prop,
items = [('BEZIER', 'Bezier', 'Bezier', 'IPO_BEZIER', 0),
('LINEAR','Linear', 'Linear', 'IPO_LINEAR', 1),
('CONSTANT', 'Constant', 'Constant', 'IPO_CONSTANT', 2)])
mp_frame_range: bpy.props.EnumProperty(name = 'Frame Range', description="Type of Frame Range", default = 'SCENE', #update = emp.mp_frame_range_update,
items = [('KEYS_ALL', 'All_Keys','Use the Scene Frame Length for the Range', 0),
('SCENE', 'Scene','Use the Scene Frame Length for the Range', 1),
('MANUAL', 'Manual','Custom Frame range using numerical input or the markers frame ranger', 2),
('AROUND', 'Around Frames','Show only around the current frame', 3)])
mp_before: bpy.props.IntProperty(name = "Before", description = "Show the frames Before the current frame", min = 0, default = 10)
mp_after: bpy.props.IntProperty(name = "After", description = "Show the frames After the current frame", min = 0, default = 10)
selected_keyframes: bpy.props.StringProperty(name="Selected Keyframes", description="Serialized representation of selected keyframes")
gizmo_size: bpy.props.IntProperty(name = "Add to Gizmo Size", description = "Addition to Gizmo Size", max = 100, min = -100, default = 10)
#Copy/Pase Matrix
@@ -312,6 +290,13 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
#Editable motion path
keyframes_range: bpy.props.IntProperty(name = "Keyframe Range", description = "The range of distance from the keyframes while hovering over them", min = 5, max = 100, default = 15)
mp_pref: bpy.props.BoolProperty(name = "Editable Motion Path Colors Theme", description = "Set the Color them of editable motion path visualization", default = False)
mp_keyframe_color: bpy.props.FloatVectorProperty(name="Keyframes", subtype='COLOR', default=(1.0, 1.0, 0.0, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_handle_color: bpy.props.FloatVectorProperty(name="Handles", subtype='COLOR', default=(1.0, 0.8, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_remove_color: bpy.props.FloatVectorProperty(name="Remove Keyframes", subtype='COLOR', default=(0.0, 0.5, 1.0, 1.0), size=4, min=0.0, max=1.0, description="Keyframe color displayed before removing")
mp_hover_color: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(1.0, 0.4, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Color during Hovering")
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
# addon updater preferences from `__init__`, be sure to copy all of them
auto_check_update: bpy.props.BoolProperty(
@@ -371,6 +356,22 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
row.prop(self, 'clear_setup')
row.prop(self, 'in_front')
layout.separator()
box = layout.box()
col = box.column()
col.prop(self, 'mp_pref', icon = 'DOWNARROW_HLT', text = 'Editable Motion Path Preferences')
if self.mp_pref:
col.prop(self, 'keyframes_range', text = 'Keyframe Distance Range')
col.label(text = 'Colors Theme')
row = box.row()
row.prop(self, 'mp_keyframe_color')
row.prop(self, 'mp_handle_color')
row.prop(self, 'mp_remove_color')
row = box.row()
row.prop(self, 'mp_hover_color')
row.prop(self, 'mp_key_selection_color')
row.prop(self, 'mp_handle_selection_color')
layout.separator()
col = layout.column()
col.label(text = 'Include Extras: ')
@@ -385,12 +386,14 @@ def loadanimtoolbox_pre(self, context):
if scene.animtoolbox.bake_frame_range:
scene.animtoolbox.bake_frame_range = False
if scene.animtoolbox.motion_path:
scene.animtoolbox.motion_path = False
if scene.emp.motion_path:
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
bpy.context.scene.emp.selected_keyframes = '{}'
if 'markers_retimer_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['markers_retimer_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('markers_retimer_dh')
@@ -411,16 +414,20 @@ def loadanimtoolbox_post(self, context):
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
if scene.animtoolbox.motion_path:
scene.animtoolbox.motion_path = False
if scene.emp.motion_path:
scene.emp.motion_path = False
bpy.context.workspace.status_text_set(None)
if 'mp_dh' in dns:
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
bpy.app.driver_namespace.pop('mp_dh')
# Reset keyframe selection for motion paths it is not used in window manager
# Because it is used for undo
bpy.context.scene.emp.selected_keyframes = '{}'
Tools.selection_order(self, context)
classes = (TempCtrlsItems, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties, IsolatedRigs,
AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
IsolatedRigs, AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
addon_keymaps = []
@@ -1,16 +1,16 @@
{
"last_check": "2025-09-23 10:57:14.918240",
"backup_date": "June-19-2025",
"last_check": "2026-02-09 15:54:54.419194",
"backup_date": "September-23-2025",
"update_ready": true,
"ignore": false,
"just_restored": false,
"just_updated": false,
"version_text": {
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=cb445f00491a54eb763f0d8e72eaae3e44e7d0ba",
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=7a9ad24a463bf1c8ae095ec853409979344e0738",
"version": [
0,
0,
8
2,
3
]
}
}
File diff suppressed because it is too large Load Diff
@@ -46,7 +46,7 @@ def attr_default(obj, fcu_key):
if attr not in obj.data.shape_keys.key_blocks:
return [0]
shapekey = obj.data.shape_keys.key_blocks[attr]
return 0 if shapekey.slider_min <= 0 else shapekey.slider_min
return [0] if shapekey.slider_min <= 0 else shapekey.slider_min
#in case of transforms in object mode
else:# fcu_key[0] in transform_types:
source = obj
@@ -273,6 +273,9 @@ def random_value(self, context):
for key in fcu.keyframe_points:
add_value(key, value * random.uniform(-threshold, threshold))
fcu.update()
self['randomness'] = 0.1
def evaluate_combine(data_path, added_array, eval_array, array_default, influence):
@@ -293,23 +296,17 @@ def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
'''Create an array from all the indexes'''
array_len = len(array_default)
fcu_array = []
#assigning the default array in case
fcu_array = array_default.copy()
#get the missing arrays in case quaternion is not complete
missing_arrays = []
for i in range(array_len):
fcu = fcurves.find(fcu_path, index = i)
if fcu is None:
missing_arrays.append(i)
continue
fcu_array[i] = fcu.evaluate(frame)
fcu_array.append(fcu.evaluate(frame))
#In case it's a quaternion and missing attributes, then adding from default value
if fcu_array and array_len == 4 and missing_arrays:
for i in missing_arrays:
fcu_array.insert(i, array_default[i])
if not len(fcu_array):
if (fcu_array == array_default).all():
return None
return np.array(fcu_array)
@@ -325,7 +322,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
fcu_path = fcu.data_path
eval_array = array_default
eval_array = array_default.copy()
for track in nla_tracks:
if track.mute:
@@ -373,6 +370,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
tweak_mode = anim_data.use_tweak_mode
if tweak_mode:
anim_data.use_tweak_mode = False
action = anim_data.action
if action:
influence = anim_data.action_influence
@@ -425,6 +423,7 @@ def evaluate_value(self, context):
for fcu in fcurves:
if fcu in fcu_paths:
continue
current_value = None
if Tools.filter_properties(context.scene.animtoolbox, fcu):
continue
if obj.mode == 'POSE':
@@ -447,10 +446,11 @@ def evaluate_value(self, context):
else:
transform = fcu.data_path
current_value = getattr(obj, transform)
#In case it was completly filtered out and not current value available
if not current_value:
continue
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
# array_default = np.array([attr_default(obj, (fcu.data_path, i)) for i in range(4)
# if fcurves.find(fcu.data_path, index = i) is not None])
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
if eval_array is None:
fcurves = Tools.get_fcurves_channelbag(obj, action)
@@ -63,6 +63,7 @@ class ANIMTOOLBOX_MT_Temp_Ctrls(bpy.types.Menu):
layout = self.layout
custom_icons = preview_collections["main"]
layout.operator('anim.bake_to_ctrl', text="World Space Ctrls", icon = 'WORLD')
# layout.operator('anim.worldspace_cursor', text="World Space Cursor Ctrls", icon ='ORIENTATION_CURSOR')
layout.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
layout.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
layout.separator()
@@ -163,7 +164,7 @@ class ANIMTOOLBOX_MT_operators(bpy.types.Menu):
layout.separator(factor = 0.5)
layout.operator('anim.switch_collections_visibility', icon = 'COLLECTION_COLOR_06', text = 'Animated Collections Visibilty')
layout.operator('anim.isolate_pose_mode', icon_value = custom_icons["isolate"].icon_id, depress = scene.animtoolbox.isolate_pose_mode)
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
layout.separator()
layout.prop(context.preferences.addons[__package__].preferences, 'quick_menu', text = 'Use Quick Icons Menu')
# layout.prop(context.window_manager.atb_ui, 'quick_menu', text = 'Use Quick Icons Menu')
@@ -257,7 +258,10 @@ class TEMPCTRLS_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
layout = self.layout
box = layout.box()
col = box.column()
col.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
row = col.row()
row.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
row.operator('anim.add_empty_ctrl', text="", icon ='EMPTY_AXIS')
# col.operator('anim.worldspace_cursor', text="WorldSpace Cursor Ctrls", icon ='ORIENTATION_CURSOR')
col.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
col.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
@@ -540,44 +544,65 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
box = layout.box()
row = box.row()
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
row.prop(scene.animtoolbox, 'mp_settings', text = '', icon = 'SETTINGS')
if scene.animtoolbox.mp_settings:
row = box.row()
row.prop(scene.animtoolbox, 'mp_color_before', text = '')
row.prop(scene.animtoolbox, 'mp_color_after', text = '')
row = box.row()
row.prop(scene.animtoolbox, 'mp_keyframe_scale', text = 'Scale Keyframes Box', icon = 'CUBE')
row = box.row()
row.prop(scene.animtoolbox, 'mp_points', text = 'Points')
row.prop(scene.animtoolbox, 'mp_lines', text = 'Lines')
row = box.row()
row.prop(scene.animtoolbox, 'mp_handles', text = 'Handles')
row.prop(scene.animtoolbox, 'mp_infront', text = 'In Front')
row = box.row()
row.prop(scene.animtoolbox, 'mp_display_frames', text = 'Display Keyframe Numbers')
row = box.row()
row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
if context.object is None:
return
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
emp = scene.emp
row.prop(scene.emp, 'settings', text = '', icon = 'SETTINGS')
if scene.emp.settings:
split = box.split(factor = 0.4)
split.label(text ='Frame Range')
# row.prop(scene.animtoolbox, 'mp_frame_range', text = '')
split.prop(context.scene.animtoolbox, 'mp_frame_range', text = '')
if context.scene.animtoolbox.mp_frame_range == 'MANUAL':
split.prop(emp, 'frame_range', text = '')
if emp.frame_range == 'MANUAL':
row = box.row()
row.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
row.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
elif context.scene.animtoolbox.mp_frame_range == 'AROUND':
row = box.row()
row.prop(context.scene.animtoolbox, 'mp_before', text = 'Before')
row.prop(context.scene.animtoolbox, 'mp_after', text = 'After')
row.prop(emp, 'frame_start', text = 'Start')
row.prop(emp, 'frame_end', text = 'End')
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
elif emp.frame_range == 'AROUND':
row = box.row()
row.prop(emp, 'before', text = 'Before')
row.prop(emp, 'after', text = 'After')
box.separator(factor = 0.1)
col = box.column()
col.prop(emp, 'display_size', icon = 'DOWNARROW_HLT')
if emp.display_size:
col.prop(emp, 'thickness', text = 'Line Thickness')
col.prop(emp, 'keyframe_size', text = 'Keyframes Size')
col.prop(emp, 'frame_size', text = 'Frames Size')
box.separator(factor = 0.1)
row = box.row()
row.label(text = 'Visualization Type')
row.prop(emp, 'vis_type', text = '')
if emp.vis_type == 'VELOCITY':
row = box.row()
row.prop(emp, 'velocity_factor', text = '')
row.prop(emp, 'clamp_min', text = '')
row.prop(emp, 'clamp_max', text = '')
row = box.row()
row.prop(emp, 'color_before', text = '')
row.prop(emp, 'color_after', text = '')
row = box.row()
row.prop(emp, 'points', text = 'Points')
row.prop(emp, 'lines', text = 'Lines')
row = box.row()
row.prop(emp, 'handles', text = 'Handles')
row.prop(emp, 'infront', text = 'In Front')
row = box.row()
row.prop(emp, 'display_frames', text = 'Display Keyframe Numbers')
row.separator()
row = box.row()
row.operator('anim.go_to_keyframe')
# row = box.row()
# row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
# if context.object is None:
# return
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
class RIGGERTOOLBOX_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
bl_label = "Rigger Toolbox"
@@ -697,4 +722,4 @@ def draw_menu(self, context):
layout.operator('anim.isolate_pose_mode', text = '', depress = scene.animtoolbox.isolate_pose_mode, icon_value = custom_icons["isolate"].icon_id)
layout.separator()
layout.operator('object.motion_path_operator', text = '', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
layout.operator('object.motion_path_operator', text = '', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -124,7 +124,9 @@ def add_value(key, value):
apply_handles(key, handle_r, handle_l)
#calculate the difference between current value and the fcurve value
def add_diff(fcurves, path, current_value, eval_array):
def add_diff(fcurves, path, current_value, eval_array):
if eval_array is None:
return
array_value = current_value - eval_array
if not any(array_value):
return
@@ -160,7 +162,7 @@ class ScaleValuesOp(bpy.types.Operator):
for obj in context.selected_objects:
if obj.animation_data.action is None:
continue
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
if obj.mode == 'POSE':
if selected_bones_filter(obj, fcu.data_path):
@@ -255,7 +257,7 @@ def random_value(self, context):
for obj in context.selected_objects:
if obj.animation_data.action is None:
continue
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
for fcu in fcurves:
if obj.mode == 'POSE':
if selected_bones_filter(obj, fcu.data_path):
@@ -306,8 +308,8 @@ def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
continue
fcu_array[i] = fcu.evaluate(frame)
if (fcu_array == array_default).all():
return None
# if (fcu_array == array_default).all():
# return None
return np.array(fcu_array)
def evaluate_layers(context, obj, anim_data, fcu, array_default):
@@ -363,7 +365,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
frame_eval = last_frame - (frame_eval - strip.frame_start)
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
frame_eval = strip.frame_start * 1/strip.scale + (frame_eval - strip.frame_start) * 1/strip.scale - offset * 1/strip.scale
fcurves = Tools.get_fcurves_channelbag(obj, action)
fcurves = Tools.get_fcurves(obj, action)
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame_eval, influence, array_default, blend_type, blend_types)
#Adding an extra layer from the action outside and on top of the nla
@@ -376,7 +378,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
influence = anim_data.action_influence
blend_type = anim_data.action_blend_type
fcurves = Tools.get_fcurves_channelbag(obj, action)
fcurves = Tools.get_fcurves(obj, action)
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence, array_default, blend_type, blend_types)
anim_data.use_tweak_mode = tweak_mode
@@ -419,7 +421,7 @@ def evaluate_value(self, context):
if obj.mode == 'POSE':
bonelist = context.selected_pose_bones if ui.multikey.selectedbones else obj.pose.bones
fcurves = Tools.get_fcurves_channelbag(obj, action)
fcurves = Tools.get_fcurves(obj, action)
for fcu in fcurves:
if fcu in fcu_paths:
continue
@@ -453,7 +455,7 @@ def evaluate_value(self, context):
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
if eval_array is None:
fcurves = Tools.get_fcurves_channelbag(obj, action)
# fcurves = Tools.get_fcurves(obj, action)
eval_array = evaluate_array(fcurves, fcu.data_path, context.scene.frame_current, array_default)
#calculate the difference between current value and the fcurve value
+27 -2
View File
@@ -558,7 +558,7 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
row = box.row()
row.prop(emp, 'frame_start', text = 'Start')
row.prop(emp, 'frame_end', text = 'End')
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
row.operator("anim.emp_markers_range", icon = 'MARKER', text ='', depress = scene.emp.marker_frame_range)
elif emp.frame_range == 'AROUND':
row = box.row()
@@ -596,8 +596,33 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
row = box.row()
row.prop(emp, 'display_frames', text = 'Display Keyframe Numbers')
row.separator()
box = layout.box()
split = box.split(factor = 0.35)
split.label(text = 'Channels')
split.prop(emp, 'channels', text ='')
row = box.row()
row.operator('anim.go_to_keyframe')
if bpy.app.version >= (4, 2, 0):
row.prop(emp, 'camera_space', icon = 'CON_CAMERASOLVER')
#row = box.row()
row.prop(emp, 'cursor_offset', icon = 'PIVOT_CURSOR')
box.separator(factor = 0.05)
row = box.row()
row.operator('anim.go_to_keyframe', icon = 'NEXT_KEYFRAME')
box.separator(factor = 0.05)
# split = box.split(factor = 0.75)
row = box.row()
row.prop(emp, 'select_circle', text = 'Selection Brush', icon = 'MESH_CIRCLE')
row = box.row()
prop_edit_icon = 'PROP_ON' if emp.prop_edit else 'PROP_OFF'
row.prop(emp, 'prop_edit', text = 'Proportional Editing', icon = prop_edit_icon)
row.prop(emp, 'smooth', text = 'Smooth', icon = 'MOD_SMOOTH')
row = box.row()
if emp.prop_edit:
row.prop(emp, 'prop_edit_falloff', text = '')
row.prop(emp, 'radius', text = '')
elif emp.smooth:
row.prop(emp, 'smooth_strength', text = '')
row.prop(emp, 'radius', text = '')
# row = box.row()
# row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
# if context.object is None: