Fix #136967: Crash on Merge Animation when Slot has no Channelbag

The Merge Animation operator assumed that when a keyframe strip exists,
it always has a channelbag for the to-be-moved slot. This isn't
necessarily the case, so now it only moves the channelbag if it exists.

Pull Request: https://projects.blender.org/blender/blender/pulls/136978
This commit is contained in:
Sybren A. Stüvel
2025-04-04 12:22:47 +02:00
parent b54d629401
commit a9f4f211d4
2 changed files with 67 additions and 10 deletions

View File

@@ -3100,17 +3100,22 @@ void move_slot(Main &bmain, Slot &source_slot, Action &from_action, Action &to_a
if (!from_action.layers().is_empty() && !from_action.layer(0)->strips().is_empty()) {
StripKeyframeData &from_strip_data = from_action.layer(0)->strip(0)->data<StripKeyframeData>(
from_action);
to_action.layer_keystrip_ensure();
StripKeyframeData &to_strip_data = to_action.layer(0)->strip(0)->data<StripKeyframeData>(
to_action);
Channelbag *channelbag = from_strip_data.channelbag_for_slot(source_slot.handle);
BLI_assert(channelbag != nullptr);
channelbag->slot_handle = target_slot.handle;
grow_array_and_append<ActionChannelbag *>(
&to_strip_data.channelbag_array, &to_strip_data.channelbag_array_num, channelbag);
int index = from_strip_data.find_channelbag_index(*channelbag);
shrink_array_and_remove<ActionChannelbag *>(
&from_strip_data.channelbag_array, &from_strip_data.channelbag_array_num, index);
/* It's perfectly fine for a slot to not have a channelbag on each keyframe strip. */
if (channelbag) {
/* Only create the layer & keyframe strip if there is a channelbag to move
* into it. Otherwise it's better to keep the Action lean, and defer their
* creation when keys are inserted. */
to_action.layer_keystrip_ensure();
StripKeyframeData &to_strip_data = to_action.layer(0)->strip(0)->data<StripKeyframeData>(
to_action);
channelbag->slot_handle = target_slot.handle;
grow_array_and_append<ActionChannelbag *>(
&to_strip_data.channelbag_array, &to_strip_data.channelbag_array_num, channelbag);
const int index = from_strip_data.find_channelbag_index(*channelbag);
shrink_array_and_remove<ActionChannelbag *>(
&from_strip_data.channelbag_array, &from_strip_data.channelbag_array_num, index);
}
}
/* Reassign all users of `source_slot` to the action `to_action` and the slot `target_slot`. */

View File

@@ -1248,6 +1248,58 @@ TEST_F(ActionLayersTest, action_move_slot)
ASSERT_EQ(action, suzanne->adt->action);
}
TEST_F(ActionLayersTest, action_move_slot_without_channelbag)
{
Action *action_2 = static_cast<Action *>(BKE_id_new(bmain, ID_AC, "Action 2"));
EXPECT_TRUE(action->is_empty());
Slot &slot_cube = action->slot_add();
Slot &slot_suzanne = action_2->slot_add();
EXPECT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(assign_action_and_slot(action_2, &slot_suzanne, suzanne->id),
ActionSlotAssignmentResult::OK);
PointerRNA cube_rna_pointer = RNA_id_pointer_create(&cube->id);
PointerRNA suzanne_rna_pointer = RNA_id_pointer_create(&suzanne->id);
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"location", 0});
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"rotation_euler", 1});
/* Make sure action_2 has a keyframe strip, but without a channelbag. */
action_2->layer_add("Bagless").strip_add(*action_2, Strip::Type::Keyframe);
ASSERT_EQ(action->layer_array_num, 1);
ASSERT_EQ(action_2->layer_array_num, 1);
Layer *layer_1 = action->layer(0);
Layer *layer_2 = action_2->layer(0);
ASSERT_EQ(layer_1->strip_array_num, 1);
ASSERT_EQ(layer_2->strip_array_num, 1);
StripKeyframeData &strip_data_1 = layer_1->strip(0)->data<StripKeyframeData>(*action);
StripKeyframeData &strip_data_2 = layer_2->strip(0)->data<StripKeyframeData>(*action_2);
ASSERT_EQ(strip_data_1.channelbag_array_num, 1);
ASSERT_EQ(strip_data_2.channelbag_array_num, 0)
<< "the keyframe strip of action_2 should NOT have a channelbag in this test";
Channelbag *bag_1 = strip_data_1.channelbag(0);
ASSERT_EQ(bag_1->fcurve_array_num, 2);
move_slot(*bmain, slot_suzanne, *action_2, *action);
ASSERT_EQ(strip_data_1.channelbag_array_num, 1);
ASSERT_EQ(strip_data_2.channelbag_array_num, 0);
ASSERT_EQ(action->slot_array_num, 2);
ASSERT_EQ(action_2->slot_array_num, 0);
/* Action should have been reassigned. */
ASSERT_EQ(action, cube->adt->action);
ASSERT_EQ(action, suzanne->adt->action);
}
/*-----------------------------------------------------------*/
/* Allocate fcu->bezt, and also return a unique_ptr to it for easily freeing the memory. */