Anim: update slot selection for Action Constraint

Like with NLA strips, Action assignment on Action Constraints needs to
have an extra step (compared to regular assignment to animated
data-blocks).

For the Action Constraint, the auto slot selection gets one more
fallback option (compared to the generic code). This is to support the
following scenario, which used to be necessary as a workaround for a bug
in Blender (#127976):

- Python script creates an Action,
- assigns it to the animated object,
- unassigns it from that object,
- and assigns it to the object's Action Constraint.

The generic code doesn't work for this. The first assignment would see
the slot `XXSlot`, and because it has never been used, just use it. This
would change its name to `OBSlot`. The assignment to the Action
Constraint would not see a 'virgin' slot, and thus not auto-select
`OBSlot`. This behaviour makes sense when assigning Actions in the
Action editor (it shouldn't automatically pick the first slot of
matching ID type), but for the Action Constraint I (Sybren) feel that it
could be a bit more 'enthousiastic' in auto-picking a slot.

Note that this is the same behaviour as for NLA strips, albeit for a
slightly different reason. Because of that it's not sharing code with
the NLA.

Pull Request: https://projects.blender.org/blender/blender/pulls/128892
This commit is contained in:
Sybren A. Stüvel
2024-10-11 15:29:31 +02:00
committed by Gitea
parent 76bddb41df
commit d00e6e7353
2 changed files with 96 additions and 2 deletions

View File

@@ -712,6 +712,67 @@ static void rna_ActionConstraint_minmax_range(
}
}
static void rna_ActionConstraint_action_set(PointerRNA *ptr, PointerRNA value, ReportList *reports)
{
using namespace blender::animrig;
BLI_assert(ptr->owner_id);
BLI_assert(ptr->data);
ID &animated_id = *ptr->owner_id;
bConstraint *con = static_cast<bConstraint *>(ptr->data);
bActionConstraint *acon = static_cast<bActionConstraint *>(con->data);
Action *action = static_cast<Action *>(value.data);
if (!action) {
const bool ok = generic_assign_action(
animated_id, nullptr, acon->act, acon->action_slot_handle, acon->action_slot_name);
BLI_assert_msg(ok, "Un-assigning an Action from an Action Constraint should always work.");
UNUSED_VARS_NDEBUG(ok);
return;
}
const bool ok = generic_assign_action(
animated_id, action, acon->act, acon->action_slot_handle, acon->action_slot_name);
if (!ok) {
BKE_reportf(reports,
RPT_ERROR,
"Could not assign action %s to Action Constraint %s",
action->id.name + 2,
con->name);
return;
}
/* For the Action Constraint, the auto slot selection gets one more fallback
* option (compared to the generic code). This is to support the following
* scenario, which used to be necessary as a workaround for a bug in Blender (#127976):
*
* - Python script creates an Action,
* - assigns it to the animated object,
* - unassigns it from that object,
* - and assigns it to the object's Action Constraint.
*
* The generic code doesn't work for this. The first assignment would see the slot
* `XXSlot`, and because it has never been used, just use it. This would change its name to
* `OBSlot`. The assignment to the Action Constraint would not see a 'virgin' slot, and thus not
* auto-select `OBSlot`. This behaviour makes sense when assigning Actions in the Action editor
* (it shouldn't automatically pick the first slot of matching ID type), but for the Action
* Constraint I (Sybren) feel that it could be a bit more 'enthousiastic' in auto-picking a slot.
*
* Note that this is the same behaviour as for NLA strips, albeit for a slightly different
* reason. Because of that it's not sharing code with the NLA.
*/
if (acon->action_slot_handle == Slot::unassigned && action->slots().size() == 1) {
Slot *first_slot = action->slot(0);
if (first_slot->is_suitable_for(animated_id)) {
const ActionSlotAssignmentResult result = generic_assign_action_slot(
first_slot, animated_id, acon->act, acon->action_slot_handle, acon->action_slot_name);
BLI_assert(result == ActionSlotAssignmentResult::OK);
UNUSED_VARS_NDEBUG(result);
}
}
}
# ifdef WITH_ANIM_BAKLAVA
static void rna_ActionConstraint_action_slot_handle_set(
PointerRNA *ptr, const blender::animrig::slot_handle_t new_slot_handle)
@@ -1902,9 +1963,10 @@ static void rna_def_constraint_action(BlenderRNA *brna)
prop = RNA_def_property(srna, "action", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "act");
RNA_def_property_pointer_funcs(prop, nullptr, nullptr, nullptr, "rna_Action_id_poll");
RNA_def_property_pointer_funcs(
prop, nullptr, "rna_ActionConstraint_action_set", nullptr, "rna_Action_id_poll");
RNA_def_property_ui_text(prop, "Action", "The constraining action");
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
# ifdef WITH_ANIM_BAKLAVA

View File

@@ -11,6 +11,7 @@ import sys
import unittest
import bpy
from bpy.types import Constraint
from mathutils import Matrix
@@ -439,6 +440,37 @@ class CopyTransformsTest(AbstractConstraintTests):
)))
class ActionConstraintTest(AbstractConstraintTests):
layer_collection = "Action"
def setUp(self):
bpy.context.preferences.view.show_developer_ui = True
bpy.context.preferences.experimental.use_animation_baklava = True
return super().setUp()
def tearDown(self) -> None:
bpy.context.preferences.view.show_developer_ui = False
bpy.context.preferences.experimental.use_animation_baklava = False
return super().tearDown()
def constraint(self) -> Constraint:
owner = bpy.context.scene.objects["Action.owner"]
constraint = owner.constraints["Action"]
return constraint
def test_assign_action_slot_virgin(self):
action = bpy.data.actions.new("Slotted")
slot = action.slots.new()
con = self.constraint()
con.action = action
self.assertEqual(
slot,
con.action_slot,
"Assigning an Action with a virgin slot should automatically select that slot")
def main():
global args
import argparse