From 2fb0847dd14186922ac69b4398522014e7935924 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Tue, 29 Oct 2024 16:39:10 +0100 Subject: [PATCH] Fix #129385: Bake Action operator not working with slots This fixes the issue by traversing the new data structure for actions. * When a slot is assigned, the action is baked into the slot. * if no slot is assigned, it creates a new slot and bakes into that. However since no slot was assigned, the animation will be static. Pull Request: https://projects.blender.org/blender/blender/pulls/129525 --- scripts/modules/bpy_extras/anim_utils.py | 52 ++++++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/scripts/modules/bpy_extras/anim_utils.py b/scripts/modules/bpy_extras/anim_utils.py index 372073c851d..b573b057dbc 100644 --- a/scripts/modules/bpy_extras/anim_utils.py +++ b/scripts/modules/bpy_extras/anim_utils.py @@ -11,7 +11,7 @@ __all__ = ( ) import bpy -from bpy.types import Action +from bpy.types import Action, ActionSlot from dataclasses import dataclass from collections.abc import ( @@ -73,6 +73,26 @@ class BakeOptions: """Bake custom properties.""" +def _get_channelbag_for_slot(action: Action, slot: ActionSlot): + # This is on purpose limited to the first layer and strip. To support more + # than 1 layer, a rewrite of this operator is needed which ideally would + # happen in C++. + for layer in action.layers: + for strip in layer.strips: + channelbag = strip.channels(slot.handle) + return channelbag + + +def _ensure_channelbag_exists(action: Action, slot: ActionSlot): + channelbag = _get_channelbag_for_slot(action, slot) + if channelbag: + return channelbag + + for layer in action.layers: + for strip in layer.strips: + return strip.channelbags.new(slot) + + def bake_action( obj, *, @@ -357,6 +377,11 @@ def bake_action_iter( is_new_action = action is None if is_new_action: action = bpy.data.actions.new("Action") + else: + # When baking into the current action, a slot needs to be assigned. + if not atd.action_slot: + slot = action.slots.new(for_id=obj) + atd.action_slot = slot # Only leave tweak mode if we actually need to modify the action (#57159) if action != atd.action: @@ -377,7 +402,13 @@ def bake_action_iter( # Apply transformations to action # pose - lookup_fcurves = {(fcurve.data_path, fcurve.array_index): fcurve for fcurve in action.fcurves} + lookup_fcurves = {} + assert action.is_action_layered + channelbag = _get_channelbag_for_slot(action, atd.action_slot) + if channelbag: + # channelbag can be None if no layers or strips exist in the action. + lookup_fcurves = {(fcurve.data_path, fcurve.array_index): fcurve for fcurve in channelbag.fcurves} + if bake_options.do_pose: for f, armature_custom_properties in armature_info: bake_custom_properties(obj, custom_props=armature_custom_properties, @@ -470,7 +501,8 @@ def bake_action_iter( if is_new_action: keyframes.insert_keyframes_into_new_action(total_new_keys, action, name) else: - keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name) + keyframes.insert_keyframes_into_existing_action( + lookup_fcurves, total_new_keys, action, atd.action_slot) # object. TODO. multiple objects if bake_options.do_object: @@ -536,7 +568,8 @@ def bake_action_iter( if is_new_action: keyframes.insert_keyframes_into_new_action(total_new_keys, action, name) else: - keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name) + keyframes.insert_keyframes_into_existing_action( + lookup_fcurves, total_new_keys, action, atd.action_slot) if bake_options.do_parents_clear: obj.parent = None @@ -652,7 +685,7 @@ class KeyframesCo: lookup_fcurves: Mapping[FCurveKey, bpy.types.FCurve], total_new_keys: int, action: Action, - action_group_name: str, + action_slot: ActionSlot, ) -> None: """ Assumes the action already exists, that it might already have F-curves. Otherwise, the @@ -675,9 +708,12 @@ class KeyframesCo: fcurve = lookup_fcurves.get(fc_key, None) if fcurve is None: data_path, array_index = fc_key - fcurve = action.fcurves.new( - data_path, index=array_index, action_group=action_group_name - ) + assert action.is_action_layered + channelbag = _ensure_channelbag_exists(action, action_slot) + if not channelbag: + # Can happen if no layers or strips exist on the action. + continue + fcurve = channelbag.fcurves.new(data_path, index=array_index) keyframe_points = fcurve.keyframe_points