2025-12-01

This commit is contained in:
2026-03-17 14:58:51 -06:00
parent 183e865f8b
commit 4b82b57113
6846 changed files with 954887 additions and 162606 deletions
@@ -0,0 +1,11 @@
from . import (
select_similar_curves,
lock_curves,
bake_anim_across_armatures,
)
modules = [
select_similar_curves,
lock_curves,
bake_anim_across_armatures,
]
@@ -0,0 +1,84 @@
# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
#
# SPDX-License-Identifier: GPL-3.0-or-later
from typing import List
import bpy
from bpy.types import Operator
def keyed_bones_names(action) -> List[str]:
"""Return a list of bone names that have keyframes in the Action of this Slot."""
keyed_bones = []
for fc in action.fcurves:
# Extracting bone name from fcurve data path
if "pose.bones" not in fc.data_path:
continue
bone_name = fc.data_path.split('["')[1].split('"]')[0]
if bone_name not in keyed_bones:
keyed_bones.append(bone_name)
return keyed_bones
class POSE_OT_bake_anim_across_armatures(Operator):
"""Constrain one armature to another, and bake over the animation"""
bl_idname = "pose.bake_anim_across_armatures"
bl_label = "Bake Animation From Active To Selected Armature"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if context.mode != 'OBJECT':
return False
if len(context.selected_objects) != 2:
return False
if not all([o.type == 'ARMATURE' for o in context.selected_objects]):
return False
if not context.object in context.selected_objects:
return False
if not context.object.animation_data and not context.object.animation_data.action:
return False
return True
def execute(self, context):
action = context.object.animation_data.action
src_rig = context.object
target_rig = context.selected_objects[0]
if src_rig == target_rig:
target_rig = context.selected_objects[1]
bone_layer_backup = target_rig.data.layers[:]
# Enable all target rig layers
target_rig.data.layers = [True]*32
# Deselect all target rig bones
for b in target_rig.data.bones:
b.select = False
keyed_bones = [src_rig.pose.bones.get(bn) for bn in keyed_bones_names(
action) if bn in src_rig.pose.bones]
for pb in keyed_bones:
# TODO: Bone name mapping based on a passed dictionary.
target_bone = target_rig.pose.bones.get(pb.name)
if not target_bone:
continue
ct = target_bone.constraints.new(type='COPY_TRANSFORMS')
ct.target = src_rig
ct.subtarget = target_bone.name
target_bone.bone.select = True
src_rig.select_set(False)
bpy.ops.nla.bake(visual_keying=True,
clear_constraints=True, bake_types={'POSE'})
target_rig.data.layers = bone_layer_backup[:]
return {'FINISHED'}
registry = [
POSE_OT_bake_anim_across_armatures,
]
@@ -0,0 +1,103 @@
# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from bpy.types import Operator, Menu
from bpy.props import EnumProperty, StringProperty, BoolProperty
from ..fcurve_utils import get_fcurves
class POSE_OT_curves_set_boolean(Operator):
"""Set lock state of all selected curves"""
bl_idname = "pose.curves_set_boolean"
bl_label = "Set a boolean on curves"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
curve_set: EnumProperty(
name="Affected Curves",
items=[
('ACTIVE', 'Active Curve', 'Active Curve'),
('SELECTED', 'Selected Curves', 'Selected Curves'),
('UNSELECTED', 'Unselected Curves', 'Unselected Curves'),
('ALL', 'All Curves', 'All Curves'),
],
default='SELECTED'
)
prop_name: StringProperty(
name="Property Name",
description="Name of the boolean property to set",
default='lock'
)
prop_value: BoolProperty(
name="Value",
description="Value to set",
default=False
)
@classmethod
def poll(cls, context):
# Only works in Graph Editor, when there is an active curve.
return context.pose_object and context.pose_object.animation_data and context.pose_object.animation_data.action
def execute(self, context):
action = context.object.animation_data.action
affected_fcurves = get_fcurves(context, action, self.curve_set)
for fc in affected_fcurves:
setattr(fc, self.prop_name, self.prop_value)
return {'FINISHED'}
class GRAPH_MT_channel_lock(Menu):
bl_label = "Lock"
def draw(self, context):
layout = self.layout
op = layout.operator(POSE_OT_curves_set_boolean.bl_idname,
text="Lock Selected", icon='LOCKED')
op.prop_name = "lock"
op.prop_value = True
op.curve_set = 'SELECTED'
op = layout.operator(POSE_OT_curves_set_boolean.bl_idname,
text="Lock Unselected", icon='LOCKED')
op.prop_name = "lock"
op.prop_value = True
op.curve_set = 'UNSELECTED'
layout.separator()
op = layout.operator(POSE_OT_curves_set_boolean.bl_idname,
text="Unlock Selected", icon='UNLOCKED')
op.prop_name = "lock"
op.prop_value = False
op.curve_set = 'SELECTED'
op = layout.operator(POSE_OT_curves_set_boolean.bl_idname,
text="Unlock All", icon='UNLOCKED')
op.prop_name = "lock"
op.prop_value = False
op.curve_set = 'ALL'
def draw_curves_lock_menu(self, context):
layout = self.layout
layout.menu("GRAPH_MT_channel_lock", icon='LOCKED')
registry = [
POSE_OT_curves_set_boolean,
GRAPH_MT_channel_lock
]
def register():
bpy.types.GRAPH_MT_channel.append(draw_curves_lock_menu)
def unregister():
bpy.types.GRAPH_MT_channel.remove(draw_curves_lock_menu)
@@ -0,0 +1,51 @@
# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from bpy.types import Operator
from ..fcurve_utils import get_fcurves_of_bones
class POSE_OT_select_matching_curves(Operator):
"""Set selection of all curves based on whether they match the transformation axis of the active curve"""
bl_idname = "pose.select_matching_curves"
bl_label = "Select Matching Curves"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
# Only works in Graph Editor, when there is an active curve.
return context.active_editable_fcurve
def execute(self, context):
action = context.object.animation_data.action
fcurves_of_selected_bones = get_fcurves_of_bones(
action, context.selected_pose_bones)
property_name = context.active_editable_fcurve.data_path.split(".")[-1]
for fc in fcurves_of_selected_bones:
fc.select = fc.data_path.endswith(property_name) and \
fc.array_index == context.active_editable_fcurve.array_index
return {'FINISHED'}
def draw_select_matching_curves(self, context):
layout = self.layout
layout.operator(POSE_OT_select_matching_curves.bl_idname)
registry = [
POSE_OT_select_matching_curves
]
def register():
bpy.types.GRAPH_MT_select.append(draw_select_matching_curves)
def unregister():
bpy.types.GRAPH_MT_select.remove(draw_select_matching_curves)