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); + } }