Fix: Anim, crash when deleting both action slot and F-Curve group
Fix a crash that happened when trying to delete an action slot and an F-Curve group contained within it. Pull Request: https://projects.blender.org/blender/blender/pulls/134048
This commit is contained in:
@@ -2704,6 +2704,169 @@ static void tag_update_animation_element(bAnimListElem *ale)
|
||||
DEG_id_tag_update(id, ID_RECALC_ANIMATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete container-like channels.
|
||||
*
|
||||
* This function may not delete everything in one run. Once an action slot has been deleted, it
|
||||
* will refuse to delete any groups; because deleting a slot deletes its channelbags, which in turn
|
||||
* contain the groups, any previously-selected group may be deleted along with the slot.
|
||||
*
|
||||
* \return true when another run is necessary after this one.
|
||||
*/
|
||||
static bool animchannels_delete_containers(const bContext *C, bAnimContext *ac)
|
||||
{
|
||||
const eAnimFilter_Flags filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE |
|
||||
ANIMFILTER_SEL | ANIMFILTER_LIST_CHANNELS |
|
||||
ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
|
||||
ListBase anim_data = {nullptr, nullptr};
|
||||
ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
|
||||
|
||||
bool must_skip_groups = false;
|
||||
bool has_skipped_group = false;
|
||||
|
||||
/* Delete selected container-like channels and their underlying data. */
|
||||
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
||||
switch (ale->type) {
|
||||
case ANIMTYPE_ACTION_SLOT: {
|
||||
BLI_assert(ale->fcurve_owner_id);
|
||||
BLI_assert(ale->data);
|
||||
BLI_assert_msg(GS(ale->fcurve_owner_id->name) == ID_AC,
|
||||
"fcurve_owner_id should be an Action");
|
||||
|
||||
blender::animrig::Action &action =
|
||||
reinterpret_cast<bAction *>(ale->fcurve_owner_id)->wrap();
|
||||
blender::animrig::Slot &slot_to_remove = static_cast<ActionSlot *>(ale->data)->wrap();
|
||||
|
||||
action.slot_remove(slot_to_remove);
|
||||
|
||||
tag_update_animation_element(ale);
|
||||
|
||||
/* Subsequent groups should be skipped, and their deletion kept for
|
||||
* another run (if they even exist after this slot was deleted). */
|
||||
must_skip_groups = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ANIMTYPE_GROUP: {
|
||||
if (must_skip_groups) {
|
||||
/* Another run of this function is needed to see if this group still
|
||||
* exists, and thus still needs deleting. */
|
||||
has_skipped_group = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bActionGroup *agrp = (bActionGroup *)ale->data;
|
||||
AnimData *adt = ale->adt;
|
||||
FCurve *fcu, *fcn;
|
||||
|
||||
/* Groups should always be part of an action. */
|
||||
if (adt == nullptr || adt->action == nullptr) {
|
||||
BLI_assert_unreachable();
|
||||
continue;
|
||||
}
|
||||
|
||||
blender::animrig::Action &action = adt->action->wrap();
|
||||
|
||||
/* Legacy actions */
|
||||
if (!action.is_action_layered()) {
|
||||
/* delete all of the Group's F-Curves, but no others */
|
||||
for (fcu = static_cast<FCurve *>(agrp->channels.first); fcu && fcu->grp == agrp;
|
||||
fcu = fcn)
|
||||
{
|
||||
fcn = fcu->next;
|
||||
|
||||
/* remove from group and action, then free */
|
||||
action_groups_remove_channel(adt->action, fcu);
|
||||
BKE_fcurve_free(fcu);
|
||||
}
|
||||
|
||||
/* free the group itself */
|
||||
BLI_freelinkN(&adt->action->groups, agrp);
|
||||
DEG_id_tag_update_ex(CTX_data_main(C), &adt->action->id, ID_RECALC_ANIMATION);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Layered actions.
|
||||
*
|
||||
* Note that the behavior here is different from deleting groups via
|
||||
* the Python API: in the Python API the fcurves that belonged to the
|
||||
* group remain, and just get ungrouped, whereas here they are deleted
|
||||
* along with the group. This difference in behavior is replicated
|
||||
* from legacy actions. */
|
||||
|
||||
blender::animrig::Channelbag &channelbag = agrp->channelbag->wrap();
|
||||
|
||||
/* Remove all the fcurves in the group, which also automatically
|
||||
* deletes the group when the last fcurve is deleted. Since the group
|
||||
* is automatically deleted, we store the fcurve range ahead of time
|
||||
* so we don't have to worry about the memory disappearing out from
|
||||
* under us. */
|
||||
const int fcurve_range_start = agrp->fcurve_range_start;
|
||||
const int fcurve_range_length = agrp->fcurve_range_length;
|
||||
for (int i = 0; i < fcurve_range_length; i++) {
|
||||
channelbag.fcurve_remove(*channelbag.fcurve(fcurve_range_start));
|
||||
}
|
||||
|
||||
DEG_id_tag_update_ex(CTX_data_main(C), &adt->action->id, ID_RECALC_ANIMATION);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ANIMTYPE_NONE:
|
||||
case ANIMTYPE_ANIMDATA:
|
||||
case ANIMTYPE_SPECIALDATA__UNUSED:
|
||||
case ANIMTYPE_SUMMARY:
|
||||
case ANIMTYPE_SCENE:
|
||||
case ANIMTYPE_OBJECT:
|
||||
case ANIMTYPE_FCURVE:
|
||||
case ANIMTYPE_NLACONTROLS:
|
||||
case ANIMTYPE_NLACURVE:
|
||||
case ANIMTYPE_FILLACT_LAYERED:
|
||||
case ANIMTYPE_FILLACTD:
|
||||
case ANIMTYPE_FILLDRIVERS:
|
||||
case ANIMTYPE_DSMAT:
|
||||
case ANIMTYPE_DSLAM:
|
||||
case ANIMTYPE_DSCAM:
|
||||
case ANIMTYPE_DSCACHEFILE:
|
||||
case ANIMTYPE_DSCUR:
|
||||
case ANIMTYPE_DSSKEY:
|
||||
case ANIMTYPE_DSWOR:
|
||||
case ANIMTYPE_DSNTREE:
|
||||
case ANIMTYPE_DSPART:
|
||||
case ANIMTYPE_DSMBALL:
|
||||
case ANIMTYPE_DSARM:
|
||||
case ANIMTYPE_DSMESH:
|
||||
case ANIMTYPE_DSTEX:
|
||||
case ANIMTYPE_DSLAT:
|
||||
case ANIMTYPE_DSLINESTYLE:
|
||||
case ANIMTYPE_DSSPK:
|
||||
case ANIMTYPE_DSGPENCIL:
|
||||
case ANIMTYPE_DSMCLIP:
|
||||
case ANIMTYPE_DSHAIR:
|
||||
case ANIMTYPE_DSPOINTCLOUD:
|
||||
case ANIMTYPE_DSVOLUME:
|
||||
case ANIMTYPE_SHAPEKEY:
|
||||
case ANIMTYPE_GPDATABLOCK:
|
||||
case ANIMTYPE_GPLAYER:
|
||||
case ANIMTYPE_GREASE_PENCIL_DATABLOCK:
|
||||
case ANIMTYPE_GREASE_PENCIL_LAYER_GROUP:
|
||||
case ANIMTYPE_GREASE_PENCIL_LAYER:
|
||||
case ANIMTYPE_MASKDATABLOCK:
|
||||
case ANIMTYPE_MASKLAYER:
|
||||
case ANIMTYPE_NLATRACK:
|
||||
case ANIMTYPE_NLAACTION:
|
||||
case ANIMTYPE_PALETTE:
|
||||
case ANIMTYPE_NUM_TYPES:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
|
||||
return has_skipped_group;
|
||||
}
|
||||
|
||||
static int animchannels_delete_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
bAnimContext ac;
|
||||
@@ -2725,140 +2888,9 @@ static int animchannels_delete_exec(bContext *C, wmOperator * /*op*/)
|
||||
* or slot will delete the channels they contain as well, so better avoid looping over those in
|
||||
* the same loop. */
|
||||
if (ac.datatype != ANIMCONT_DRIVERS) {
|
||||
/* filter data */
|
||||
filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_SEL |
|
||||
ANIMFILTER_LIST_CHANNELS | ANIMFILTER_FOREDIT | ANIMFILTER_NODUPLIS);
|
||||
ANIM_animdata_filter(
|
||||
&ac, &anim_data, eAnimFilter_Flags(filter), ac.data, eAnimCont_Types(ac.datatype));
|
||||
|
||||
/* delete selected groups and their associated channels */
|
||||
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
||||
switch (ale->type) {
|
||||
case ANIMTYPE_ACTION_SLOT: {
|
||||
BLI_assert(ale->fcurve_owner_id);
|
||||
BLI_assert(ale->data);
|
||||
BLI_assert_msg(GS(ale->fcurve_owner_id->name) == ID_AC,
|
||||
"fcurve_owner_id should be an Action");
|
||||
|
||||
blender::animrig::Action &action =
|
||||
reinterpret_cast<bAction *>(ale->fcurve_owner_id)->wrap();
|
||||
blender::animrig::Slot &slot_to_remove = static_cast<ActionSlot *>(ale->data)->wrap();
|
||||
|
||||
action.slot_remove(slot_to_remove);
|
||||
|
||||
tag_update_animation_element(ale);
|
||||
break;
|
||||
}
|
||||
case ANIMTYPE_GROUP: {
|
||||
bActionGroup *agrp = (bActionGroup *)ale->data;
|
||||
AnimData *adt = ale->adt;
|
||||
FCurve *fcu, *fcn;
|
||||
|
||||
/* Groups should always be part of an action. */
|
||||
if (adt == nullptr || adt->action == nullptr) {
|
||||
BLI_assert_unreachable();
|
||||
continue;
|
||||
}
|
||||
|
||||
blender::animrig::Action &action = adt->action->wrap();
|
||||
|
||||
/* Legacy actions */
|
||||
if (!action.is_action_layered()) {
|
||||
/* delete all of the Group's F-Curves, but no others */
|
||||
for (fcu = static_cast<FCurve *>(agrp->channels.first); fcu && fcu->grp == agrp;
|
||||
fcu = fcn)
|
||||
{
|
||||
fcn = fcu->next;
|
||||
|
||||
/* remove from group and action, then free */
|
||||
action_groups_remove_channel(adt->action, fcu);
|
||||
BKE_fcurve_free(fcu);
|
||||
}
|
||||
|
||||
/* free the group itself */
|
||||
BLI_freelinkN(&adt->action->groups, agrp);
|
||||
DEG_id_tag_update_ex(CTX_data_main(C), &adt->action->id, ID_RECALC_ANIMATION);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Layered actions.
|
||||
*
|
||||
* Note that the behavior here is different from deleting groups via
|
||||
* the Python API: in the Python API the fcurves that belonged to the
|
||||
* group remain, and just get ungrouped, whereas here they are deleted
|
||||
* along with the group. This difference in behavior is replicated
|
||||
* from legacy actions. */
|
||||
|
||||
blender::animrig::Channelbag &channelbag = agrp->channelbag->wrap();
|
||||
|
||||
/* Remove all the fcurves in the group, which also automatically
|
||||
* deletes the group when the last fcurve is deleted. Since the group
|
||||
* is automatically deleted, we store the fcurve range ahead of time
|
||||
* so we don't have to worry about the memory disappearing out from
|
||||
* under us. */
|
||||
const int fcurve_range_start = agrp->fcurve_range_start;
|
||||
const int fcurve_range_length = agrp->fcurve_range_length;
|
||||
for (int i = 0; i < fcurve_range_length; i++) {
|
||||
channelbag.fcurve_remove(*channelbag.fcurve(fcurve_range_start));
|
||||
}
|
||||
|
||||
DEG_id_tag_update_ex(CTX_data_main(C), &adt->action->id, ID_RECALC_ANIMATION);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ANIMTYPE_NONE:
|
||||
case ANIMTYPE_ANIMDATA:
|
||||
case ANIMTYPE_SPECIALDATA__UNUSED:
|
||||
case ANIMTYPE_SUMMARY:
|
||||
case ANIMTYPE_SCENE:
|
||||
case ANIMTYPE_OBJECT:
|
||||
case ANIMTYPE_FCURVE:
|
||||
case ANIMTYPE_NLACONTROLS:
|
||||
case ANIMTYPE_NLACURVE:
|
||||
case ANIMTYPE_FILLACT_LAYERED:
|
||||
case ANIMTYPE_FILLACTD:
|
||||
case ANIMTYPE_FILLDRIVERS:
|
||||
case ANIMTYPE_DSMAT:
|
||||
case ANIMTYPE_DSLAM:
|
||||
case ANIMTYPE_DSCAM:
|
||||
case ANIMTYPE_DSCACHEFILE:
|
||||
case ANIMTYPE_DSCUR:
|
||||
case ANIMTYPE_DSSKEY:
|
||||
case ANIMTYPE_DSWOR:
|
||||
case ANIMTYPE_DSNTREE:
|
||||
case ANIMTYPE_DSPART:
|
||||
case ANIMTYPE_DSMBALL:
|
||||
case ANIMTYPE_DSARM:
|
||||
case ANIMTYPE_DSMESH:
|
||||
case ANIMTYPE_DSTEX:
|
||||
case ANIMTYPE_DSLAT:
|
||||
case ANIMTYPE_DSLINESTYLE:
|
||||
case ANIMTYPE_DSSPK:
|
||||
case ANIMTYPE_DSGPENCIL:
|
||||
case ANIMTYPE_DSMCLIP:
|
||||
case ANIMTYPE_DSHAIR:
|
||||
case ANIMTYPE_DSPOINTCLOUD:
|
||||
case ANIMTYPE_DSVOLUME:
|
||||
case ANIMTYPE_SHAPEKEY:
|
||||
case ANIMTYPE_GPDATABLOCK:
|
||||
case ANIMTYPE_GPLAYER:
|
||||
case ANIMTYPE_GREASE_PENCIL_DATABLOCK:
|
||||
case ANIMTYPE_GREASE_PENCIL_LAYER_GROUP:
|
||||
case ANIMTYPE_GREASE_PENCIL_LAYER:
|
||||
case ANIMTYPE_MASKDATABLOCK:
|
||||
case ANIMTYPE_MASKLAYER:
|
||||
case ANIMTYPE_NLATRACK:
|
||||
case ANIMTYPE_NLAACTION:
|
||||
case ANIMTYPE_PALETTE:
|
||||
case ANIMTYPE_NUM_TYPES:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
/* Keep deleting container-like channels until there are no more to delete. */
|
||||
while (animchannels_delete_containers(C, &ac))
|
||||
;
|
||||
}
|
||||
|
||||
/* filter data */
|
||||
|
||||
Reference in New Issue
Block a user