mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Anim: Replace Action uses with new action
This patch adds a new operator to replace all uses of a given animation with a newly created one. The operator can be found just below the "Replace Action" operator in the Action editor (Action dropdown) and in the 3D viewport (Object or Pose -> Animation) I extracted the common code with `ANIM_OT_replace_action` into common functions Part of #150572 Pull Request: https://projects.blender.org/blender/blender/pulls/153812
This commit is contained in:
parent
cce2a2dc1d
commit
1a8298109f
4 changed files with 153 additions and 44 deletions
|
|
@ -645,6 +645,7 @@ class DOPESHEET_MT_action(Menu):
|
|||
layout.operator("anim.merge_animation")
|
||||
layout.operator("anim.separate_slots")
|
||||
layout.operator("anim.replace_action")
|
||||
layout.operator("anim.replace_action_new")
|
||||
|
||||
layout.separator()
|
||||
layout.operator("anim.slot_channels_move_to_new_action")
|
||||
|
|
|
|||
|
|
@ -2869,6 +2869,7 @@ class VIEW3D_MT_object_animation(Menu):
|
|||
layout.operator("nla.bake", text="Bake Action...")
|
||||
layout.operator("grease_pencil.bake_grease_pencil_animation", text="Bake Object Transform to Grease Pencil...")
|
||||
layout.operator("anim.replace_action")
|
||||
layout.operator("anim.replace_action_new")
|
||||
|
||||
|
||||
class VIEW3D_MT_object_rigid_body(Menu):
|
||||
|
|
|
|||
|
|
@ -1329,6 +1329,64 @@ static void ANIM_OT_merge_animation(wmOperatorType *ot)
|
|||
/** \name Replace Animation
|
||||
* \{ */
|
||||
|
||||
static bool replace_action_common_poll(bContext *C)
|
||||
{
|
||||
Object *active_object = CTX_data_active_object(C);
|
||||
if (!active_object) {
|
||||
return false;
|
||||
}
|
||||
AnimData *adt = BKE_animdata_from_id(&active_object->id);
|
||||
if (!adt || !adt->action) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void replace_action_common_failure_report(const Span<ID *> failures, ReportList &reports)
|
||||
{
|
||||
if (failures.size() == 0) {
|
||||
return;
|
||||
}
|
||||
std::string report = "Replacing the action failed on: ";
|
||||
for (ID *id : failures) {
|
||||
report += id->name + 2;
|
||||
}
|
||||
BKE_report(&reports, RPT_WARNING, report.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through all IDs in Main and replaces any uses of `old_action` in AnimData.action with
|
||||
* `new_action`.
|
||||
*/
|
||||
static Vector<ID *> replace_action(Main &bmain,
|
||||
const animrig::Action &old_action,
|
||||
animrig::Action &new_action)
|
||||
{
|
||||
Vector<ID *> failures;
|
||||
ID *id;
|
||||
/* Cannot use the Action Slot user map because some action assignments may be missing a slot
|
||||
* assignment and those should also be remapped. */
|
||||
FOREACH_MAIN_ID_BEGIN (&bmain, id) {
|
||||
AnimData *adt = BKE_animdata_from_id(id);
|
||||
if (!adt || !adt->action || adt->action != &old_action) {
|
||||
continue;
|
||||
}
|
||||
if (!ID_IS_EDITABLE(id) && !ID_IS_OVERRIDE_LIBRARY(id)) {
|
||||
continue;
|
||||
}
|
||||
const bool success = animrig::assign_action(&new_action, {*id, *adt});
|
||||
DEG_id_tag_update(id, ID_RECALC_ALL);
|
||||
if (!success) {
|
||||
failures.append(id);
|
||||
}
|
||||
}
|
||||
FOREACH_MAIN_ID_END;
|
||||
|
||||
DEG_relations_tag_update(&bmain);
|
||||
DEG_id_tag_update(&new_action.id, ID_RECALC_ALL);
|
||||
return failures;
|
||||
}
|
||||
|
||||
static wmOperatorStatus replace_action_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
|
|
@ -1348,36 +1406,8 @@ static wmOperatorStatus replace_action_exec(bContext *C, wmOperator *op)
|
|||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
Vector<ID *> failures;
|
||||
ID *id;
|
||||
/* Cannot use the Action Slot user map because some action assignments may be missing a slot
|
||||
* assignment and those should also be remapped. */
|
||||
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
||||
AnimData *adt = BKE_animdata_from_id(id);
|
||||
if (!adt || !adt->action || adt->action != old_action) {
|
||||
continue;
|
||||
}
|
||||
if (!ID_IS_EDITABLE(id) && !ID_IS_OVERRIDE_LIBRARY(id)) {
|
||||
continue;
|
||||
}
|
||||
const bool success = animrig::assign_action(new_action, {*id, *adt});
|
||||
DEG_id_tag_update(id, ID_RECALC_ALL);
|
||||
if (!success) {
|
||||
failures.append(id);
|
||||
}
|
||||
}
|
||||
FOREACH_MAIN_ID_END;
|
||||
|
||||
if (failures.size() > 0) {
|
||||
std::string report = "Replacing the action failed on: ";
|
||||
for (ID *id : failures) {
|
||||
report += id->name + 2;
|
||||
}
|
||||
BKE_report(op->reports, RPT_WARNING, report.c_str());
|
||||
}
|
||||
|
||||
DEG_relations_tag_update(bmain);
|
||||
DEG_id_tag_update(&new_action->id, ID_RECALC_ALL);
|
||||
Vector<ID *> failures = replace_action(*bmain, old_action->wrap(), new_action->wrap());
|
||||
replace_action_common_failure_report(failures, *op->reports);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, nullptr);
|
||||
|
||||
|
|
@ -1401,19 +1431,6 @@ static wmOperatorStatus replace_action_invoke(bContext *C,
|
|||
return WM_operator_props_dialog_popup(C, op, 400, IFACE_("Replace Action"), IFACE_("Replace"));
|
||||
}
|
||||
|
||||
static bool replace_action_poll(bContext *C)
|
||||
{
|
||||
Object *active_object = CTX_data_active_object(C);
|
||||
if (!active_object) {
|
||||
return false;
|
||||
}
|
||||
AnimData *adt = BKE_animdata_from_id(&active_object->id);
|
||||
if (!adt || !adt->action) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void replace_action_ui(bContext *C, wmOperator *op)
|
||||
{
|
||||
ui::Layout &layout = *op->layout;
|
||||
|
|
@ -1434,7 +1451,7 @@ static void ANIM_OT_replace_action(wmOperatorType *ot)
|
|||
ot->invoke = replace_action_invoke;
|
||||
ot->exec = replace_action_exec;
|
||||
ot->ui = replace_action_ui;
|
||||
ot->poll = replace_action_poll;
|
||||
ot->poll = replace_action_common_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
|
|
@ -1461,6 +1478,75 @@ static void ANIM_OT_replace_action(wmOperatorType *ot)
|
|||
0);
|
||||
}
|
||||
|
||||
static wmOperatorStatus replace_action_new_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
const uint32_t old_session_uid = RNA_int_get(op->ptr, "old_session_uid");
|
||||
bAction *old_dna_action = reinterpret_cast<bAction *>(
|
||||
BKE_libblock_find_session_uid(bmain, ID_AC, old_session_uid));
|
||||
|
||||
if (!old_dna_action) {
|
||||
BKE_report(op->reports, RPT_ERROR_INVALID_INPUT, "Invalid UID for old Action");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
animrig::Action &old_action = old_dna_action->wrap();
|
||||
animrig::Action &new_action = animrig::action_add(*bmain, DATA_("Action"));
|
||||
for (animrig::Slot *old_slot : old_action.slots()) {
|
||||
animrig::Slot &new_slot = new_action.slot_add();
|
||||
new_action.slot_identifier_define(new_slot, old_slot->identifier);
|
||||
}
|
||||
|
||||
Vector<ID *> failures = replace_action(*bmain, old_action, new_action);
|
||||
replace_action_common_failure_report(failures, *op->reports);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_NLA_ACTCHANGE, nullptr);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static wmOperatorStatus replace_action_new_invoke(bContext *C,
|
||||
wmOperator *op,
|
||||
const blender::wmEvent * /* event */)
|
||||
{
|
||||
Object *active_object = CTX_data_active_object(C);
|
||||
BLI_assert(active_object != nullptr);
|
||||
AnimData *adt = BKE_animdata_from_id(&active_object->id);
|
||||
bAction *dna_action = adt->action;
|
||||
BLI_assert(dna_action != nullptr);
|
||||
RNA_int_set(op->ptr, "old_session_uid", int(dna_action->id.session_uid));
|
||||
|
||||
return replace_action_new_exec(C, op);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like ANIM_OT_replace_action but creates a new action to assign to all users of the given action.
|
||||
*/
|
||||
static void ANIM_OT_replace_action_new(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Replace with new Action";
|
||||
ot->idname = "ANIM_OT_replace_action_new";
|
||||
ot->description =
|
||||
"Swap all users of one action to a new action. This ignores the NLA and Action Constraints";
|
||||
|
||||
ot->invoke = replace_action_new_invoke;
|
||||
ot->exec = replace_action_new_exec;
|
||||
ot->poll = replace_action_common_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
PropertyRNA *prop = RNA_def_int(ot->srna,
|
||||
"old_session_uid",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"Old Action",
|
||||
"Old Action's session uid to replace",
|
||||
0,
|
||||
0);
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
@ -1516,6 +1602,7 @@ void ED_operatortypes_anim()
|
|||
|
||||
WM_operatortype_append(ANIM_OT_merge_animation);
|
||||
WM_operatortype_append(ANIM_OT_replace_action);
|
||||
WM_operatortype_append(ANIM_OT_replace_action_new);
|
||||
|
||||
WM_operatortype_append(ed::animrig::POSELIB_OT_create_pose_asset);
|
||||
WM_operatortype_append(ed::animrig::POSELIB_OT_asset_modify);
|
||||
|
|
|
|||
|
|
@ -866,6 +866,26 @@ class ReplaceAction(unittest.TestCase):
|
|||
self.assertEqual(self.obj_nla.animation_data.action, None)
|
||||
self.assertEqual(self.obj_nla.animation_data.nla_tracks[0].strips[0].action, self.initial_action)
|
||||
|
||||
def test_action_remap_new(self):
|
||||
self.assertEqual(self.obj_animated.animation_data.action, self.initial_action)
|
||||
|
||||
bpy.ops.anim.replace_action_new(
|
||||
old_session_uid=self.initial_action.session_uid)
|
||||
|
||||
new_action = bpy.data.actions["Action"]
|
||||
self.assertEqual(self.obj_animated.animation_data.action, new_action)
|
||||
# The new action should have the same slots as the previous one.
|
||||
self.assertNotEqual(self.obj_animated.animation_data.action_slot, None)
|
||||
self.assertEqual(self.obj_no_slot_after.animation_data.action, new_action)
|
||||
self.assertEqual(self.obj_no_slot_before.animation_data.action, new_action)
|
||||
self.assertEqual(self.armature.animation_data.action, new_action)
|
||||
|
||||
# The operator shouldn't touch the NLA or action constraints.
|
||||
self.assertEqual(self.obj_action_constraint.animation_data.action, None)
|
||||
self.assertEqual(self.obj_action_constraint.constraints[0].action, self.initial_action)
|
||||
self.assertEqual(self.obj_nla.animation_data.action, None)
|
||||
self.assertEqual(self.obj_nla.animation_data.nla_tracks[0].strips[0].action, self.initial_action)
|
||||
|
||||
|
||||
def main():
|
||||
global args
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue