From 446f0a806edfddd96c836a455bbcd5751dde83d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 19 Dec 2024 14:43:07 +0100 Subject: [PATCH] Anim: version Actions before versioning Action Assignments Split the versioning of legacy Actions to slotted ones into two steps: 1. Versioning the Actions themselves, in the regular versioning code (`do_versions_after_linking_400`). This has to happen in the 'after linking' stage, and not in the 'before linking' stage, as there's older 'after linking' code that will break when it gets fed slotted actions. Any ID that is using a legacy Action will get tagged. 2. Versioning Action assignments, where the correct Action Slot has to be chosen & assigned for each tagged ID. This has to happen in the `do_versions_after_setup` stage, as choosing a slot requires that the actions (both local & linked) have been converted already. This also includes some necessary changes to the pre-2.50 Action versioning code. Note that this change does not handle library overrides. That's dealt with in !131426. Pull Request: https://projects.blender.org/blender/blender/pulls/131627 --- source/blender/animrig/ANIM_action.hh | 6 +- source/blender/animrig/ANIM_versioning.hh | 41 +++- source/blender/animrig/CMakeLists.txt | 1 + source/blender/animrig/intern/versioning.cc | 182 +++++++++++++++++- .../blender/animrig/intern/versioning_test.cc | 10 + source/blender/blenkernel/BKE_ipo.h | 2 +- source/blender/blenkernel/intern/ipo.cc | 73 +++++-- source/blender/blenloader/BLO_readfile.hh | 14 ++ source/blender/blenloader/intern/readfile.cc | 24 +-- .../blenloader/intern/versioning_400.cc | 117 +---------- .../blenloader/intern/versioning_common.cc | 7 + 11 files changed, 317 insertions(+), 160 deletions(-) diff --git a/source/blender/animrig/ANIM_action.hh b/source/blender/animrig/ANIM_action.hh index 622e4c21d02..2c88844681a 100644 --- a/source/blender/animrig/ANIM_action.hh +++ b/source/blender/animrig/ANIM_action.hh @@ -61,12 +61,12 @@ class Slot; * Action. These methods will eventually be removed when runtime support for * legacy actions is fully removed. For code in blend file loading and * versioning, which will stick around for the long-term, use - * `BKE_action_is_layered()` instead. (Note that an empty Action is considered - * both a valid legacy *and* layered action.) + * `animrig::versioning::action_is_layered()` instead. (Note that an empty + * Action is considered both a valid legacy *and* layered action.) * * \see #AnimData::action * \see #AnimData::slot_handle - * \see #BKE_action_is_layered + * \see #animrig::versioning::action_is_layered() */ class Action : public ::bAction { public: diff --git a/source/blender/animrig/ANIM_versioning.hh b/source/blender/animrig/ANIM_versioning.hh index 4ffbc676f95..b3f17cd480b 100644 --- a/source/blender/animrig/ANIM_versioning.hh +++ b/source/blender/animrig/ANIM_versioning.hh @@ -6,13 +6,15 @@ /** \file * \ingroup animrig * - * \brief Versioning of old animation data. Most of this lives in the - * versioning_xxx.cc files, but some is broken out and placed here. + * \brief Versioning of old animation data. Most animation versioning code lives + * in the versioning_xxx.cc files, but some is broken out and placed here. */ struct bAction; struct BlendFileReadReport; +struct ID; struct Main; +struct ReportList; namespace blender::animrig { class Action; @@ -27,11 +29,10 @@ namespace blender::animrig::versioning { * during file read and versioning to determine how forward-compatible and * legacy data should be handled. * - * The handling of pre-Animato actions is _only_ done here in this function, in the - * `...::versioning` namespace, as that needs access to depreacted DNA fields. - * * NOTE: this is semi-duplicated from `Action::is_action_layered()`, but with - * tweaks to also recognize ultra-legacy (pre-Animato) data. + * tweaks to also recognize ultra-legacy (pre-Animato) data. Because this needs access to + * depreacted DNA fields, which is ok here in the versioning code, the other "is this legacy or + * layered?" functions do not check for pre-Animato data. * * \see Action::is_action_layered() */ @@ -42,7 +43,7 @@ bool action_is_layered(const bAction &dna_action); * * This function does *not* work on pre-Animato actions. */ -void convert_legacy_actions(Main &bmain); +void convert_legacy_animato_actions(Main &bmain); /** * Convert legacy (Animato) Action to slotted Action, in-place. @@ -52,6 +53,30 @@ void convert_legacy_actions(Main &bmain); * This always creates a slot and a layer for the Action, even when the Action doesn't actually * contain any animation data. This ensures that versioned Actions all look the same, and there's * just less variations to keep track of. */ -void convert_legacy_action(bAction &dna_action); +void convert_legacy_animato_action(bAction &dna_action); + +/** + * Go over all animated IDs, and tag them whenever they use a legacy Action. + * + * \see convert_legacy_action_assignments + */ +void tag_action_users_for_slotted_actions_conversion(Main &bmain); + +/** + * Tag this ID so it'll get its legacy Action assignment converted. + * + * \see convert_legacy_action_assignments + */ +void tag_action_user_for_slotted_actions_conversion(ID &animated_id); + +/** + * Convert the Action assignments of all animated IDs. + * + * For all IDs that use an Action, this also picks an Action Slot to ensure the ID is still + * animated. + * + * This only visits IDs tagged by #tag_action_users_for_slotted_actions_conversion. + */ +void convert_legacy_action_assignments(Main &bmain, ReportList *reports); } // namespace blender::animrig::versioning diff --git a/source/blender/animrig/CMakeLists.txt b/source/blender/animrig/CMakeLists.txt index 0b433eb8b93..32b996a5fbb 100644 --- a/source/blender/animrig/CMakeLists.txt +++ b/source/blender/animrig/CMakeLists.txt @@ -62,6 +62,7 @@ set(SRC set(LIB PRIVATE bf::blenkernel bf::blenlib + PRIVATE bf::blenloader PRIVATE bf::blentranslation PRIVATE bf::depsgraph bf::dna diff --git a/source/blender/animrig/intern/versioning.cc b/source/blender/animrig/intern/versioning.cc index 86ca87578be..80b568b5547 100644 --- a/source/blender/animrig/intern/versioning.cc +++ b/source/blender/animrig/intern/versioning.cc @@ -26,6 +26,8 @@ #include "BLT_translation.hh" +#include "BLO_readfile.hh" + namespace blender::animrig::versioning { constexpr const char *DEFAULT_VERSIONED_SLOT_NAME = "Legacy Slot"; @@ -42,26 +44,35 @@ bool action_is_layered(const bAction &dna_action) return has_layered_data || (!has_animato_data && !has_pre_animato_data); } -void convert_legacy_actions(Main &bmain) +void convert_legacy_animato_actions(Main &bmain) { LISTBASE_FOREACH (bAction *, dna_action, &bmain.actions) { blender::animrig::Action &action = dna_action->wrap(); - if (!action.is_action_legacy()) { + if (action_is_layered(action)) { /* This is just a safety net. Blender files that trigger this versioning code are not * expected to have any layered/slotted Actions. */ continue; } - convert_legacy_action(action); + /* This function should skip pre-2.50 Actions, as those are versioned in a special step (see + * `do_versions_after_setup()` in `versioning_common.cc`). */ + if (!BLI_listbase_is_empty(&action.chanbase)) { + continue; + } + + convert_legacy_animato_action(action); } } -void convert_legacy_action(bAction &dna_action) +void convert_legacy_animato_action(bAction &dna_action) { + BLI_assert_msg(BLI_listbase_is_empty(&dna_action.chanbase), + "this function cannot handle pre-2.50 Actions"); if (!BLI_listbase_is_empty(&dna_action.chanbase)) { - BLI_assert_msg(BLI_listbase_is_empty(&dna_action.chanbase), - "Cannot upgrade pre-2.5 Action to slotted Action"); + /* This is a pre-2.5 Action, which cannot be converted here. It's converted in another function + * to a post-2.5 Action (aka Animato Action), and after that, this function will be called + * again. */ return; } @@ -128,4 +139,163 @@ void convert_legacy_action(bAction &dna_action) action.groups = {nullptr, nullptr}; } +void tag_action_user_for_slotted_actions_conversion(ID &animated_id) +{ + animated_id.runtime.readfile_data->tags.action_assignment_needs_slot = true; +} + +void tag_action_users_for_slotted_actions_conversion(Main &bmain) +{ + /* This function is only called when the blendfile is old enough to NOT use + * slotted Actions, so we can safely tag anything that uses an Action. */ + + auto flag_adt = [](ID &animated_id, + bAction *& /*action_ptr_ref*/, + slot_handle_t & /*slot_handle_ref*/, + char * /*slot_name*/) -> bool { + tag_action_user_for_slotted_actions_conversion(animated_id); + + /* Once tagged, the foreach loop can stop, because more tagging of the same + * ID doesn't do anything. */ + return false; + }; + + ID *id; + FOREACH_MAIN_ID_BEGIN (&bmain, id) { + foreach_action_slot_use_with_references(*id, flag_adt); + + /* Process embedded IDs, as these are not listed in bmain, but still can + * have their own Action+Slot. Unfortunately there is no generic looper + * for embedded IDs. At this moment the only animatable embedded ID is a + * node tree. */ + bNodeTree *node_tree = blender::bke::node_tree_from_id(id); + if (node_tree) { + foreach_action_slot_use_with_references(node_tree->id, flag_adt); + } + } + FOREACH_MAIN_ID_END; +} + +void convert_legacy_action_assignments(Main &bmain, ReportList *reports) +{ + auto version_slot_assignment = [&](ID &animated_id, + bAction *&action_ptr_ref, + slot_handle_t &slot_handle_ref, + char *last_used_slot_identifier) { + BLI_assert(action_ptr_ref); /* Ensured by the foreach loop. */ + Action &action = action_ptr_ref->wrap(); + + if (action.slot_array_num == 0) { + /* animated_id is from an older file (because it is in the being-versioned-right-now bmain), + * and it's referring to an Action from an already-versioned library file. We know this + * because versioned legacy Actions always have a single slot called "Legacy Slot", and so + * this Action must have been opened in some Blender and had its slot removed. */ + + /* Another reason that there is no slot is that it was a _really_ old (pre-2.50) + * Action that should have been upgraded already. */ + BLI_assert_msg(BLI_listbase_is_empty(&action.chanbase), + "Did not expect pre-2.5 Action at this stage of the versioning code"); + + return true; + } + + /* Reset the "last used slot identifier" to the default "Legacy Slot". That way + * generic_slot_for_autoassign() will pick up on legacy slots automatically. + * + * Note that this function should only run on legacy users of Actions, i.e. they are not + * expected to have any last-used slot at all. The field in DNA can still be set, though, + * because the 4.3 code already has the data model for slotted Actions. */ + + /* Ensure that the identifier has the correct ID type prefix. */ + *reinterpret_cast(last_used_slot_identifier) = GS(animated_id.name); + + static_assert(Slot::identifier_length_max > 2); /* Because of the -2 below. */ + BLI_strncpy_utf8(last_used_slot_identifier + 2, + DATA_(DEFAULT_VERSIONED_SLOT_NAME), + Slot::identifier_length_max - 2); + + Slot *slot_to_assign = generic_slot_for_autoassign( + animated_id, action, last_used_slot_identifier); + if (!slot_to_assign) { + /* This means that there is no slot that can be found by name, not even the "Legacy Slot" + * name. Keep the ID unanimated, as this means that the referenced Action has changed + * significantly since this file was opened. */ + BKE_reportf(reports, + RPT_WARNING, + "\"%s\" is using Action \"%s\", which does not have a slot with identifier " + "\"%s\" or \"%s\". Manually assign the right action slot to \"%s\".\n", + animated_id.name, + action.id.name + 2, + last_used_slot_identifier, + animated_id.name, + animated_id.name + 2); + return true; + } + + const ActionSlotAssignmentResult result = generic_assign_action_slot( + slot_to_assign, animated_id, action_ptr_ref, slot_handle_ref, last_used_slot_identifier); + switch (result) { + case ActionSlotAssignmentResult::OK: + break; + case ActionSlotAssignmentResult::SlotNotSuitable: + /* If the slot wasn't suitable for the ID, we force assignment anyway, + * but with a warning. + * + * This happens when the legacy action assigned to the ID had a + * mismatched idroot, and therefore the created slot does as well. + * This mismatch can happen in a variety of ways, and we opt to + * preserve this unusual (but technically valid) state of affairs. + */ + slot_handle_ref = slot_to_assign->handle; + BLI_strncpy_utf8( + last_used_slot_identifier, slot_to_assign->identifier, Slot::identifier_length_max); + /* Not necessary to add this ID to the slot user list, as that list is + * going to get refreshed after versioning anyway. */ + + BKE_reportf( + reports, + RPT_WARNING, + "Legacy action \"%s\" is assigned to \"%s\", which does not match the " + "action's id_root \"%s\". The action has been upgraded to a slotted action with " + "slot \"%s\" with an id_type \"%s\", which has also been assigned to \"%s\" despite " + "this type mismatch. This likely indicates something odd about the blend file.\n", + action.id.name + 2, + animated_id.name, + slot_to_assign->identifier_prefix_for_idtype().c_str(), + slot_to_assign->identifier_without_prefix().c_str(), + slot_to_assign->identifier_prefix_for_idtype().c_str(), + animated_id.name); + break; + case ActionSlotAssignmentResult::SlotNotFromAction: + BLI_assert_msg(false, "SlotNotFromAction should not be returned here"); + break; + case ActionSlotAssignmentResult::MissingAction: + BLI_assert_msg(false, "MissingAction should not be returned here"); + break; + } + + return true; + }; + + ID *id; + FOREACH_MAIN_ID_BEGIN (&bmain, id) { + /* Process the ID itself. */ + if (BLO_readfile_id_runtime_tags(*id).action_assignment_needs_slot) { + foreach_action_slot_use_with_references(*id, version_slot_assignment); + id->runtime.readfile_data->tags.action_assignment_needs_slot = false; + } + + /* Process embedded IDs, as these are not listed in bmain, but still can + * have their own Action+Slot. Unfortunately there is no generic looper + * for embedded IDs. At this moment the only animatable embedded ID is a + * node tree. */ + bNodeTree *node_tree = blender::bke::node_tree_from_id(id); + if (node_tree && BLO_readfile_id_runtime_tags(node_tree->id).action_assignment_needs_slot) { + foreach_action_slot_use_with_references(node_tree->id, version_slot_assignment); + node_tree->id.runtime.readfile_data->tags.action_assignment_needs_slot = false; + } + } + FOREACH_MAIN_ID_END; +} + } // namespace blender::animrig::versioning diff --git a/source/blender/animrig/intern/versioning_test.cc b/source/blender/animrig/intern/versioning_test.cc index 87c55233996..10e989bb370 100644 --- a/source/blender/animrig/intern/versioning_test.cc +++ b/source/blender/animrig/intern/versioning_test.cc @@ -75,6 +75,16 @@ TEST(animrig_versioning, action_is_layered) EXPECT_TRUE(action_is_layered(action)) << "Layered Actions should be considered 'layered'"; } + { /* Layered Action as it exists on disk, with forward-compatible info in there. */ + bAction action = {{nullptr}}; + FCurve *fake_fcurve = nullptr; + action.layer_array_num = 1; + + BLI_addtail(&action.curves, &fake_fcurve); + EXPECT_TRUE(action_is_layered(action)) + << "Layered Actions with forward-compat data should be considered 'layered'"; + } + { /* Completely zero'ed out Action. */ bAction action = {{nullptr}}; EXPECT_TRUE(action_is_layered(action)) << "Zero'ed-out Actions should be considered 'layered'"; diff --git a/source/blender/blenkernel/BKE_ipo.h b/source/blender/blenkernel/BKE_ipo.h index d1fdb2fbe69..de8572885a5 100644 --- a/source/blender/blenkernel/BKE_ipo.h +++ b/source/blender/blenkernel/BKE_ipo.h @@ -20,7 +20,7 @@ struct Main; * Note: this *only* deals with animation data that is *pre-Animato*, and * upgrades it all the way past Animato to modern Layered Actions and drivers. * Actions that are already Animato actions are ignored, as they are versioned - * elsewhere (see `version_legacy_actions_to_layered()`). This is admittedly + * elsewhere (see `animrig::versioning::convert_legacy_actions()`). This is admittedly * weird, but it's due to the fact that versioning pre-Animato data requires * creating new datablocks, which must happen at a stage *after* the standard * versioning where the simpler Animato-to-Layered upgrades are done. diff --git a/source/blender/blenkernel/intern/ipo.cc b/source/blender/blenkernel/intern/ipo.cc index c110edf9eb5..cb9d06803f1 100644 --- a/source/blender/blenkernel/intern/ipo.cc +++ b/source/blender/blenkernel/intern/ipo.cc @@ -1792,7 +1792,6 @@ static void ipo_to_animato(ID *id, /** * Convert a pre-Animato Action to an Animato Action and drivers. * - * * New curves may not be converted directly into the given Action (i.e. for Actions linked * to Objects, where ob->ipo and ob->action need to be combined). * @@ -1878,8 +1877,13 @@ static void ensure_action_is_layered( convert_pre_animato_action_to_animato_action_in_place(id, act, groups, curves, drivers); } + /* If there is an animated ID, tag it so that its Action usage also will get converted. */ + if (id) { + blender::animrig::versioning::tag_action_user_for_slotted_actions_conversion(*id); + } + /* Convert to layered action. */ - blender::animrig::versioning::convert_legacy_action(*act); + blender::animrig::versioning::convert_legacy_animato_action(*act); } /* ------------------------- */ @@ -1946,7 +1950,8 @@ static void ipo_to_animdata( BLI_movelisttolist(&adt->action->curves, &anim); /* Upgrade Animato action to a layered action. */ - blender::animrig::versioning::convert_legacy_action(*adt->action); + blender::animrig::versioning::tag_action_user_for_slotted_actions_conversion(*id); + blender::animrig::versioning::convert_legacy_animato_action(*adt->action); } /* deal with drivers */ @@ -2128,7 +2133,7 @@ static bool seq_convert_callback(Strip *strip, void *userdata) /* convert IPO */ ipo_to_animdata(cd->bmain, (ID *)cd->scene, strip->ipo, nullptr, nullptr, strip); - if (cd->adt->action) { + if (cd->adt->action && !blender::animrig::versioning::action_is_layered(*cd->adt->action)) { cd->adt->action->idroot = ID_SCE; /* scene-rooted */ } @@ -2142,6 +2147,8 @@ static bool seq_convert_callback(Strip *strip, void *userdata) void do_versions_ipos_to_layered_actions(Main *bmain) { + using blender::animrig::versioning::action_is_layered; + ListBase drivers = {nullptr, nullptr}; ID *id; @@ -2150,6 +2157,45 @@ void do_versions_ipos_to_layered_actions(Main *bmain) return; } + /* Only convert if the bmain version is old enough. + * + * Note that the mixing of pre-2.50 and post-2.50 animation data is not supported, and so there + * is no need to check for the version of library blend files. + * + * See the check in #BKE_blendfile_link(), that actively warns users that their animation data + * will not be converted when linking to a pre-2.50 blend file. + * + * But even when the main file is newer, it could still link in pre-2.50 Actions, and those need + * to be converted in this function as well. + */ + if (bmain->versionfile >= 250) { + bool shown_info = false; + + LISTBASE_FOREACH (ID *, id, &bmain->actions) { + bAction *action = reinterpret_cast(id); + + const bool is_pre_animato_action = !BLI_listbase_is_empty(&action->chanbase); + if (!is_pre_animato_action) { + continue; + } + + if (G.debug & G_DEBUG) { + if (!shown_info) { + printf("INFO: Converting IPO Action to modern animation data types...\n"); + shown_info = true; + } + + printf("\tconverting action %s\n", id->name + 2); + } + + /* This Action will be object-only. */ + action->idroot = ID_OB; + + ensure_action_is_layered(nullptr, action, &action->groups, &action->curves, &drivers); + } + return; + } + if (G.debug & G_DEBUG) { printf("INFO: Converting IPO to modern animation data types...\n"); } @@ -2276,7 +2322,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* object's action will always be object-rooted */ { AnimData *adt = BKE_animdata_from_id(id); - if (adt && adt->action) { + if (adt && adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = ID_OB; } } @@ -2301,7 +2347,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* Convert Shape-key data... */ ipo_to_animdata(bmain, id, key->ipo, nullptr, nullptr, nullptr); - if (adt->action) { + if (adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = key->ipo->blocktype; } @@ -2326,7 +2372,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* Convert Material data... */ ipo_to_animdata(bmain, id, ma->ipo, nullptr, nullptr, nullptr); - if (adt->action) { + if (adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = ma->ipo->blocktype; } @@ -2351,7 +2397,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* Convert World data... */ ipo_to_animdata(bmain, id, wo->ipo, nullptr, nullptr, nullptr); - if (adt->action) { + if (adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = wo->ipo->blocktype; } @@ -2386,7 +2432,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* Convert Texture data... */ ipo_to_animdata(bmain, id, te->ipo, nullptr, nullptr, nullptr); - if (adt->action) { + if (adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = te->ipo->blocktype; } @@ -2411,7 +2457,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* Convert Camera data... */ ipo_to_animdata(bmain, id, ca->ipo, nullptr, nullptr, nullptr); - if (adt->action) { + if (adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = ca->ipo->blocktype; } @@ -2436,7 +2482,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* Convert Light data... */ ipo_to_animdata(bmain, id, la->ipo, nullptr, nullptr, nullptr); - if (adt->action) { + if (adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = la->ipo->blocktype; } @@ -2461,7 +2507,7 @@ void do_versions_ipos_to_layered_actions(Main *bmain) /* Convert Curve data... */ ipo_to_animdata(bmain, id, cu->ipo, nullptr, nullptr, nullptr); - if (adt->action) { + if (adt->action && !action_is_layered(*adt->action)) { adt->action->idroot = cu->ipo->blocktype; } @@ -2516,7 +2562,8 @@ void do_versions_ipos_to_layered_actions(Main *bmain) new_act->idroot = ipo->blocktype; /* Upgrade the resulting Animato action to a layered action. */ - blender::animrig::versioning::convert_legacy_action(*new_act); + blender::animrig::versioning::tag_action_user_for_slotted_actions_conversion(*id); + blender::animrig::versioning::convert_legacy_animato_action(*new_act); } /* clear fake-users, and set user-count to zero to make sure it is cleared on file-save */ diff --git a/source/blender/blenloader/BLO_readfile.hh b/source/blender/blenloader/BLO_readfile.hh index fdd10845f6e..0a358e4f1e2 100644 --- a/source/blender/blenloader/BLO_readfile.hh +++ b/source/blender/blenloader/BLO_readfile.hh @@ -545,9 +545,23 @@ short BLO_version_from_file(const char *filepath); struct ID_Readfile_Data { struct Tags { bool is_id_link_placeholder : 1; + + /** + * Set when this ID used a legacy Action, in which case it also should pick + * an appropriate slot. + * + * \see ANIM_versioning.hh + */ + bool action_assignment_needs_slot : 1; } tags; }; +/** + * Return `id->runtime.readfile_data->tags` if the `readfile_data` is allocated, + * otherwise return an all-zero set of tags. + */ +ID_Readfile_Data::Tags BLO_readfile_id_runtime_tags(ID &id); + /** * Free the ID_Readfile_Data of all IDs in this bmain and all their embedded IDs. * diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index f8e469a9052..06a83cf1241 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -2159,16 +2159,12 @@ static void readfile_id_runtime_data_ensure(ID &id) id.runtime.readfile_data = MEM_cnew(__func__); } -/** - * Return `id.runtime.readfile_data->tags` if the `readfile_data` is allocated, - * otherwise return an all-zero set of tags. - */ -static ID_Readfile_Data::Tags readfile_id_runtime_tags(ID *id) +ID_Readfile_Data::Tags BLO_readfile_id_runtime_tags(ID &id) { - if (!id->runtime.readfile_data) { + if (!id.runtime.readfile_data) { return ID_Readfile_Data::Tags{}; } - return id->runtime.readfile_data->tags; + return id.runtime.readfile_data->tags; } void BLO_readfile_id_runtime_data_free(ID &id) @@ -2253,7 +2249,7 @@ static void direct_link_id_common(BlendDataReader *reader, id->runtime.readfile_data->tags = id_read_tags; } - if (readfile_id_runtime_tags(id).is_id_link_placeholder) { + if (BLO_readfile_id_runtime_tags(*id).is_id_link_placeholder) { /* For placeholder we only need to set the tag and properly initialize generic ID fields above, * no further data to read. */ return; @@ -2565,7 +2561,7 @@ static bool direct_link_id(FileData *fd, /* Read part of datablock that is common between real and embedded datablocks. */ direct_link_id_common(&reader, main->curlib, id, id_old, tag, id_read_tags); - if (readfile_id_runtime_tags(id).is_id_link_placeholder) { + if (BLO_readfile_id_runtime_tags(*id).is_id_link_placeholder) { /* For placeholder we only need to set the tag, no further data to read. */ id->tag = tag; return true; @@ -4224,7 +4220,7 @@ static void expand_doit_library(void *fdhandle, Main *mainvar, void *old) else { /* Convert any previously read weak link to regular link * to signal that we want to read this data-block. */ - if (readfile_id_runtime_tags(id).is_id_link_placeholder) { + if (BLO_readfile_id_runtime_tags(*id).is_id_link_placeholder) { id->flag &= ~ID_FLAG_INDIRECT_WEAK_LINK; } @@ -4264,7 +4260,7 @@ static void expand_doit_library(void *fdhandle, Main *mainvar, void *old) /* Convert any previously read weak link to regular link to signal that we want to read this * data-block. Note that this function also visits already-loaded data-blocks, and thus their * `readfile_data` field might already have been freed. */ - if (readfile_id_runtime_tags(id).is_id_link_placeholder) { + if (BLO_readfile_id_runtime_tags(*id).is_id_link_placeholder) { id->flag &= ~ID_FLAG_INDIRECT_WEAK_LINK; } @@ -4680,7 +4676,7 @@ static int has_linked_ids_to_read(Main *mainvar) while (a--) { LISTBASE_FOREACH (ID *, id, lbarray[a]) { - if (readfile_id_runtime_tags(id).is_id_link_placeholder && + if (BLO_readfile_id_runtime_tags(*id).is_id_link_placeholder && !(id->flag & ID_FLAG_INDIRECT_WEAK_LINK)) { return true; @@ -4758,7 +4754,7 @@ static void read_library_linked_ids(FileData *basefd, while (id) { ID *id_next = static_cast(id->next); - if (readfile_id_runtime_tags(id).is_id_link_placeholder && + if (BLO_readfile_id_runtime_tags(*id).is_id_link_placeholder && !(id->flag & ID_FLAG_INDIRECT_WEAK_LINK)) { BLI_remlink(lbarray[a], id); @@ -4822,7 +4818,7 @@ static void read_library_clear_weak_links(FileData *basefd, ListBase *mainlist, /* This function also visits already-loaded data-blocks, and thus their * `readfile_data` field might already have been freed. */ - if (readfile_id_runtime_tags(id).is_id_link_placeholder && + if (BLO_readfile_id_runtime_tags(*id).is_id_link_placeholder && (id->flag & ID_FLAG_INDIRECT_WEAK_LINK)) { CLOG_INFO(&LOG, 3, "Dropping weak link to '%s'", id->name); diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 8cc2a9ded4f..f1d574e36e4 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -122,120 +122,6 @@ static void version_composite_nodetree_null_id(bNodeTree *ntree, Scene *scene) } } -static void version_legacy_actions_to_layered(Main *bmain) -{ - using namespace blender::animrig; - - struct ActionUserInfo { - ID *id; - slot_handle_t *slot_handle; - bAction **action_ptr_ptr; - char *slot_name; - }; - - blender::Map> action_users; - LISTBASE_FOREACH (bAction *, dna_action, &bmain->actions) { - Action &action = dna_action->wrap(); - - if (versioning::action_is_layered(action)) { - continue; - } - action_users.add(dna_action, {}); - } - - auto callback = [&](ID &animated_id, - bAction *&action_ptr_ref, - slot_handle_t &slot_handle_ref, - char *slot_name) -> bool { - blender::Vector *action_user_vector = action_users.lookup_ptr(action_ptr_ref); - /* Only actions that need to be converted are in this map. */ - if (!action_user_vector) { - return true; - } - ActionUserInfo user_info; - user_info.id = &animated_id; - user_info.action_ptr_ptr = &action_ptr_ref; - user_info.slot_handle = &slot_handle_ref; - user_info.slot_name = slot_name; - action_user_vector->append(user_info); - return true; - }; - - ID *id; - FOREACH_MAIN_ID_BEGIN (bmain, id) { - /* Process the ID itself. */ - foreach_action_slot_use_with_references(*id, callback); - - /* Process embedded IDs, as these are not listed in bmain, but still can - * have their own Action+Slot. Unfortunately there is no generic looper - * for embedded IDs. At this moment the only animatable embedded ID is a - * node tree. */ - bNodeTree *node_tree = blender::bke::node_tree_from_id(id); - if (node_tree) { - foreach_action_slot_use_with_references(node_tree->id, callback); - } - } - FOREACH_MAIN_ID_END; - - for (const auto &item : action_users.items()) { - Action &action = item.key->wrap(); - - /* Skip all handling of pre-Animato actions. These are handled in a later - * versioning step. See `do_versions_ipos_to_layered_actions()`. */ - if (!BLI_listbase_is_empty(&action.chanbase)) { - continue; - } - - versioning::convert_legacy_action(action); - blender::Vector &user_infos = item.value; - Slot &slot_to_assign = *action.slot(0); - - for (ActionUserInfo &action_user : user_infos) { - const ActionSlotAssignmentResult result = generic_assign_action_slot( - &slot_to_assign, - *action_user.id, - *action_user.action_ptr_ptr, - *action_user.slot_handle, - action_user.slot_name); - switch (result) { - case ActionSlotAssignmentResult::OK: - break; - case ActionSlotAssignmentResult::SlotNotSuitable: - /* If the slot wasn't suitable for the ID, we force assignment anyway, - * but with a warning. - * - * This happens when the legacy action assigned to the ID had a - * mismatched idroot, and therefore the created slot does as well. - * This mismatch can happen in a variety of ways, and we opt to - * preserve this unusual (but technically valid) state of affairs. - */ - *action_user.slot_handle = slot_to_assign.handle; - BLI_strncpy_utf8( - action_user.slot_name, slot_to_assign.identifier, Slot::identifier_length_max); - - printf( - "Warning: legacy action \"%s\" is assigned to \"%s\", which does not match the " - "action's id_root \"%s\". The action has been upgraded to a slotted action with " - "slot \"%s\" with an id_type \"%s\", which has also been assigned to \"%s\" despite " - "this type mismatch. This likely indicates something odd about the blend file.\n", - action.id.name + 2, - action_user.id->name, - slot_to_assign.identifier_prefix_for_idtype().c_str(), - slot_to_assign.identifier_without_prefix().c_str(), - slot_to_assign.identifier_prefix_for_idtype().c_str(), - action_user.id->name); - break; - case ActionSlotAssignmentResult::SlotNotFromAction: - BLI_assert(!"SlotNotFromAction should not be returned here"); - break; - case ActionSlotAssignmentResult::MissingAction: - BLI_assert(!"MissingAction should not be returned here"); - break; - } - } - } -} - static void version_fcurve_noise_modifier(FCurve &fcurve) { LISTBASE_FOREACH (FModifier *, fcurve_modifier, &fcurve.modifiers) { @@ -1415,7 +1301,8 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain) } if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 2)) { - version_legacy_actions_to_layered(bmain); + blender::animrig::versioning::convert_legacy_animato_actions(*bmain); + blender::animrig::versioning::tag_action_users_for_slotted_actions_conversion(*bmain); } if (!MAIN_VERSION_FILE_ATLEAST(bmain, 404, 7)) { diff --git a/source/blender/blenloader/intern/versioning_common.cc b/source/blender/blenloader/intern/versioning_common.cc index c13a3c07f96..7017a3d5e96 100644 --- a/source/blender/blenloader/intern/versioning_common.cc +++ b/source/blender/blenloader/intern/versioning_common.cc @@ -33,6 +33,8 @@ #include "BKE_node_tree_update.hh" #include "BKE_screen.hh" +#include "ANIM_versioning.hh" + #include "NOD_socket.hh" #include "BLT_translation.hh" @@ -679,4 +681,9 @@ void do_versions_after_setup(Main *new_bmain, /* Convert all the legacy grease pencil objects. This does not touch annotations. */ blender::bke::greasepencil::convert::legacy_main(*new_bmain, lapp_context, *reports); } + + if (!blendfile_or_libraries_versions_atleast(new_bmain, 404, 2)) { + /* Version all the action assignments of just-versioned datablocks. */ + blender::animrig::versioning::convert_legacy_action_assignments(*new_bmain, reports->reports); + } }