diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h index aa8295ee0ee..335057506a5 100644 --- a/source/blender/blenkernel/BKE_nla.h +++ b/source/blender/blenkernel/BKE_nla.h @@ -394,6 +394,12 @@ struct NlaStrip *BKE_nlastrip_find_active(struct NlaTrack *nlt); * Make the given NLA-Strip the active one within the given block. */ void BKE_nlastrip_set_active(struct AnimData *adt, struct NlaStrip *strip); +/** + * Find the NLA-strip with the given name within the given track. + * + * \return pointer to the strip, or nullptr when not found. + */ +struct NlaStrip *BKE_nlastrip_find_by_name(struct NlaTrack *nlt, const char *name); /** * Does the given NLA-strip fall within the given bounds (times)?. diff --git a/source/blender/blenkernel/intern/nla.cc b/source/blender/blenkernel/intern/nla.cc index 56c1d10cd3c..36b6ff904e8 100644 --- a/source/blender/blenkernel/intern/nla.cc +++ b/source/blender/blenkernel/intern/nla.cc @@ -1457,6 +1457,31 @@ void BKE_nlastrip_set_active(AnimData *adt, NlaStrip *strip) } } +static NlaStrip *nlastrip_find_by_name(ListBase /* NlaStrip */ *strips, const char *name) +{ + LISTBASE_FOREACH (NlaStrip *, strip, strips) { + if (STREQ(strip->name, name)) { + return strip; + } + + if (strip->type != NLASTRIP_TYPE_META) { + continue; + } + + NlaStrip *inner_strip = nlastrip_find_by_name(&strip->strips, name); + if (inner_strip != nullptr) { + return inner_strip; + } + } + + return nullptr; +} + +NlaStrip *BKE_nlastrip_find_by_name(NlaTrack *nlt, const char *name) +{ + return nlastrip_find_by_name(&nlt->strips, name); +} + bool BKE_nlastrip_within_bounds(NlaStrip *strip, float min, float max) { const float stripLen = (strip) ? strip->end - strip->start : 0.0f; diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index af92f7cf3dd..d83842e44bf 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -14,6 +14,7 @@ /* Define macros in `DNA_genfile.h`. */ #define DNA_GENFILE_VERSIONING_MACROS +#include "DNA_anim_types.h" #include "DNA_brush_types.h" #include "DNA_camera_types.h" #include "DNA_curve_types.h" @@ -55,6 +56,7 @@ #include "BKE_main.hh" #include "BKE_material.h" #include "BKE_mesh_legacy_convert.hh" +#include "BKE_nla.h" #include "BKE_node_runtime.hh" #include "BKE_scene.hh" #include "BKE_tracking.h" @@ -349,6 +351,51 @@ static void versioning_replace_splitviewer(bNodeTree *ntree) } } +/** + * Exit NLA tweakmode when the AnimData struct has insufficient information. + * + * When NLA tweakmode is enabled, Blender expects certain pointers to be set up + * correctly, and if that fails, can crash. This function ensures that + * everything is consistent, by exiting tweakmode everywhere there's missing + * pointers. + * + * This shouldn't happen, but the example blend file attached to #119615 needs + * this. + */ +static void version_nla_tweakmode_incomplete(Main *bmain) +{ + bool any_valid_tweakmode_left = false; + + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + AnimData *adt = BKE_animdata_from_id(id); + if (!adt || !(adt->flag & ADT_NLA_EDIT_ON)) { + continue; + } + + if (adt->act_track && adt->actstrip) { + /* Expected case. */ + any_valid_tweakmode_left = true; + continue; + } + + /* Not enough info in the blend file to reliably stay in tweak mode. This is the most important + * part of this versioning code, as it prevents future nullptr access. */ + BKE_nla_tweakmode_exit(adt); + } + FOREACH_MAIN_ID_END; + + if (any_valid_tweakmode_left) { + /* There are still NLA strips correctly in tweak mode. */ + return; + } + + /* Nothing is in a valid tweakmode, so just disable the corresponding flags on all scenes. */ + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + scene->flag &= ~SCE_NLA_EDIT_ON; + } +} + void do_versions_after_linking_400(FileData *fd, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 400, 9)) { @@ -443,6 +490,10 @@ void do_versions_after_linking_400(FileData *fd, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 23)) { + version_nla_tweakmode_incomplete(bmain); + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/makesrna/intern/rna_animation.cc b/source/blender/makesrna/intern/rna_animation.cc index 76316f6b0de..dfe1fbf7825 100644 --- a/source/blender/makesrna/intern/rna_animation.cc +++ b/source/blender/makesrna/intern/rna_animation.cc @@ -12,6 +12,7 @@ #include "DNA_anim_types.h" #include "DNA_scene_types.h" +#include "BLI_listbase_wrapper.hh" #include "BLI_utildefines.h" #include "BLT_translation.hh" @@ -189,6 +190,34 @@ bool rna_AnimData_tweakmode_override_apply(Main * /*bmain*/, anim_data_dst->flag = (anim_data_dst->flag & ~ADT_NLA_EDIT_ON) | (anim_data_src->flag & ADT_NLA_EDIT_ON); + + if (!(anim_data_dst->flag & ADT_NLA_EDIT_ON)) { + /* If tweak mode is not enabled, there's nothing left to do. */ + return true; + } + + if (!anim_data_src->act_track || !anim_data_src->actstrip) { + /* If there is not enough information to find the active track/strip, don't bother. */ + return true; + } + + /* AnimData::act_track and AnimData::actstrip are not directly exposed to RNA as editable & + * overridable, so the override doesn't contain this info. Reconstruct the pointers by name. */ + for (NlaTrack *track : blender::ListBaseWrapper(anim_data_dst->nla_tracks)) { + if (!STREQ(track->name, anim_data_src->act_track->name)) { + continue; + } + + NlaStrip *strip = BKE_nlastrip_find_by_name(track, anim_data_src->actstrip->name); + if (!strip) { + continue; + } + + anim_data_dst->act_track = track; + anim_data_dst->actstrip = strip; + break; + } + return true; }