Anim: Add backward-compatible RNA API for Action.groups
The `Action.groups` RNA functions now work with layered Actions as well. They just expose / operate on the groups of the channelbag that belongs to the first slot of the Action. Pull Request: https://projects.blender.org/blender/blender/pulls/127241
This commit is contained in:
@@ -825,25 +825,122 @@ static bool use_backward_compatible_api(animrig::Action &action)
|
||||
return (USER_EXPERIMENTAL_TEST(&U, use_animation_baklava) && action.is_empty()) ||
|
||||
!action.is_action_legacy();
|
||||
}
|
||||
# endif /* WITH_ANIM_BAKLAVA */
|
||||
|
||||
static bActionGroup *rna_Action_groups_new(bAction *act, ReportList *reports, const char name[])
|
||||
static void rna_iterator_Action_groups_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
|
||||
{
|
||||
if (!act->wrap().is_action_legacy()) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"Cannot add legacy Action Groups to a layered Action '%s'. Convert it to a legacy "
|
||||
"Action first.",
|
||||
act->id.name + 2);
|
||||
return nullptr;
|
||||
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, channelbag->channel_groups());
|
||||
}
|
||||
else {
|
||||
rna_iterator_listbase_begin(iter, &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 action_groups_add_new(act, name);
|
||||
return RNA_pointer_create(&action.id, &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);
|
||||
}
|
||||
|
||||
# endif /* WITH_ANIM_BAKLAVA */
|
||||
|
||||
static bActionGroup *rna_Action_groups_new(bAction *act, const char name[])
|
||||
{
|
||||
bActionGroup *group;
|
||||
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
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);
|
||||
}
|
||||
# else
|
||||
group = action_groups_add_new(act, name);
|
||||
# endif
|
||||
|
||||
/* 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. */
|
||||
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
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;
|
||||
}
|
||||
# endif
|
||||
|
||||
FCurve *fcu, *fcn;
|
||||
|
||||
/* try to remove the F-Curve from the action */
|
||||
@@ -2336,9 +2433,19 @@ static void rna_def_action_groups(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
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");
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
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);
|
||||
# endif
|
||||
|
||||
func = RNA_def_function(srna, "new", "rna_Action_groups_new");
|
||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||
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);
|
||||
|
||||
@@ -224,6 +224,31 @@ class LegacyAPIOnLayeredActionTest(unittest.TestCase):
|
||||
# After this, there is no need to test the rest of the functions, as the
|
||||
# Action will be in the same state as in test_fcurves_on_layered_action().
|
||||
|
||||
def test_groups(self) -> None:
|
||||
# Create a group by using the legacy API to create an F-Curve with group name.
|
||||
group_name = "Object Transfoibles"
|
||||
self.action.fcurves.new("scale", index=1, action_group=group_name)
|
||||
|
||||
layer = self.action.layers[0]
|
||||
strip = layer.strips[0]
|
||||
channelbag = strip.channelbags[0]
|
||||
|
||||
self.assertEqual(1, len(channelbag.groups), "The new group should be available on the channelbag")
|
||||
self.assertEqual(group_name, channelbag.groups[0].name)
|
||||
self.assertEqual(1, len(self.action.groups), "The new group should be available with the legacy group API")
|
||||
self.assertEqual(group_name, self.action.groups[0].name)
|
||||
|
||||
# Create a group via the legacy API.
|
||||
group = self.action.groups.new(group_name)
|
||||
self.assertEqual("{}.001".format(group_name), group.name, "The group should have a unique name")
|
||||
self.assertEqual(group, self.action.groups[1], "The group should be accessible via the legacy API")
|
||||
self.assertEqual(group, channelbag.groups[1], "The group should be accessible via the channelbag")
|
||||
|
||||
# Remove a group via the legacy API.
|
||||
self.action.groups.remove(group)
|
||||
self.assertNotIn(group, self.action.groups[:], "A group should be removable via the legacy API")
|
||||
self.assertNotIn(group, channelbag.groups[:], "A group should be removable via the legacy API")
|
||||
|
||||
|
||||
class TestLegacyLayered(unittest.TestCase):
|
||||
"""Test boundaries between legacy & layered Actions.
|
||||
@@ -253,20 +278,6 @@ class TestLegacyLayered(unittest.TestCase):
|
||||
act.slots.new()
|
||||
self.assertSequenceEqual([], act.slots)
|
||||
|
||||
def test_layered_action(self) -> None:
|
||||
"""Test legacy operations on a layered Action"""
|
||||
|
||||
act = bpy.data.actions.new('LayeredAction')
|
||||
act.layers.new("laagje") # Add a layer to make this a non-empty legacy Action.
|
||||
self.assertFalse(act.is_action_legacy)
|
||||
self.assertTrue(act.is_action_layered)
|
||||
self.assertFalse(act.is_empty)
|
||||
|
||||
# Adding an ActionGroup should be prevented, at least until grouping is supported.
|
||||
with self.assertRaises(RuntimeError):
|
||||
act.groups.new("groepie")
|
||||
self.assertSequenceEqual([], act.groups)
|
||||
|
||||
|
||||
class ChannelBagsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user