Python: remove legacy Action API

Remove the legacy (aka backward-compatible) API from `bpy.types.Action`:

- `action.fcurves`
- `action.groups`
- `action.id_root`

These have been deprecated since the introduction of Slotted Actions in
Blender 4.4. They operated on a subset of the Action's data, which was
not guaranteed to be the correct subset, given the Action's new
possibilities. This was to give add-on developers time to migrate to the
new API, and has always been intended to be removed in Blender 5.0.

See #146586 for more info.

Pull Request: https://projects.blender.org/blender/blender/pulls/146626
This commit is contained in:
Sybren A. Stüvel
2025-10-03 15:25:27 +02:00
parent b36b7e2d70
commit 1395abc502

View File

@@ -912,362 +912,6 @@ static PointerRNA rna_ActionGroup_channels_get(CollectionPropertyIterator *iter)
return RNA_pointer_create_with_parent(iter->parent, &RNA_FCurve, fcurve);
}
/* Use the backward-compatible API only when we're working with the action as a
* layered action. */
static bool use_backward_compatible_api(animrig::Action &action)
{
return !animrig::legacy::action_treat_as_legacy(action);
}
static void rna_iterator_Action_groups_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
animrig::Action &action = rna_action(ptr);
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag) {
return;
}
rna_iterator_array_begin(iter, ptr, channelbag->channel_groups());
}
else {
rna_iterator_listbase_begin(iter, ptr, &action.groups, nullptr);
}
}
static void rna_iterator_Action_groups_next(CollectionPropertyIterator *iter)
{
animrig::Action &action = rna_action(&iter->parent);
if (use_backward_compatible_api(action)) {
rna_iterator_array_next(iter);
}
else {
rna_iterator_listbase_next(iter);
}
}
static void rna_iterator_Action_groups_end(CollectionPropertyIterator *iter)
{
animrig::Action &action = rna_action(&iter->parent);
if (use_backward_compatible_api(action)) {
rna_iterator_array_end(iter);
}
else {
rna_iterator_listbase_end(iter);
}
}
static PointerRNA rna_iterator_Action_groups_get(CollectionPropertyIterator *iter)
{
animrig::Action &action = rna_action(&iter->parent);
bActionGroup *group;
if (use_backward_compatible_api(action)) {
group = static_cast<bActionGroup *>(rna_iterator_array_dereference_get(iter));
}
else {
group = static_cast<bActionGroup *>(rna_iterator_listbase_get(iter));
}
return RNA_pointer_create_with_parent(iter->parent, &RNA_ActionGroup, group);
}
static int rna_iterator_Action_groups_length(PointerRNA *ptr)
{
animrig::Action &action = rna_action(ptr);
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag) {
return 0;
}
return channelbag->channel_groups().size();
}
return BLI_listbase_count(&action.groups);
}
static bActionGroup *rna_Action_groups_new(bAction *act, const char name[])
{
bActionGroup *group;
animrig::Action &action = act->wrap();
if (use_backward_compatible_api(action)) {
animrig::Channelbag &channelbag = animrig::legacy::channelbag_ensure(action);
group = &channelbag.channel_group_create(name);
}
else {
group = action_groups_add_new(act, name);
}
/* I (Sybren) expected that the commented-out notifier below was missing.
* However, the animation filtering code (`animfilter_act_group()`) hides
* empty groups. Because this group is newly created, it's not shown in the
* UI, and thus there is no need to send notifiers.
*
* WM_main_add_notifier(NC_ANIMATION | ND_ANIMCHAN | NA_ADDED, nullptr); */
return group;
}
static void rna_Action_groups_remove(bAction *act, ReportList *reports, PointerRNA *agrp_ptr)
{
bActionGroup *agrp = static_cast<bActionGroup *>(agrp_ptr->data);
BLI_assert(agrp); /* Ensured by RNA flag PROP_NEVER_NULL. */
animrig::Action &action = act->wrap();
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag || !channelbag->channel_group_remove(*agrp)) {
BKE_reportf(reports,
RPT_ERROR,
"Action group '%s' not found in action '%s'",
agrp->name,
act->id.name + 2);
}
/* Removing a group also removes its F-Curves. */
DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH);
WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
return;
}
FCurve *fcu, *fcn;
/* try to remove the F-Curve from the action */
if (BLI_remlink_safe(&act->groups, agrp) == false) {
BKE_reportf(reports,
RPT_ERROR,
"Action group '%s' not found in action '%s'",
agrp->name,
act->id.name + 2);
return;
}
/* Move every one of the group's F-Curves out into the Action again. */
for (fcu = static_cast<FCurve *>(agrp->channels.first); (fcu) && (fcu->grp == agrp); fcu = fcn) {
fcn = fcu->next;
/* remove from group */
action_groups_remove_channel(act, fcu);
/* tack onto the end */
BLI_addtail(&act->curves, fcu);
}
MEM_freeN(agrp);
agrp_ptr->invalidate();
DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH);
WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
}
static void rna_iterator_Action_fcurves_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
animrig::Action &action = rna_action(ptr);
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag) {
return;
}
rna_iterator_array_begin(iter, ptr, channelbag->fcurves());
}
else {
rna_iterator_listbase_begin(iter, ptr, &action.curves, nullptr);
}
}
static void rna_iterator_Action_fcurves_next(CollectionPropertyIterator *iter)
{
animrig::Action &action = rna_action(&iter->parent);
if (use_backward_compatible_api(action)) {
rna_iterator_array_next(iter);
}
else {
rna_iterator_listbase_next(iter);
}
}
static void rna_iterator_Action_fcurves_end(CollectionPropertyIterator *iter)
{
animrig::Action &action = rna_action(&iter->parent);
if (use_backward_compatible_api(action)) {
rna_iterator_array_end(iter);
}
else {
rna_iterator_listbase_end(iter);
}
}
static PointerRNA rna_iterator_Action_fcurves_get(CollectionPropertyIterator *iter)
{
animrig::Action &action = rna_action(&iter->parent);
FCurve *fcurve;
if (use_backward_compatible_api(action)) {
fcurve = static_cast<FCurve *>(rna_iterator_array_dereference_get(iter));
}
else {
fcurve = static_cast<FCurve *>(rna_iterator_listbase_get(iter));
}
return RNA_pointer_create_with_parent(iter->parent, &RNA_FCurve, fcurve);
}
static int rna_iterator_Action_fcurves_length(PointerRNA *ptr)
{
animrig::Action &action = rna_action(ptr);
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag) {
return 0;
}
return channelbag->fcurves().size();
}
return BLI_listbase_count(&action.curves);
}
static FCurve *rna_Action_fcurve_new(bAction *act,
Main *bmain,
ReportList *reports,
const char *data_path,
int index,
const char *group)
{
BLI_assert(data_path != nullptr);
if (data_path[0] == '\0') {
BKE_report(reports, RPT_ERROR, "F-Curve data path empty, invalid argument");
return nullptr;
}
animrig::FCurveDescriptor fcurve_descriptor = {data_path, index};
if (group && group[0] != '\0') {
fcurve_descriptor.channel_group = group;
}
animrig::Action &action = act->wrap();
if (use_backward_compatible_api(action)) {
/* Add the F-Curve to the channelbag for the first slot. */
animrig::Channelbag &channelbag = animrig::legacy::channelbag_ensure(action);
FCurve *fcurve = channelbag.fcurve_create_unique(bmain, fcurve_descriptor);
if (!fcurve) {
/* The only reason fcurve_create_unique() returns nullptr is when the curve
* already exists. */
/* This is using the same error as below, as this is mimicking the legacy API. */
BKE_reportf(reports,
RPT_ERROR,
"F-Curve '%s[%d]' already exists in action '%s'",
data_path,
index,
act->id.name + 2);
return nullptr;
}
return fcurve;
}
/* Annoying, check if this exists. */
if (blender::animrig::fcurve_find_in_action(act, fcurve_descriptor)) {
BKE_reportf(reports,
RPT_ERROR,
"F-Curve '%s[%d]' already exists in action '%s'",
data_path,
index,
act->id.name + 2);
return nullptr;
}
return blender::animrig::action_fcurve_ensure_legacy(
bmain,
act,
fcurve_descriptor.channel_group ? fcurve_descriptor.channel_group->c_str() : nullptr,
nullptr,
fcurve_descriptor);
}
static FCurve *rna_Action_fcurve_find(bAction *act,
ReportList *reports,
const char *data_path,
int index)
{
if (data_path[0] == '\0') {
BKE_report(reports, RPT_ERROR, "F-Curve data path empty, invalid argument");
return nullptr;
}
animrig::Action &action = act->wrap();
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag) {
return nullptr;
}
return channelbag->fcurve_find({data_path, index});
}
/* Returns nullptr if not found. */
return animrig::fcurve_find_in_action(act, {data_path, index});
}
static void rna_Action_fcurve_remove(bAction *act, ReportList *reports, PointerRNA *fcu_ptr)
{
FCurve *fcu = static_cast<FCurve *>(fcu_ptr->data);
animrig::Action &action = act->wrap();
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag) {
BKE_reportf(reports, RPT_ERROR, "F-Curve not found in action '%s'", act->id.name + 2);
return;
}
if (!channelbag->fcurve_remove(*fcu)) {
BKE_reportf(reports, RPT_ERROR, "F-Curve not found in action '%s'", act->id.name + 2);
return;
}
fcu_ptr->invalidate();
DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH);
WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
return;
}
if (fcu->grp) {
if (BLI_findindex(&act->groups, fcu->grp) == -1) {
BKE_reportf(reports,
RPT_ERROR,
"F-Curve's action group '%s' not found in action '%s'",
fcu->grp->name,
act->id.name + 2);
return;
}
action_groups_remove_channel(act, fcu);
BKE_fcurve_free(fcu);
fcu_ptr->invalidate();
}
else {
if (BLI_findindex(&act->curves, fcu) == -1) {
BKE_reportf(reports, RPT_ERROR, "F-Curve not found in action '%s'", act->id.name + 2);
return;
}
BLI_remlink(&act->curves, fcu);
BKE_fcurve_free(fcu);
fcu_ptr->invalidate();
}
DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH);
WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
}
static void rna_Action_fcurve_clear(bAction *act)
{
animrig::Action &action = act->wrap();
if (use_backward_compatible_api(action)) {
animrig::Channelbag *channelbag = animrig::legacy::channelbag_get(action);
if (!channelbag) {
/* Nothing to clear, so the post-condition of not having F-Curves is fulfilled. */
return;
}
channelbag->fcurves_clear();
}
else {
BKE_action_fcurves_clear(act);
}
WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
}
static TimeMarker *rna_Action_pose_markers_new(bAction *act, const char name[])
{
TimeMarker *marker = MEM_callocN<TimeMarker>("TimeMarker");
@@ -1673,55 +1317,6 @@ static void rna_ActionSlot_target_id_type_set(PointerRNA *ptr, int value)
action.slot_idtype_define(slot, ID_Type(value));
}
/**
* For API backwards compatibility with pre-layered-actions (Blender 4.3 and
* earlier), we treat `Action.id_root` as a proxy for the `target_id_type`
* property (`idtype` in DNA) of the Action's first Slot.
*
* If the Action has no slots, then we fallback to returning 'unspecified' (0).
*/
static int rna_Action_id_root_get(PointerRNA *ptr)
{
animrig::Action &action = reinterpret_cast<bAction *>(ptr->owner_id)->wrap();
if (action.slots().is_empty()) {
return 0;
}
return action.slot(0)->idtype;
}
/**
* For API backwards compatibility with pre-layered-actions (Blender 4.3 and
* earlier), we treat `Action.id_root` as a proxy for the `target_id_type`
* property (`idtype` in DNA) of the Action's first Slot.
*
* If the Action has no slots, then a legacy slot is created and its
* `target_id_type` is set.
*/
static void rna_Action_id_root_set(PointerRNA *ptr, int value)
{
animrig::Action &action = reinterpret_cast<bAction *>(ptr->owner_id)->wrap();
animrig::Slot &slot = animrig::legacy::slot_ensure(action);
action.slot_idtype_define(slot, ID_Type(value));
}
static void rna_Action_id_root_update(Main *bmain, Scene *, PointerRNA *ptr)
{
animrig::Action &action = rna_action(ptr);
if (action.slots().is_empty()) {
/* Nothing to do: id_root can't be set without at least one slot, so no
* change was possible that would necessitate an update. */
return;
}
/* Setting id_root actually sets the target ID type of the first slot, so it's
* the resulting changes to the first slot that we need to propagate. */
action.slot_identifier_propagate(*bmain, *action.slot(0));
}
#else
static void rna_def_dopesheet(BlenderRNA *brna)
@@ -2758,110 +2353,6 @@ static void rna_def_action_group(BlenderRNA *brna)
/* =========================== Legacy Action interface =========================== */
/* fcurve.keyframe_points */
static void rna_def_action_groups(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
FunctionRNA *func;
PropertyRNA *parm;
RNA_def_property_srna(cprop, "ActionGroups");
srna = RNA_def_struct(brna, "ActionGroups", nullptr);
RNA_def_struct_sdna(srna, "bAction");
RNA_def_struct_ui_text(srna, "Action Groups", "Collection of action groups");
RNA_def_property_collection_funcs(cprop,
"rna_iterator_Action_groups_begin",
"rna_iterator_Action_groups_next",
"rna_iterator_Action_groups_end",
"rna_iterator_Action_groups_get",
"rna_iterator_Action_groups_length",
nullptr,
nullptr,
nullptr);
func = RNA_def_function(srna, "new", "rna_Action_groups_new");
RNA_def_function_ui_description(func, "Create a new action group and add it to the action");
parm = RNA_def_string(func, "name", "Group", 0, "", "New name for the action group");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_pointer(func, "action_group", "ActionGroup", "", "Newly created action group");
RNA_def_function_return(func, parm);
func = RNA_def_function(srna, "remove", "rna_Action_groups_remove");
RNA_def_function_ui_description(func, "Remove action group");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "action_group", "ActionGroup", "", "Action group to remove");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, ParameterFlag(0));
}
static void rna_def_action_fcurves(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
FunctionRNA *func;
PropertyRNA *parm;
RNA_def_property_srna(cprop, "ActionFCurves");
srna = RNA_def_struct(brna, "ActionFCurves", nullptr);
RNA_def_struct_sdna(srna, "bAction");
RNA_def_struct_ui_text(
srna,
"Action F-Curves",
"Collection of action F-Curves. Note that this is a legacy API that is unaware of action "
"slots, and will only consider the F-Curves for this action's first slot");
RNA_def_property_collection_funcs(cprop,
"rna_iterator_Action_fcurves_begin",
"rna_iterator_Action_fcurves_next",
"rna_iterator_Action_fcurves_end",
"rna_iterator_Action_fcurves_get",
"rna_iterator_Action_fcurves_length",
nullptr,
nullptr,
nullptr);
/* Action.fcurves.new(...) */
func = RNA_def_function(srna, "new", "rna_Action_fcurve_new");
RNA_def_function_ui_description(func,
"Add an F-Curve for the first slot of this action, creating the "
"necessary layer, strip, and slot if necessary");
RNA_def_function_flag(func, FUNC_USE_REPORTS | FUNC_USE_MAIN);
parm = RNA_def_string(func, "data_path", nullptr, 0, "Data Path", "F-Curve data path to use");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
RNA_def_int(func, "index", 0, 0, INT_MAX, "Index", "Array index", 0, INT_MAX);
RNA_def_string(
func, "action_group", nullptr, 0, "Action Group", "Acton group to add this F-Curve into");
parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "Newly created F-Curve");
RNA_def_function_return(func, parm);
/* Action.fcurves.find(...) */
func = RNA_def_function(srna, "find", "rna_Action_fcurve_find");
RNA_def_function_ui_description(
func,
"Find an F-Curve. Note that this function performs a linear scan "
"of all F-Curves for the action's first slot.");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
parm = RNA_def_string(func, "data_path", nullptr, 0, "Data Path", "F-Curve data path");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
RNA_def_int(func, "index", 0, 0, INT_MAX, "Index", "Array index", 0, INT_MAX);
parm = RNA_def_pointer(
func, "fcurve", "FCurve", "", "The found F-Curve, or None if it does not exist");
RNA_def_function_return(func, parm);
/* Action.fcurves.remove(...) */
func = RNA_def_function(srna, "remove", "rna_Action_fcurve_remove");
RNA_def_function_ui_description(func, "Remove the F-Curve from the action's first slot");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "F-Curve to remove");
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, ParameterFlag(0));
/* Action.fcurves.clear() */
func = RNA_def_function(srna, "clear", "rna_Action_fcurve_clear");
RNA_def_function_ui_description(func, "Remove all F-Curves from the action's first slot");
}
static void rna_def_action_pose_markers(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
@@ -2910,53 +2401,6 @@ static void rna_def_action_pose_markers(BlenderRNA *brna, PropertyRNA *cprop)
RNA_def_property_ui_text(prop, "Active Pose Marker Index", "Index of active pose marker");
}
/* Access to 'legacy' Action features, like the top-level F-Curves, the corresponding F-Curve
* groups, and the top-level id_root. */
static void rna_def_action_legacy(BlenderRNA *brna, StructRNA *srna)
{
PropertyRNA *prop;
/* collections */
prop = RNA_def_property(srna, "fcurves", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "curves", nullptr);
RNA_def_property_struct_type(prop, "FCurve");
RNA_def_property_ui_text(
prop,
"F-Curves",
"Legacy API, for backward compatibility with code that does not handle slotted actions yet. "
"This collection contains the F-Curves for the action's first slot");
rna_def_action_fcurves(brna, prop);
prop = RNA_def_property(srna, "groups", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, nullptr, "groups", nullptr);
RNA_def_property_struct_type(prop, "ActionGroup");
RNA_def_property_ui_text(
prop,
"Groups",
"Legacy API, for backward compatibility with code that does not handle slotted actions yet. "
"This collection contains the F-Curve groups for the action's first slot");
rna_def_action_groups(brna, prop);
/* special "type" limiter - should not really be edited in general,
* but is still available/editable in 'emergencies' */
prop = RNA_def_property(srna, "id_root", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "idroot");
RNA_def_property_enum_items(prop, default_ActionSlot_target_id_type_items);
RNA_def_property_enum_funcs(prop,
"rna_Action_id_root_get",
"rna_Action_id_root_set",
"rna_ActionSlot_target_id_type_itemf");
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN, "rna_Action_id_root_update");
RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT);
RNA_def_property_ui_text(
prop,
"ID Root Type",
"Legacy API, for backward compatibility with code that does not handle slotted actions yet. "
"Type of data-block that the action's first slot can be used on. Do not change unless you "
"know what you are doing");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
}
static void rna_def_action(BlenderRNA *brna)
{
StructRNA *srna;
@@ -3141,8 +2585,6 @@ static void rna_def_action(BlenderRNA *brna)
parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "The found or created F-Curve");
RNA_def_function_return(func, parm);
rna_def_action_legacy(brna, srna);
/* API calls */
RNA_api_action(srna);
}