Fix #145753: Crash deleting a keyframe for another slot when tweaking

When deleting the last key of an F-Curve, the F-Curve itself also gets
deleted. This is now done via the same code as deleting F-Curves from
the channel list.

Before this fix, a deletion function was used that's not capable of
deleting from Actions in NLA strips, which is why it crashed.

Pull Request: https://projects.blender.org/blender/blender/pulls/145929
This commit is contained in:
Sybren A. Stüvel
2025-09-09 10:14:46 +02:00
parent 81c8b22eeb
commit 78032d57e7
5 changed files with 88 additions and 54 deletions

View File

@@ -460,6 +460,15 @@ bool BKE_nlatracks_have_animated_strips(ListBase *tracks);
*/
void BKE_nlastrip_validate_fcurves(NlaStrip *strip);
/**
* Delete the NLA-Strip's control F-Curve.
*
* This also ensures that the strip's flags are correctly updated.
*
* \return Whether the F-Curve was actually removed.
*/
bool BKE_nlastrip_controlcurve_remove(NlaStrip *strip, FCurve *fcurve);
/**
* Check if the given RNA pointer + property combo should be handled by
* NLA strip curves or not.

View File

@@ -1862,6 +1862,23 @@ void BKE_nlastrip_validate_fcurves(NlaStrip *strip)
}
}
bool BKE_nlastrip_controlcurve_remove(NlaStrip *strip, FCurve *fcurve)
{
if (STREQ(fcurve->rna_path, "strip_time")) {
strip->flag &= ~NLASTRIP_FLAG_USR_TIME;
}
else if (STREQ(fcurve->rna_path, "influence")) {
strip->flag &= ~NLASTRIP_FLAG_USR_INFLUENCE;
}
else {
return false;
}
BLI_remlink(&strip->fcurves, fcurve);
BKE_fcurve_free(fcurve);
return true;
}
bool BKE_nlastrip_has_curves_for_property(const PointerRNA *ptr, const PropertyRNA *prop)
{
/* sanity checks */

View File

@@ -2905,6 +2905,54 @@ static bool animchannels_delete_containers(const bContext *C, bAnimContext *ac)
return has_skipped_group;
}
void ED_anim_ale_fcurve_delete(bAnimContext &ac, bAnimListElem &ale)
{
BLI_assert(ELEM(ale.type, ANIMTYPE_FCURVE, ANIMTYPE_NLACURVE));
switch (ale.type) {
case ANIMTYPE_FCURVE: {
AnimData *adt = ale.adt;
FCurve *fcu = static_cast<FCurve *>(ale.data);
BLI_assert_msg((fcu->driver != nullptr) == (ac.datatype == ANIMCONT_DRIVERS),
"Expecting only driver F-Curves in the drivers editor");
if (ale.fcurve_owner_id && GS(ale.fcurve_owner_id->name) == ID_AC) {
/* F-Curves can be owned by Actions assigned to NLA strips, which
* `animrig::animdata_fcurve_delete()` (below) cannot handle. */
BLI_assert_msg(!fcu->driver, "Drivers are not expected to be owned by Actions");
blender::animrig::Action &action =
reinterpret_cast<bAction *>(ale.fcurve_owner_id)->wrap();
BLI_assert(!action.is_action_legacy());
action_fcurve_remove(action, *fcu);
}
else if (fcu->driver || adt->action) {
/* This function only works for drivers & directly-assigned Actions: */
blender::animrig::animdata_fcurve_delete(adt, fcu);
}
else {
BLI_assert_unreachable();
}
break;
}
case ANIMTYPE_NLACURVE: {
/* NLA Control Curve. */
NlaStrip *strip = static_cast<NlaStrip *>(ale.owner);
FCurve *fcu = static_cast<FCurve *>(ale.data);
if (!BKE_nlastrip_controlcurve_remove(strip, fcu)) {
printf("ERROR: Trying to delete NLA Control Curve for unknown property '%s'\n",
fcu->rna_path);
}
break;
}
default:
BLI_assert_unreachable();
}
tag_update_animation_element(&ale);
}
static wmOperatorStatus animchannels_delete_exec(bContext *C, wmOperator * /*op*/)
{
bAnimContext ac;
@@ -2941,55 +2989,11 @@ static wmOperatorStatus animchannels_delete_exec(bContext *C, wmOperator * /*op*
/* delete selected data channels */
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
switch (ale->type) {
case ANIMTYPE_FCURVE: {
/* F-Curves if we can identify its parent */
AnimData *adt = ale->adt;
FCurve *fcu = static_cast<FCurve *>(ale->data);
/* try to free F-Curve */
BLI_assert_msg((fcu->driver != nullptr) == (ac.datatype == ANIMCONT_DRIVERS),
"Expecting only driver F-Curves in the drivers editor");
if (ale->fcurve_owner_id && GS(ale->fcurve_owner_id->name) == ID_AC) {
/* F-Curves can be owned by Actions assigned to NLA strips, which
* `animrig::animdata_fcurve_delete()` (below) cannot handle. */
BLI_assert_msg(!fcu->driver, "Drivers are not expected to be owned by Actions");
blender::animrig::Action &action =
reinterpret_cast<bAction *>(ale->fcurve_owner_id)->wrap();
BLI_assert(!action.is_action_legacy());
action_fcurve_remove(action, *fcu);
}
else if (fcu->driver || adt->action) {
/* This function only works for drivers & directly-assigned Actions: */
blender::animrig::animdata_fcurve_delete(adt, fcu);
}
else {
BLI_assert_unreachable();
}
tag_update_animation_element(ale);
case ANIMTYPE_FCURVE:
case ANIMTYPE_NLACURVE:
ED_anim_ale_fcurve_delete(ac, *ale);
break;
}
case ANIMTYPE_NLACURVE: {
/* NLA Control Curve - Deleting it should disable the corresponding setting... */
NlaStrip *strip = static_cast<NlaStrip *>(ale->owner);
FCurve *fcu = static_cast<FCurve *>(ale->data);
if (STREQ(fcu->rna_path, "strip_time")) {
strip->flag &= ~NLASTRIP_FLAG_USR_TIME;
}
else if (STREQ(fcu->rna_path, "influence")) {
strip->flag &= ~NLASTRIP_FLAG_USR_INFLUENCE;
}
else {
printf("ERROR: Trying to delete NLA Control Curve for unknown property '%s'\n",
fcu->rna_path);
}
/* unlink and free the F-Curve */
BLI_remlink(&strip->fcurves, fcu);
BKE_fcurve_free(fcu);
tag_update_animation_element(ale);
break;
}
case ANIMTYPE_GPLAYER: {
/* Grease Pencil layer */
bGPdata *gpd = reinterpret_cast<bGPdata *>(ale->id);

View File

@@ -1231,6 +1231,14 @@ void ED_animedit_unlink_action(
*/
void ED_drivers_editor_init(bContext *C, ScrArea *area);
/**
* Delete an F-Curve from its owner.
*
* This can delete an F-Curve from an Action (both directly assigned and via an
* NLA strip), Drivers, and NLA control curves.
*/
void ED_anim_ale_fcurve_delete(bAnimContext &ac, bAnimListElem &ale);
/* ************************************************ */
enum eAnimvizCalcRange {

View File

@@ -1144,15 +1144,11 @@ static bool delete_action_keys(bAnimContext *ac)
changed = ED_masklayer_frames_delete((MaskLayer *)ale->data);
}
else {
FCurve *fcu = (FCurve *)ale->key_data;
AnimData *adt = ale->adt;
/* delete selected keyframes only */
FCurve *fcu = static_cast<FCurve *>(ale->key_data);
changed = BKE_fcurve_delete_keys_selected(fcu);
/* Only delete curve too if it won't be doing anything anymore */
if (BKE_fcurve_is_empty(fcu)) {
blender::animrig::animdata_fcurve_delete(adt, fcu);
if (changed && BKE_fcurve_is_empty(fcu)) {
ED_anim_ale_fcurve_delete(*ac, *ale);
ale->key_data = nullptr;
}
}